Ansichtsrahmenänderungen zwischen viewWillAppear: und viewDidAppear:

85

Ich habe in meiner Anwendung ein seltsames Verhalten festgestellt, bei dem eine verbundene IBOutletAnsicht den Rahmen ihrer verbundenen Ansicht zwischen den Aufrufen in meinem Ansichtscontroller an viewWillAppear:und hat viewDidAppear:. Hier ist der relevante Code in meiner UIViewControllerUnterklasse:

-(void)viewWillAppear:(BOOL)animated {
    NSLog(@"%@", self.scrollView);
}

-(void)viewDidAppear:(BOOL)animated {
    NSLog(@"%@", self.scrollView);
}

und die resultierende Protokollausgabe:

MyApp[61880:c07] <UIScrollView: 0x1057eff0; frame = (0 0; 0 0); clipsToBounds = YES; autoresize = TM+BM; gestureRecognizers = <NSArray: 0x10580100>; layer = <CALayer: 0x1057f210>; contentOffset: {0, 0}>
MyApp[61880:c07] <UIScrollView: 0x1057eff0; frame = (0 44; 320 416); clipsToBounds = YES; autoresize = TM+BM; gestureRecognizers = <NSArray: 0x10580100>; layer = <CALayer: 0x1057f210>; contentOffset: {0, 0}>

Dies zeigt deutlich, dass sich der Frame zwischen den beiden Aufrufen ändert. Ich wollte das Setup mit der Ansicht in der viewDidLoadMethode durchführen, aber wenn der Inhalt nicht verfügbar ist, um ihn zu ändern, bis er auf dem Bildschirm angezeigt wird, scheint dies ziemlich nutzlos zu sein. Was könnte passieren?

Jumhyn
quelle
2
Verwenden Sie Autolayout? Fügen Sie diese Ansicht im Interface Builder oder programmgesteuert hinzu?
Andrea
Autolayout ist aktiviert und diese Ansicht wird in IB aus einem Storyboard erstellt.
Jumhyn
1
Ich habe nie ein Storyboard verwendet, aber am wahrscheinlichsten ist es richtig. Die Verwendung des Autolayout-Rahmens Ihrer Ansichten wird festgelegt, wenn die Autolayout-Engine ihre Berechnung startet. Versuchen Sie, das Gleiche direkt nach der Super-of - (void) viewDidLayoutSubviews-Methode Ihres View-Controllers zu fragen.
Andrea
Dadurch wird mein Ereignis zum richtigen Zeitpunkt erfolgreich ausgelöst. Diese Methode wird jedoch auch aufgerufen, wenn ich eine Animation für die Ansicht ausführe.
Jumhyn
1
viewDidLayoutSubviewswar der richtige Weg. Ich musste nur meinen gesamten Inhalt in eine Unteransicht einfügen, damit die Methode nicht erneut aufgerufen wurde, wenn ich den Rahmen der Hauptansicht änderte.
Jumhyn

Antworten:

111

AutolayoutWir haben die Art und Weise, wie wir die Benutzeroberfläche unserer Ansichten entwerfen und entwickeln, grundlegend geändert. Einer der Hauptunterschiede besteht darin, dass autolayoutsich unsere Ansichtsgrößen nicht sofort ändern, sondern nur, wenn sie ausgelöst werden, dh zu einem bestimmten Zeitpunkt. Wir können sie jedoch zwingen, unsere Einschränkungen sofort neu zu berechnen oder sie als "bedarfsbedürftig" für das Layout zu markieren. Es funktioniert wie -setNeedDisplay.
Die große Herausforderung für mich bestand darin, das zu verstehen und zu akzeptieren. Wir müssen keine Masken für die automatische Größenänderung mehr verwenden, und der Rahmen ist zu einer nutzlosen Eigenschaft beim Platzieren unserer Ansichten geworden. Wir müssen nicht mehr über die Ansichtsposition nachdenken, aber wir sollten darüber nachdenken, wie wir sie in einem Raum sehen wollen, der miteinander in Beziehung steht.
Wenn wir alte Autoresizing-Maske und Autolayout mischen möchten, treten Probleme auf. Wir sollten sehr bald über die Implementierung von Autolayout nachdenken und versuchen, den alten Ansatz nicht in eine auf Autolayout basierende Ansichtshierarchie zu mischen.
Es ist in Ordnung, eine Containeransicht zu haben, die nur Masken für die automatische Größenänderung verwendet, z. B. eine Hauptansicht eines Ansichtscontrollers. Es ist jedoch besser, wenn wir nicht versuchen, zu mischen.
Ich habe nie ein Storyboard verwendet, aber am wahrscheinlichsten ist es richtig. Mit Autolayout wird der Rahmen Ihrer Ansichten festgelegt, wenn die Autolayout-Engine ihre Berechnung startet. Versuchen Sie, das Gleiche direkt nach der Super- - (void)viewDidLayoutSubviewsMethode Ihres View Controllers zu fragen .
Diese Methode wird aufgerufen, wenn die Autolayout-Engine die Berechnung der Frames Ihrer Ansichten beendet hat.

