Wann wird layoutSubviews aufgerufen?

264

Ich habe eine benutzerdefinierte Ansicht, die layoutSubviewwährend der Animation keine Nachrichten erhält .

Ich habe eine Ansicht, die den Bildschirm ausfüllt. Am unteren Bildschirmrand befindet sich eine benutzerdefinierte Unteransicht, deren Größe in Interface Builder korrekt geändert wird, wenn ich die Höhe der Navigationsleiste ändere. layoutSubviewswird aufgerufen, wenn die Ansicht erstellt wird, jedoch nie wieder. Meine Unteransichten sind korrekt angelegt. Wenn ich die Statusleiste für eingehende Anrufe ausschalte, wird die Unteransicht überhaupt layoutSubviewsnicht aufgerufen, obwohl die Hauptansicht die Größenänderung animiert.

Unter welchen Umständen heißt das layoutSubviewseigentlich?

Ich habe autoresizesSubviewsauf NOmeine benutzerdefinierte Ansicht. Und im Interface Builder habe ich die oberen und unteren Streben und den vertikalen Pfeil eingestellt.


Ein weiterer Teil des Puzzles ist, dass das Fenster zum Schlüssel gemacht werden muss:

[window makeKeyAndVisible];

Andernfalls wird die Größe der Unteransichten nicht automatisch geändert.

Steve Weller
quelle

Antworten:

492

Ich hatte eine ähnliche Frage, war aber mit der Antwort (oder einer, die ich im Internet finden konnte) nicht zufrieden. Deshalb habe ich sie in der Praxis ausprobiert und hier ist, was ich bekommen habe:

  • initführt nicht dazu, gerufen layoutSubviewszu werden (duh)
  • addSubview:bewirkt layoutSubviews, dass die hinzugefügte Ansicht, die hinzugefügte Ansicht (Zielansicht) und alle Unteransichten des Ziels aufgerufen werden
  • view setFrame ruft layoutSubviewsdie Ansicht, deren Frame eingestellt ist, nur dann intelligent auf, wenn der Größenparameter des Frames unterschiedlich ist
  • Durch das Scrollen einer UIScrollView wird layoutSubviewsdie scrollView und ihre Übersicht aufgerufen
  • Durch Drehen eines Geräts wird nur layoutSubviewdie übergeordnete Ansicht aufgerufen (die primäre Ansicht des antwortenden viewControllers).
  • Wenn Sie die Größe einer Ansicht ändern, wird layoutSubviewsderen Übersicht aufgerufen

Meine Ergebnisse - http://blog.logichigh.com/2011/03/16/when-does-layoutsubviews-get-called/

BadPirate
quelle
1
Gute Antwort. Ich habe mich immer gefragt layoutSubviews. Hat initWithFrame:Ursache layoutSubviewszu nennen?
Robert
2
@ Robert - Ich habe initWithFrame verwendet ... also nein.
BadPirate
8
@ BadPirate: Ja . Nach meinen Experimenten, wenn Sie die Größe ändern view1.1, ruft layoutSubviewsvon view1und dann layoutSubviewsvon view1.1. Dieser Aufruf wird nicht unbegrenzt an die Superviews weitergegeben, sondern view1.1.1nur layoutSubviewsbei view1.1und view1.1.1. Nur zu bewegen, ohne seine Größe zu ändern, ruft layoutSubviewskeinen von ihnen auf.
João Portela
1
viewDidLoad wird nicht in UIView (sondern in UIViewController) aufgerufen. View Did load wird aufgerufen, nachdem die UIView gestartet wurde.
BadPirate
2
Basierend auf meinem Experiment kann die zweite Regel nicht genau sein: wenn ich hinzufügen , view1.2in view1, layoutSubviewsvon view1.2und view1bin berufen, aber layoutSubviewsdie view1.1wird nicht aufgerufen. ( view1.1und view1.2sind Unteransichten von view1). Das heißt, nicht alle Unteransichten der Zielansicht werden als layoutSubviewsMethode bezeichnet .
HongchaoZhang
96

Aufbauend auf der vorherigen Antwort von @BadPirate habe ich etwas weiter experimentiert und einige Klarstellungen / Korrekturen vorgenommen. Ich habe festgestellt, dass layoutSubviews:eine Ansicht genau dann aufgerufen wird, wenn:

  • Die eigenen Grenzen (nicht der Rahmen) haben sich geändert.
  • Die Grenzen einer seiner direkten Unteransichten haben sich geändert.
  • Der Ansicht wird eine Unteransicht hinzugefügt oder aus der Ansicht entfernt.

