Wygląda na to, że przy włączonym Xcode 8 viewDidLoad
wszystkie podglądy kontrolera widoku mają ten sam rozmiar 1000x1000. Dziwna rzecz, ale w porządku, viewDidLoad
nigdy nie było lepszym miejscem do poprawnego rozmiaru widoków.
Ale viewDidLayoutSubviews
jest!
A na moim obecnym projekcie staram się wydrukować rozmiar przycisku:
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
NSLog(@"%@", self.myButton);
}
Dziennik pokazuje rozmiar (1000x1000) dla myButton! Następnie, jeśli loguję się na przycisk, na przykład, dziennik pokazuje normalny rozmiar.
Używam autoukładu.
Czy to błąd?
ios
autolayout
ios10
xcode8
Jaskółka oknówka
źródło
źródło
(0, 0, 1000, 1000)
powiązana inicjalizacja to nowy sposób, w jaki Xcode inicjuje widoki z IB. Przed Xcode8, widoki były tworzone ze skonfigurowanym rozmiarem w xib, a następnie zmieniane zgodnie z ekranem zaraz po. Ale teraz w dokumencie IB nie ma skonfigurowanego rozmiaru, ponieważ rozmiar zależy od wyboru urządzenia (na dole ekranu). Tak więc prawdziwe pytanie brzmi: czy istnieje wiarygodne miejsce, w którym można sprawdzić ostateczną wielkość wyświetleń?Odpowiedzi:
Teraz Interface Builder pozwala użytkownikowi dynamicznie zmieniać rozmiar każdego kontrolera widoku w scenorysie, aby zasymulować rozmiar określonego urządzenia.
Przed tą funkcją użytkownik powinien ręcznie ustawić każdy rozmiar kontrolera widoku. Więc kontroler widoku został zapisany z określonym rozmiarem, który został użyty
initWithCoder
do ustawienia początkowej klatki.Teraz wygląda na to, że
initWithCoder
nie używaj rozmiaru zdefiniowanego w scenorysie i zdefiniuj rozmiar 1000x1000 pikseli dla widoku kontrolera widoku i wszystkich jego podglądów podrzędnych.Nie stanowi to problemu, ponieważ widoki powinny zawsze używać jednego z następujących rozwiązań układu:
autolayout, a wszystkie ograniczenia będą poprawnie układać widoki
autoresizingMask, która będzie układać każdy widok, do którego nie są dołączone żadne ograniczenia ( uwaga: automatyczne układanie i ograniczenia marginesów są teraz zgodne w tym samym widoku \ o /! )
Ale to jest problem dla wszystkich rzeczy układu związanego z warstwą widoku, jak
cornerRadius
, ponieważ ani autolayout ani automatycznej zmiany maska dotyczy właściwości warstw.Aby odpowiedzieć na ten problem, typowym sposobem jest użycie,
viewDidLayoutSubviews
jeśli jesteś w kontrolerze lublayoutSubview
jeśli jesteś w widoku. W tym momencie (nie zapomnij wywołać ichsuper
względnych metod), jesteś prawie pewien, że wszystko zostało zrobione!Całkiem pewny? Hum ... nie do końca, zauważyłem, i dlatego zadałem to pytanie, w niektórych przypadkach widok nadal ma rozmiar 1000x1000 w tej metodzie. Myślę, że nie ma odpowiedzi na moje własne pytanie. Aby podać jak najwięcej informacji na ten temat:
1- Dzieje się to tylko podczas układania komórek! W
UITableViewCell
&UICollectionViewCell
podklasach,layoutSubview
nie będzie wywoływane po prawidłowym rozmieszczeniu podglądów podrzędnych.2- Jak zauważył @EugenDimboiu (proszę zagłosuj za jego odpowiedź, jeśli jest dla ciebie przydatna), wywołanie
[myView layoutIfNeeded]
nie- ułożonego podglądu podrzędnego spowoduje prawidłowe ułożenie go w samą porę.- (void)layoutSubviews { [super layoutSubviews]; NSLog (self.myLabel); // 1000x1000 size [self.myLabel layoutIfNeeded]; NSLog (self.myLabel); // normal size }
3- Moim zdaniem jest to zdecydowanie błąd. Wysłałem to na radar (id 28562874).
PS: Nie jestem Anglikiem, więc nie krępuj się edytować mojego posta, jeśli moja gramatyka powinna zostać poprawiona;)
PS2: Jeśli masz jakieś lepsze rozwiązanie, nie wahaj się pisać kolejnej odpowiedzi. Przesunę zaakceptowaną odpowiedź.
źródło
UIStackView
wnętrzeUICollectionViewCell
nie przywracało prawidłowej wysokości podczasviewDidLayoutSubviews
. ZadzwonienielayoutIfNeeded
natychmiast rozwiązało problem.Czy Twój przycisk ma zaokrąglone rogi? Zadzwoń
layoutIfNeeded()
wcześniej.źródło
Rozwiązanie: Wrap wszystko wewnątrz
viewDidLayoutSubviews
wDispatchQueue.main.async
.// swift 3 override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() DispatchQueue.main.async { // do stuff here } }
źródło
-viewDidLoad
... takie dziwne obejścieWiem, że to nie było twoje dokładne pytanie, ale natknąłem się na podobny problem, w którym, podobnie jak podczas aktualizacji, niektóre z moich widoków były pomieszane, mimo że w widoku viewDidLayoutSubviews miał prawidłowy rozmiar klatki. Zgodnie z informacjami o wydaniu iOS 10:
Zasadniczo nie można wywołać layoutIfNeeded na obiekcie podrzędnym View, jeśli używasz translatesAutoresizingMaskIntoConstraints - teraz wywołanie layoutIfNeeded musi znajdować się w superView i nadal możesz wywołać to w viewDidLayoutSubviews.
źródło
Jeśli ramki nie są poprawne w layoutSubViews (a nie są), możesz wysłać asynchroniczny fragment kodu w głównym wątku. Daje to systemowi trochę czasu na wykonanie układu. Kiedy wysyłany blok jest wykonywany, ramki mają odpowiednie rozmiary.
źródło
To rozwiązało dla mnie (absurdalnie irytujący) problem:
- (void) viewDidLayoutSubviews { [super viewDidLayoutSubviews]; self.view.frame = CGRectMake(0,0,[[UIScreen mainScreen] bounds].size.width,[[UIScreen mainScreen] bounds].size.height); }
Edycja / Uwaga: to jest dla pełnego ekranu ViewController.
źródło
[super viewDidLayoutSubviews];
tę metodę z powodu wielu rzeczy związanych z autoukładem wykonywanych przez sam widokUIView
. Ale jeśli musisz wykonać specjalne obliczenia na określonej ramce widoku, odpowiedź Eugena działa: zadzwońlayoutIfNeeded
do niej. Mam wrażenie, że to nie jest najlepsze rozwiązanie, ale nadal nie znalazłem lepszego.Właściwie
viewDidLayoutSubviews
też nie jest to najlepsze miejsce na kadrowanie widoku. O ile zrozumiałem, od teraz jedynym miejscem, w którym należy to zrobić, jestlayoutSubviews
metoda w kodzie rzeczywistego widoku. Żałuję, że nie mam racji, niech ktoś mnie poprawi, jeśli to nieprawda!źródło
viewDidLayoutSubviews
jest dość niejednoznaczna. Drugie zdanie w „dyskusjach” w pewien sposób przeczy temu ostatniemu. developer.apple.com/reference/uikit/uiviewcontroller/…Zgłosiłem już ten problem do Apple, ten problem istnieje od dawna, kiedy inicjujesz UIViewController z Xib, ale znalazłem całkiem niezłe obejście. Oprócz tego znalazłem ten problem w niektórych przypadkach, gdy layoutIfNeeded na UICollectionView i UITableView, gdy źródło danych nie jest ustawione w początkowym momencie, i potrzebowałem go również zmienić.
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() } } }
Wysyłka po rozszerzeniu:
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() } }
Rozszerzenie Swizzle:
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 } }
źródło
Mój problem został rozwiązany poprzez zmianę użycia z
-(void)viewDidLayoutSubviews{ [super viewDidLayoutSubviews]; self.viewLoginMailTop.constant = -self.viewLoginMail.bounds.size.height; }
do
-(void)viewWillLayoutSubviews{ [super viewWillLayoutSubviews]; self.viewLoginMailTop.constant = -self.viewLoginMail.bounds.size.height; }
A więc od Did do Will
Super dziwne
źródło
Lepsze rozwiązanie dla mnie.
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_) } }
Za pomocą
class CircleShapeComplement: LayoutComplementProtocol { func didLayoutSubviews(with targetView_: UIView) { targetView_.layer.cornerRadius = targetView_.frame.size.height / 2 } } myButton.add(layoutComplement: CircleShapeComplement())
źródło
Zastąp układ Podwarstwy (warstwy: CALayer) zamiast podglądy układu w podglądzie komórki, aby mieć prawidłowe klatki
źródło
Jeśli musisz zrobić coś w oparciu o ramkę swojego widoku - nadpisz układSubviews i call layout IfNeeded
override func layoutSubviews() { super.layoutSubviews() yourView.layoutIfNeeded() setGradientForYourView() }
Miałem problem z viewDidLayoutSubviews zwracającym niewłaściwą ramkę dla mojego widoku, dla którego musiałem dodać gradient. I tylko layoutIfNeeded zrobił właściwą rzecz :)
źródło
Zgodnie z nową aktualizacją w iOS jest to w rzeczywistości błąd, ale możemy go zmniejszyć, używając -
Jeśli używasz xib z autoukładem w swoim projekcie, musisz po prostu zaktualizować ramkę w ustawieniach autolayout, znajdź obraz do tego.
źródło