Andrea
quelle
5
- (void) viewDidLayoutSubviews ist die Antwort für mich! Vielen Dank!
FrizzTheSnail
Diese Antwort ist wirklich nicht richtig. Ja, natürlich müssen Sie seit (5?) Jahren Autolayout verwenden. Es gibt jedoch eine beliebige Anzahl von Situationen ( mit Autolayout ), in denen Sie beispielsweise etwas auf einem Bildschirm hinzufügen müssen, "kurz bevor es dem Benutzer angezeigt wird". (Wenn Sie es in viewDidAppear tun, erhalten Sie ein Flimmern. Wenn Sie es in viewWillAppear tun, sind die Positionen falsch.) Die eigentliche Antwort ist in der Tat, viewDidLayoutSubviews zu verwenden.
Fattie
add something on a screen, "just before it appears to the user". The actual answer is indeed to use viewDidLayoutSubviews.... Sie werden diese Ansicht oft zeigen
Andrea
156

Aus der Dokumentation:

viewWillAppear:

Benachrichtigt den Ansichtscontroller, dass seine Ansicht einer Ansichtshierarchie hinzugefügt werden soll.

viewDidAppear:

Benachrichtigt den Ansichtscontroller, dass seine Ansicht einer Ansichtshierarchie hinzugefügt wurde.

Infolgedessen sind die Frames der Unteransichten noch nicht in der viewWillAppear :

Die geeignete Methode zum Ändern Ihrer Benutzeroberfläche, bevor die Ansicht auf dem Bildschirm angezeigt wird, ist:

viewDidLayoutSubviews

Benachrichtigt den Ansichtscontroller, dass seine Ansicht gerade seine Unteransichten angelegt hat.

George Sachpatzidis
quelle
2
Ugh, das ist nervig. Selbst der Wortlaut der Dokumentation lässt den Eindruck entstehen, dass viewWillApepar: und viewDidAppear: direkt nacheinander erfolgen sollten.
Jumhyn
2 Monate auf diese Antwort warten ... danke, ich habe sie gefunden !! VIELEN DANK!
Rafael Ruiz Muñoz
6
Zu beachten ist, dass viewDidLayoutSubviewsdies mehrmals und nicht immer mit demselben Frame aufgerufen wird (ich denke, es wird manchmal beim ersten Aufruf mit CGRectZero aufgerufen). Es wird für jede hinzugefügte Unteransicht und andere Änderungen in der Ansicht aufgerufen.
BauerMusic
Ich habe mich den ganzen Tag darauf eingelassen (viewDidLayoutSubviews werden mehrmals aufgerufen, auch wenn die Ansicht verworfen wird), habe nur die ef damit gesagt und die Logik in eine if-Anweisung eingefügt und einen Bool erstellt, der nach dem auf true gesetzt wird Code wurde einmal ausgeführt. Ich denke, es muss einen saubereren Weg geben, aber es ist bereits zu viel Zeit eingeflossen.
Magnet
wir müssen dem auf den Grund gehen! viewDidLayoutSubviews ist schrecklich, seNeedDisplay funktioniert nicht
Yaro
9

Anruf

self.scrollView.layoutIfNeeded ()

in Ihrer viewWillAppearMethode. Danach können Sie auf den Rahmen zugreifen und er hat den gleichen Wert wie beim DruckenviewDidAppear

andrei
quelle
1
Nein, das ist nicht richtig. Beispiel: Sie haben eine Navigationsleiste auf dem Bildschirm (von Ihrem Navigationscontroller). Auch nach layoutIfNeeded () ist die Höhe der Navigationsleiste nicht enthalten, sodass sich Ihre Rahmengröße ändert.
Xaphod
Dieses IS ein guter Weg , um eine erneute Berechnung eines Scroll Zargenaußenmaß zu zwingen , so dass es konsequent über die View - Layout Aufrufhierarchie ist , wenn der Scroll Rahmen innerhalb seiner Superview an Einschränkungen gebunden ist.
Smakus
3

In meinem Fall verschieben Sie alle rahmenbezogenen Methoden nach

override func viewWillLayoutSubviews()

funktionierte perfekt (ich habe versucht, Einschränkungen aus dem Storyboard zu ändern).

Siddhesh Mahadeshwar
quelle