Xcode 8 ve iOS10'dan beri, görünümler viewDidLayoutSubviews üzerinde düzgün boyutlandırılmadı


95

Görünüşe göre Xcode 8 açıkken viewDidLoad, tüm görüntü denetleyici alt görünümleri aynı boyutta 1000x1000. Tuhaf bir şey, ama tamam, viewDidLoadgörüşleri doğru şekilde boyutlandırmak için daha iyi bir yer olmamıştı.

Ama viewDidLayoutSubviewsöyle!

Ve mevcut projemde, bir düğme boyutunu yazdırmaya çalışıyorum:

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];

    NSLog(@"%@", self.myButton);
}

Günlük, myButton için (1000x1000) boyutunu gösterir! Daha sonra, örneğin bir düğmeyi tıklayarak oturum açarsam, günlük normal bir boyut gösterir.

Otomatik düzen kullanıyorum.

Bu bir böcek mi?


1
UIImageView ile aynı sorunu yaşıyorum - yazdırdığımda garip bir çerçeve alıyorum = (0 0; 1000 1000) ;. Bir UITableViewCell'in içindeyim ve tablo görünümünü bir kez yenilediğimde çerçeve olmasını beklediğim şeydir (ayrıca hücre görünüm penceresinden çıkıp tekrar geri geldiğinde). Bunun neden olduğu hakkında herhangi bir fikri olan var mı (varsayılan olarak tuhaf çerçeve)?
Eugen Dimboiu

4
Bence (0, 0, 1000, 1000)bağlı başlatma, Xcode'un IB'den gelen görüşleri yorumlamasının yeni yolu. Xcode8'den önce, görünümler xib'de yapılandırılmış boyutlarıyla oluşturuldu ve hemen ardından ekrana göre yeniden boyutlandırıldı. Ancak artık, boyut cihaz seçiminize bağlı olduğundan (ekranın alt kısmında) IB belgesinde yapılandırılmış bir boyut yoktur. Öyleyse asıl soru şu: Görünümlerin nihai boyutunun kontrol edilebileceği güvenilir bir yer var mı?
Martin

4
Düğmeniz için yuvarlatılmış köşeler mi kullanıyorsunuz? Daha önce layoutIfNeeded () öğesini çağırmayı deneyin.
Eugen Dimboiu

İlginç. Gerçekten de yuvarlak bir sınır hesaplamak için görüş çerçevesini kullanıyordum. Soruyu cevaplamasa bile işe yarıyor. Akılda tutulması gereken iyi bir ipucu. Teşekkürler!
Martin

Bir uitextfield'ın sağ görünümünün içine bir resim düğmesi ayarlarken benzer sorunlar yaşadığımı düşünüyorum. Görüntü düğmesinin yüksekliğini ve genişliğini metin alanının yüksekliğine ayarlamak istedim, böylece en boy oranını ve kapsayıcıdan çıkmasını sağladı.
atlantach_james

Yanıtlar:


98

Artık, Arayüz Oluşturucu, belirli bir cihazın boyutunu simüle etmek için kullanıcının film şeridindeki her görüntü denetleyicisinin boyutunu dinamik olarak değiştirmesine izin veriyor.

Bu işlevsellikten önce, kullanıcı her bir görünüm denetleyicisi boyutunu manuel olarak ayarlamalıdır. Böylece, görüntü denetleyicisi, initWithCoderbaşlangıç ​​karesini ayarlamak için kullanılan belirli bir boyutta kaydedildi .

Şimdi, initWithCoderfilm şeridinde tanımlanan boyutu kullanmadığı ve görüntü denetleyici görünümü ve tüm alt görünümleri için 1000x1000 piksellik bir boyut tanımladığı görülüyor.

Bu bir sorun değil, çünkü görünümler her zaman şu düzen çözümlerinden birini kullanmalıdır:

  • otomatik düzen ve tüm kısıtlamalar görünümlerinizi doğru bir şekilde düzenleyecektir

  • autoresizingMask, herhangi bir kısıtlama eklenmemiş her görünümü düzenleyecek ( unutmayın otomatik düzen ve kenar boşluğu kısıtlamaları artık aynı görünümde uyumludur \ o /! )