Einige relevante Details:

  • Die Grenzen gelten nur dann als geändert, wenn der neue Wert unterschiedlich ist, einschließlich eines anderen Ursprungs . Beachten Sie insbesondere, dass dies layoutSubviews:immer dann aufgerufen wird, wenn eine UIScrollView einen Bildlauf durchführt, da sie den Bildlauf durch Ändern des Ursprungs ihrer Grenzen ausführt.
  • Durch Ändern des Rahmens werden die Grenzen nur geändert, wenn sich die Größe geändert hat, da dies das einzige Element ist, das an die Eigenschaft bounds weitergegeben wird.
  • Eine Änderung der Grenzen einer Ansicht, die sich noch nicht in einer Ansichtshierarchie befindet, führt zu einem Aufruf, layoutSubviews: wann die Ansicht schließlich zu einer Ansichtshierarchie hinzugefügt wird .
  • Und der Vollständigkeit halber : Diese Trigger rufen nicht direkt layoutSubviews auf, sondern callen setNeedsLayout, wodurch ein Flag gesetzt / gehisst wird. Bei jeder Iteration der Ausführungsschleife wird für alle Ansichten in der Ansichtshierarchie dieses Flag aktiviert. Für jede Ansicht, in der das Flag angehoben gefunden wird, layoutSubviews:wird es aufgerufen und das Flag wird zurückgesetzt. Ansichten weiter oben in der Hierarchie werden zuerst überprüft / aufgerufen.
Patrick Pijnappel
quelle
5
Ich kann diese Antwort nicht genug bewerten. Es sollte die beste Antwort sein. Die drei angegebenen Regeln sind alles, was Sie brauchen. Ich bin noch nie auf ein layoutSubview-Verhalten gestoßen, das diese Regeln nicht perfekt beschrieben haben.
Pärserk
1
Ich glaube nicht, dass layoutSubviews aufgerufen wird, wenn die Grenzen einer direkten Unteransicht geändert werden. Ich denke, der Aufruf von layoutSubviews ist nur ein Nebeneffekt Ihrer Tests. In einigen Fällen werden layoutSubviews nicht aufgerufen, wenn sich die Grenzen einer Unteransicht ändern. Bitte überprüfen Sie die Antwort von frogcjn, da seine Antwort auf der Dokumentation von Apple basiert und nicht nur auf Experimenten.
Simon Backx
19

https://developer.apple.com/library/prerelease/tvos/documentation/WindowsViews/Conceptual/ViewPG_iPhoneOS/CreatingViews/CreatingViews.html#//apple_ref/doc/uid/TP40009503-CH5-SW1

Layoutänderungen können immer dann auftreten, wenn eines der folgenden Ereignisse in einer Ansicht auftritt:

ein. Die Größe des Rechtecks ​​einer Ansicht ändert sich.
b. Es tritt eine Änderung der Schnittstellenausrichtung auf, die normalerweise eine Änderung des Begrenzungsrechtecks ​​der Stammansicht auslöst.
c. Der mit der Ebenenebene der Ansicht verknüpfte Satz von Kernanimations-Unterebenen ändert sich und erfordert ein Layout.
d. Ihre Anwendung erzwingt das Auftreten des Layouts durch Aufrufen der   Methode setNeedsLayout oder  layoutIfNeededeiner Ansicht.
e. Ihre Anwendung erzwingt das Layout, indem sie die setNeedsLayout Methode des zugrunde liegenden Ebenenobjekts der Ansicht aufruft  .

Frosch
quelle
Es ist auch wichtig darauf hinzuweisen, dass keines dieser Ereignisse aufgerufen wird, wenn die Ansicht noch nicht zum Ansichtsstapel hinzugefügt wurde. Sie fügen dies mit dem Wort "can" ein, aber speziell wird es im nächsten verfügbaren Zyklus des Hauptthreads der Anwendung aufgerufen, wenn es durch eines dieser Ereignisse als Layout erforderlich markiert wurde.
user1122069
13