Ancak bu , görünüm katmanıyla ilgili tüm düzen öğeleri için bir sorundur cornerRadius, çünkü ne otomatik düzen ne de otomatik yeniden boyutlandırma maskesi katman özelliklerine uygulanmaz.

Bu soruna cevap vermenin yaygın yolu, viewDidLayoutSubviewskontrol cihazındaysanız veya layoutSubviewbir görünümdeyseniz kullanmaktır. Bu noktada ( supergöreceli yöntemlerini çağırmayı unutmayın ), tüm düzen işlerinin yapıldığından oldukça eminiz!

Oldukça emin? Hum ... tam olarak değil, belirttim ve bu yüzden bu soruyu sordum, bazı durumlarda görüş hala bu yöntemde 1000x1000 boyutunda. Kendi sorumun cevabı olmadığını düşünüyorum. Bununla ilgili maksimum bilgi vermek için:

1- Bu sadece hücreleri yerleştirirken olur! In UITableViewCell& UICollectionViewCellalt sınıflar, layoutSubviewçağrılmayacaktır sonra subviews doğru dışarı koydu olacaktır.

2- @EugenDimboiu'nun belirttiği gibi (lütfen sizin için yararlıysa cevabını destekleyin), yerleştirilmemiş alt görünümü çağırmak [myView layoutIfNeeded], tam zamanında doğru bir şekilde düzenleyecektir.

- (void)layoutSubviews {
    [super layoutSubviews];
    NSLog (self.myLabel); // 1000x1000 size 
    [self.myLabel layoutIfNeeded];
    NSLog (self.myLabel); // normal size
}

3- Bence bu kesinlikle bir hata. Radara gönderdim (id 28562874).

Not: Anadili İngilizce değilim, bu yüzden dilbilgimin düzeltilmesi gerekiyorsa gönderimi düzenlemekten çekinmeyin;)

Not2: Daha iyi bir çözümünüz varsa, başka bir yanıt yazmamaktan çekinmeyin. Kabul edilen cevabı değiştireceğim.


3
teşekkürler, ancak 28562874 kimliğine sahip radarı bulamadım, radar URL'sini alabilir miyim?
Joey

Manzaranın katmanları için de benim için bir cazibe gibi çalıştı!
hlynbech

@ Joey, hatamın bağlantısını alabilir miyim bilmiyorum. Herhangi bir doğrudan URL bulamadım ve görünüşe göre diğer kullanıcılar raporlarımı göremiyor. Bu SO yanıtı stackoverflow.com/a/145223/127493'e göre , bir hatanın önceliğini artırmanın en iyi yolunun bir kopya yapmak olduğu görülüyor.
Martin

Bu, Apple açısından aptallığın da ötesinde. Otomatik düzen görüntüleme denetleyicisi tarafından tam olarak belirlenmiş bir cihazım var ve metin alanlarının birçoğu ve bir UIView, bu aptal 0,0,1000,1000 çerçeveye sahip. Fakat hepsi değil. Bu QA'dan nasıl kaçabilir? Sanırım okuyamayacakları başka bir Radar dosyalayabilirim.
ahwulf

3
Görüş ve önerileriniz için size ve diğer herkese teşekkür ederim. Ben çünkü dışarı saçımı çekerek bütün gün geçirdi UIStackViewbir iç UICollectionViewCellsırasında doğru yükseklik dönen değildi viewDidLayoutSubviews. Aramak layoutIfNeededsorunu derhal çözdü.
Ruiz

40

Düğmeniz için yuvarlatılmış köşeler mi kullanıyorsunuz? Daha layoutIfNeeded()önce aramayı deneyin .


1
ahah, daha fazla itibar kazanmaya mı çalışıyorsun? Yorumumda da söylediğim gibi, bu soruya cevap vermiyor. Ancak, +1 kazandığınız için bana yardımcı oldu :)
Martin

4
Gelecekte birine yardımcı olabilir ve yorumları karşılaştırmak daha kolay
Eugen Dimboiu

1
Bana şimdi yardımcı oldu!
daidai

@daidai bunu duyduğuma sevindim!
Eugen Dimboiu

bu çalışıyor! ama neden? aynı zamanda doğru çerçeve boyutu elde etmek için doğru çözüm nedir?
Crashalot

25

Çözüm: inside Wrap her şey viewDidLayoutSubviewsiçinde DispatchQueue.main.async.

// swift 3

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    DispatchQueue.main.async {
        // do stuff here
    }
}

1
bu işe yarıyor olsa bile -viewDidLoad... bu kadar garip bir çözüm
medvedNick

1
Muhtemelen, viewDidLayoutSubviews görüntülemelerde sistemin yapılması gerekeni yapacak zamanı olmadığı için. Gönderiyi çağırmak, sistemin sözünü bitirmesine izin vererek bir runloop atlamanızı sağlar. Birkaç milisaniyelik bir uykuyla aynı davranışı elde edebildiğinizde
haklıysam

çünkü tüm UI görevleri ana iş parçacığı içinde gerçekleştirilmelidir, çözümünüz için teşekkürler!
danywarner

18

Bunun tam olarak sorunuz olmadığını biliyorum, ancak benzer bir sorunla karşılaştım, güncellemede olduğu gibi viewDidLayoutSubviews'te doğru çerçeve boyutuna sahip olmama rağmen görünümlerimden bazıları karıştı. İOS 10 Sürüm notlarına göre:

"Bir görünüme ihtiyaç duyuluyorsa, görünümü hareket ettirmesi beklenmez, ancak önceki sürümlerde, görünüm translatesAutoresizingMaskIntoConstraints olarak ayarlandıysa ve kısıtlamalarla konumlandırılmışsa layoutIfNeeded, düzeni göndermeden önce yerleşim motoruyla eşleşecek şekilde görünümü hareket ettirirdi. Bu değişiklikler bu davranışı düzeltir ve alıcının konumu ve boyutu genellikle layoutIfNeeded'den etkilenmez.

Bazı mevcut kodlar, şimdi düzeltilmiş olan bu yanlış davranışa güveniyor olabilir. İOS 10'dan önce bağlanan ikili dosyalar için davranış değişikliği yoktur, ancak iOS 10'da derleme yaparken, önceki alıcı olan translatesAutoresizingMaskIntoConstraints görünümünün bir denetimine -layoutIfNeeded göndererek veya başka bir şekilde onu daha önce konumlandırıp boyutlandırarak bazı durumları düzeltmeniz gerekebilir ( veya sonra, istediğiniz davranışa bağlı olarak) layoutIfNeeded.

Süper çağırmadan önce kendi kendine düzen alt görünümlerini ve kirli düzeni geçersiz kılan Otomatik Yerleşimi kullanan özel UIView alt sınıflarına sahip üçüncü taraf uygulamalar, iOS 10'da yeniden oluşturduklarında bir düzen geri bildirim döngüsünü tetikleme riski altındadır. bir noktada kendi kendine düzeni kirletmeyi durdurun (bu aramanın iOS 10'dan önceki sürümde atlandığını unutmayın). "

Esasen, eğer translatesAutoresizingMaskIntoConstraints kullanıyorsanız, View öğesinin bir alt nesnesinde layoutIfNeeded öğesini arayamazsınız - şimdi layoutIfNeeded çağrısının superView'da olması gerekir ve bunu hala viewDidLayoutSubviews içinde çağırabilirsiniz.


5

LayoutSubViews'ta (ki bunlar değil) çerçeveler doğru değilse, ana iş parçacığı üzerinde bir miktar kod eşzamansız olarak gönderebilirsiniz. Bu, sisteme düzeni yapması için biraz zaman verir. Gönderdiğiniz blok yürütüldüğünde, çerçevelerin uygun boyutları vardır.


3