Einige der Punkte in der Antwort von BadPirate sind nur teilweise richtig:

  1. Für addSubViewPunkt

    addSubview bewirkt, dass layoutSubviews für die hinzugefügte Ansicht, die Ansicht, zu der es hinzugefügt wird (Zielansicht) und alle Unteransichten des Ziels aufgerufen werden.

    Dies hängt von der automatischen Größenmaske der Ansicht (Zielansicht) ab. Wenn die automatische Größenmaske aktiviert ist, wird layoutSubview jeweils aufgerufen addSubview. Wenn keine Maske für die automatische Größenänderung vorhanden ist, wird layoutSubview nur aufgerufen, wenn sich die Rahmengröße der Ansicht (Zielansicht) ändert.

    Beispiel: Wenn Sie UIView programmgesteuert erstellt haben (standardmäßig gibt es keine Maske für die automatische Größenänderung), wird LayoutSubview nur aufgerufen, wenn sich der UIView-Frame nicht bei jedem ändert addSubview.

    Durch diese Technik erhöht sich auch die Leistung der Anwendung.

  2. Für den Gerätedrehpunkt

    Durch Drehen eines Geräts wird layoutSubview nur in der übergeordneten Ansicht (der primären Ansicht des antwortenden viewControllers) aufgerufen.

    Dies kann nur zutreffen, wenn sich Ihre VC in der VC-Hierarchie befindet (root at window.rootViewController). Dies ist der häufigste Fall. Wenn Sie in iOS 5 eine VC erstellen, diese jedoch keiner anderen VC hinzugefügt wird, wird diese VC beim Drehen des Geräts nicht bemerkt. Daher wird seine Ansicht beim Aufrufen von layoutSubviews nicht bemerkt.

Mohit Nigam
quelle
9

Ich habe die Lösung auf das Bestehen von Interface Builder zurückgeführt, dass Federn in einer Ansicht, in der die simulierten Bildschirmelemente aktiviert sind (Statusleiste usw.), nicht geändert werden können. Da die Federn für die Hauptansicht ausgeschaltet waren, konnte diese Ansicht die Größe nicht ändern und wurde daher beim Anzeigen der Anrufleiste in ihrer Gesamtheit nach unten gescrollt.

Durch Deaktivieren der simulierten Funktionen, Ändern der Größe der Ansicht und korrektes Einstellen der Federn wurde die Animation ausgeführt und meine Methode aufgerufen.

Ein zusätzliches Problem beim Debuggen besteht darin, dass der Simulator die App beendet, wenn der Anrufstatus über das Menü umgeschaltet wird. App beenden = kein Debugger.

Steve Weller
quelle
Wollen Sie damit sagen, dass layoutSubviews aufgerufen wird, wenn die Größe der Ansicht geändert wird? Ich habe immer angenommen, dass es nicht ...
Andrey Tarantsov
Es ist. Wenn die Größe einer Ansicht geändert wird, muss sie etwas mit ihren Unteransichten tun. Wenn Sie es nicht bereitstellen, bewegt es sie automatisch mit Federn, Streben usw.
Steve Weller
8

Durch Aufrufen [self.view setNeedsLayout]; von viewController wird viewDidLayoutSubviews aufgerufen

Bademi
quelle
5

Haben Sie sich layoutIfNeeded angesehen?

Das Dokumentations-Snippet finden Sie weiter unten. Funktioniert die Animation, wenn Sie diese Methode während der Animation explizit aufrufen?

layoutIfNeeded Legt bei Bedarf die Unteransichten fest.

- (void)layoutIfNeeded

Diskussion Verwenden Sie diese Methode, um das Layout von Unteransichten vor dem Zeichnen zu erzwingen.

Verfügbarkeit Verfügbar in iPhone OS 2.0 und höher.

Willi Ballenthin
quelle
2

Bei der Migration einer OpenGL-App von SDK 3 auf 4 wurde layoutSubviews nicht mehr aufgerufen. Nach vielen Versuchen und Irrtümern öffnete ich schließlich MainWindow.xib, wählte das Fensterobjekt aus, wählte im Inspektor die Registerkarte Fensterattribute (ganz links) und aktivierte "Beim Start sichtbar". Es scheint, dass es in SDK 3 immer noch einen layoutSubViews-Aufruf verursachte, aber nicht in 4.

6 Stunden Frust sind zu Ende.

Tal Yaniv
quelle
Hast du den Fensterschlüssel gemacht? Wenn nicht, kann dies dazu führen, dass alle möglichen interessanten Dinge nicht passieren.
Steve Weller
-2

Ein ziemlich dunkler, aber möglicherweise wichtiger Fall, wenn er layoutSubviewsnie angerufen wird, ist:

import UIKit

class View: UIView {

    override class var layerClass: AnyClass { return Layer.self }

    class Layer: CALayer {
        override func layoutSublayers() {
            // if we don't call super.layoutSublayers()...
            print(type(of: self), #function)
        }
    }

    override func layoutSubviews() {
        // ... this method never gets called by the OS!
        print(type(of: self), #function)
    }
}

let view = View(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
milos
quelle
1
Warum ist diese Antwort hier drin?
Dominik Bucher