Bu benim için (gülünç derecede can sıkıcı) sorunu çözdü:

- (void) viewDidLayoutSubviews {

    [super viewDidLayoutSubviews];

    self.view.frame = CGRectMake(0,0,[[UIScreen mainScreen] bounds].size.width,[[UIScreen mainScreen] bounds].size.height);

}

Düzenleme / Not: Bu, tam ekran bir ViewController içindir.


MainScreen sınırlarını kullanmak iyi bir çözüm değildir çünkü çoğu durumda viewController tüm ekran alanını kaplamaz. Ek olarak, [super viewDidLayoutSubviews];görünümün kendisi tarafından yapılan birçok otomatik düzen işlemi nedeniyle bu yöntemi çağırmalısınız
Martin

@Martin ne önerirsin? Bunun ideal görünmediği konusunda hemfikirim.
Crashalot

Cevabımda söylediğim gibi @Crashalot, otomatik düzen veya otomatik yeniden boyutlandırmaMask kullanarak UIViews. Ancak belirli bir görüş çerçevesinde özel hesaplama yapmanız gerekiyorsa, Eugen'in cevabı işe yarar: çağırın layoutIfNeeded. Bunun en iyi çözüm olmadığını hissediyorum , ama yine de daha iyisini bulamadım.
Martin

2

Aslında viewDidLayoutSubviewsbakış açınızı çerçevelemek için en iyi yer de değildir. Anladığım kadarıyla şu andan itibaren yapılması gereken tek yer layoutSubviews, gerçek görünümün kodundaki yöntemdir. Keşke haklı olmasaydım, birisi beni düzeltsin lütfen doğru değilse


Cevabınız için teşekkürler. Apple belgeleri viewDidLayoutSubviewsoldukça belirsiz. "Tartışmalardaki" ikinci cümle, sonuncusuyla bir şekilde çelişiyor. developer.apple.com/reference/uikit/uiviewcontroller/…
Martin

0

Bu sorunu zaten Apple'a bildirmiştim, bu sorun uzun zamandan beri var, Xib'den UIViewController'ı başlatırken var, ancak oldukça güzel bir çözüm buldum. Buna ek olarak, bazı durumlarda, UICollectionView ve UITableView üzerinde layoutIfNeeded veri kaynağı ilk anda ayarlanmadığında ve ayrıca onu değiştirmesi gerektiğinde bu sorunu buldum.

extension UIViewController {
    open override class func initialize() {
        if self !== UIViewController.self {
            return
        }
        DispatchQueue.once(token: "io.inspace.uiviewcontroller.swizzle") {
            ins_applyFixToViewFrameWhenLoadingFromNib()
        }
    }

    @objc func ins_setView(view: UIView!) {
        // View is loaded from xib file
        if nibBundle != nil && storyboard == nil && !view.frame.equalTo(UIScreen.main.bounds) {
            view.frame = UIScreen.main.bounds
            view.layoutIfNeeded()
        }
        ins_setView(view: view)
    }

    private class func ins_applyFixToViewFrameWhenLoadingFromNib() {
        UIViewController.swizzle(originalSelector: #selector(setter: UIViewController.view),
                                 with: #selector(UIViewController.ins_setView(view:)))
        UICollectionView.swizzle(originalSelector: #selector(UICollectionView.layoutSubviews),
                                 with: #selector(UICollectionView.ins_layoutSubviews))
        UITableView.swizzle(originalSelector: #selector(UITableView.layoutSubviews),
                                 with: #selector(UITableView.ins_layoutSubviews))
     }
}

extension UITableView {
    @objc fileprivate func ins_layoutSubviews() {
        if dataSource == nil {
            super.layoutSubviews()
        } else {
            ins_layoutSubviews()
        }
    }
}

extension UICollectionView {
    @objc fileprivate func ins_layoutSubviews() {
        if dataSource == nil {
            super.layoutSubviews()
        } else {
            ins_layoutSubviews()
        }
    }
}

Bir kez gönder uzantı:

extension DispatchQueue {

    private static var _onceTracker = [String]()

    /**
     Executes a block of code, associated with a unique token, only once.  The code is thread safe and will
     only execute the code once even in the presence of multithreaded calls.

     - parameter token: A unique reverse DNS style name such as com.vectorform.<name> or a GUID
     - parameter block: Block to execute once
     */
    public class func once(token: String, block: (Void) -> Void) {
        objc_sync_enter(self); defer { objc_sync_exit(self) }

        if _onceTracker.contains(token) {
            return
        }

        _onceTracker.append(token)
        block()
    }
}

Swizzle uzantısı:

extension NSObject {
    @discardableResult
    class func swizzle(originalSelector: Selector, with selector: Selector) -> Bool {

        var originalMethod: Method?
        var swizzledMethod: Method?

        originalMethod = class_getInstanceMethod(self, originalSelector)
        swizzledMethod = class_getInstanceMethod(self, selector)

        if originalMethod != nil && swizzledMethod != nil {
            method_exchangeImplementations(originalMethod!, swizzledMethod!)
            return true
        }
        return false
    }
}

0

Sorunum, kullanımı değiştirerek çözüldü.

-(void)viewDidLayoutSubviews{
    [super viewDidLayoutSubviews];
    self.viewLoginMailTop.constant = -self.viewLoginMail.bounds.size.height;
}

-e

-(void)viewWillLayoutSubviews{
    [super viewWillLayoutSubviews];
    self.viewLoginMailTop.constant = -self.viewLoginMail.bounds.size.height;
}

Yani, Did'den Will'e

Çok tuhaf


0

Benim için daha iyi çözüm.

protocol LayoutComplementProtocol {
    func didLayoutSubviews(with targetView_: UIView)
}

private class LayoutCaptureView: UIView {
    var targetView: UIView!
    var layoutComplements: [LayoutComplementProtocol] = []

    override func layoutSubviews() {
        super.layoutSubviews()

        for layoutComplement in self.layoutComplements {
            layoutComplement.didLayoutSubviews(with: self.targetView)
        }
    }
}

extension UIView {
    func add(layoutComplement layoutComplement_: LayoutComplementProtocol) {
        func findLayoutCapture() -> LayoutCaptureView {
            for subView in self.subviews {
                if subView is LayoutCaptureView {
                    return subView as? LayoutCaptureView
                }
            }
            let layoutCapture = LayoutCaptureView(frame: CGRect(x: -100, y: -100, width: 10, height: 10)) // not want to show, want to have size
            layoutCapture.targetView = self
            self.addSubview(layoutCapture)
            return layoutCapture
        }

        let layoutCapture = findLayoutCapture()
        layoutCapture.layoutComplements.append(layoutComplement_)
    }
}

Kullanma

class CircleShapeComplement: LayoutComplementProtocol {
    func didLayoutSubviews(with targetView_: UIView) {
        targetView_.layer.cornerRadius = targetView_.frame.size.height / 2
    }
}

myButton.add(layoutComplement: CircleShapeComplement())

0

Doğru karelere sahip olmak için hücre alt görünümündeki alt görünümler yerine layout alt katmanlarını (katman: CALayer) geçersiz kıl


0

Görünümünüzün çerçevesine göre bir şey yapmanız gerekirse - layoutSubviews'i geçersiz kılın ve layoutIfNeeded'i arayın

    override func layoutSubviews() {
    super.layoutSubviews()

    yourView.layoutIfNeeded()
    setGradientForYourView()
}

Beraber sorunu vardı viewDidLayoutSubviews yanlış çerçeve dönen bir gradyan eklemek için gerekli olan benim görünümü için. Ve sadece layoutIfNeeded doğru olanı yaptı :)


-1

İOS'taki yeni güncellemeye göre bu aslında bir hatadır, ancak bunu kullanarak azaltabiliriz -

Projenizde autolayout ile xib kullanıyorsanız, kareyi otomatik düzen ayarında güncellemeniz yeterlidir, lütfen bunun için resim bulun.görüntü açıklamasını buraya girin

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.