Wann kann ich Layoutbeschränkungen aktivieren / deaktivieren?

104

Ich habe in IB mehrere Sätze von Einschränkungen eingerichtet und möchte je nach Status programmgesteuert zwischen diesen wechseln. Es gibt eine constraintsAOutlet-Sammlung, die alle als von IB installiert markiert sind, und eine constraintsBOutlet-Sammlung, die alle in IB deinstalliert sind.

Ich kann programmgesteuert zwischen den beiden Sätzen umschalten:

NSLayoutConstraint.deactivateConstraints(constraintsA)
NSLayoutConstraint.activateConstraints(constraintsB)

Aber ... ich kann nicht herausfinden, wann ich das tun soll. Es scheint, als sollte ich das einmal tun können viewDidLoad, aber ich kann das nicht zum Laufen bringen. Ich habe versucht anzurufen view.updateConstraints()und view.layoutSubviews()nach dem Festlegen der Einschränkungen, aber ohne Erfolg.

Ich habe festgestellt, dass, wenn ich die Einschränkungen in viewDidLayoutSubviewsallem festlege, alles wie erwartet funktioniert. Ich würde gerne zwei Dinge wissen ...

  1. Warum bekomme ich dieses Verhalten?
  2. Ist es möglich, Einschränkungen in viewDidLoad zu aktivieren / deaktivieren?
tybro0103
quelle
2
Meinen Sie damit, dass die deaktivierten und aktivierten Einschränkungen in viewWillLayoutSubviews funktioniert haben? Ich habe das versucht und es hat dort oder in viewDidLoad nicht funktioniert. Es hat in viewDidAppear funktioniert. Die Ansicht wurde dort angezeigt, wo die neuen Einschränkungen sie platzieren sollten. Wenn ich mich jedoch im Querformat drehte, wurde die Ansicht an die Position zurückgesetzt, die durch die in IB festgelegten Einschränkungen bestimmt wurde (und blieb dort, als ich mich wieder im Hochformat drehte). Beim Protokollieren der Einschränkungen wurden die richtigen angezeigt (die neu aktivierten). Das scheint mir ein Fehler zu sein.
Rdelmar
1
Ja, sie waren gültig (sie haben in viewDidAppear gearbeitet), und es besteht keine Notwendigkeit, super aufzurufen, da es keine Standardimplementierung von viewWillLayoutSubviews gibt (ich habe es trotzdem mit super versucht, aber das machte keinen Unterschied).
Rdelmar
1
@rdelmar Ich habe gerade die Gelegenheit bekommen, mehr zu testen ... Ich kann überprüfen, ob ich tatsächlich das gleiche Verhalten habe, das Sie beschrieben haben ... funktioniert zuerst in viewDidAppear, kehrt dann aber bei Drehung zurück.
Tybro0103
3
Anscheinend können Sie Einschränkungen nicht als zu diesem Zweck nicht in IB installiert markieren. Ich habe diese Informationen hier gefunden: stackoverflow.com/questions/27663249/… und das Problem wurde für mich gelöst.
Stefan
1
Ich habe meine Einschränkungen auf die gleiche Weise wie in der Frage beschrieben implementieren lassen, außer dass ich einige davon in viewDidAppear aktiviert / deaktiviert habe. Dies funktionierte, aber Sie konnten sehen, dass die Elemente schnell ihre Position änderten (ein kleines, aber unerwünschtes Problem). Das Ändern in viewWillAppear oder viewDidLoad hat nicht funktioniert. Nachdem ich diese Frage gelesen hatte, versuchte ich, diese Änderung in viewDidLayoutSubviews vorzunehmen. Es hat funktioniert und die Positionsänderung ist für den Benutzer nicht mehr sichtbar. (Es hat auch in viewWillLayoutSubviews funktioniert). Also danke für diesen Tipp!
Friedenstyp

Antworten:

185

Ich aktiviere und deaktiviere NSLayoutConstraintsin viewDidLoadund habe keine Probleme damit. Also funktioniert es. Es muss einen Unterschied im Setup zwischen deiner und meiner App geben :-)

Ich beschreibe nur mein Setup - vielleicht kann es Ihnen einen Hinweis geben:

  1. Ich habe @IBOutletsalle Einschränkungen eingerichtet, die ich zum Aktivieren / Deaktivieren benötige.
  2. In ViewControllerspeichere ich die Einschränkungen in Klasseneigenschaften, die nicht schwach sind. Der Grund dafür ist, dass ich festgestellt habe, dass ich eine Einschränkung nach dem Deaktivieren nicht reaktivieren konnte - sie war gleich Null. Es scheint also gelöscht zu werden, wenn es deaktiviert ist.
  3. Ich benutze nicht NSLayoutConstraint.deactivate/activatewie du, ich benutze constraint.active = YES/ NOstattdessen.
  4. Nachdem ich die Einschränkungen festgelegt habe, rufe ich an view.layoutIfNeeded().
Joachim Bøggild
quelle
131
"Speichern Sie die Einschränkungen in Klasseneigenschaften, die nicht schwach sind" Sie haben mir viel Zeit gespart, danke!
OpenUserX03
10
"Ich speichere die Einschränkungen in Klasseneigenschaften, die nicht schwach sind": Das ersparte mir jede Menge Herzschmerz. Ich wusste nicht, dass ich einen Selektor für ein Null-Objekt aufrufe. Vielen Dank!!
static0886
4
Es ist wichtig zu beachten, dass "inaktive" Einschränkungen vom automatischen Layout nicht ignoriert werden, sondern entfernt werden. Durch Aktivieren / Deaktivieren von Einschränkungen werden diese tatsächlich hinzugefügt und entfernt. Ich habe einige Zeit damit verbracht, ein widersprüchliches automatisches Layout zu debuggen, nachdem ich die zuvor festgelegten Einschränkungen hinzugefügt hatte und .active = falseerwartet hatte, dass sie ignoriert werden, bis ich sie auf aktiv setze.
Lbarbosa
1
Speichern Sie die Einschränkungen in Klasseneigenschaften, die nicht schwach sind. OK, das spart viel Zeit. Ohne diese Ergebnisse habe ich einige gemischte Ergebnisse erzielt. Danke, Mann!
MegaManX
3
In Apples doc heißt es: Durch Aktivieren oder Deaktivieren der Einschränkungsaufrufe addConstraint ( :) und removeConstraint ( :) in der Ansicht, die der nächste gemeinsame Vorfahr der von dieser Einschränkung verwalteten Elemente ist. Verwenden Sie diese Eigenschaft, anstatt addConstraint ( :) oder removeConstraint ( :) direkt aufzurufen . Wenn eine Einschränkung deaktiviert ist, wird sie anscheinend entfernt, und es gibt keine starken Verweise mehr auf die Einschränkung, es sei denn, das IBOutlet ist stark. Daher wird die Einschränkung gelöscht. IMHO ist dies fast ein Fehler oder zumindest ein sehr unerwartetes Verhalten.
Olle Raab
52

Vielleicht könnten Sie Ihre überprüfen @properties, ersetzen weakdurchstrong .

Manchmal ist es so active = NOeingestellt self.yourConstraint = nil, dass man es nicht mehr benutzen kann self.yourConstraint.

Löwe
quelle
5
Wie im Swift Language Guide angegeben , sind die Eigenschaften standardmäßig stark, sodass Sie sie auch einfach entfernen weakkönnen.
Jonathan Cabrera
30
override func viewDidLayoutSubviews() {
// do it here, after constraints have been materialized
}
Tony Swiftguy
quelle
1
Da mein View Controller ein untergeordneter View Controller ist, scheint es der einzige Weg zu sein, dies in "didLayoutSubviews" zu tun !! Zu Ihrer Information.
TalL
Dies ist die einzig gültige Antwort
Yunus Eren Güzel
@TalL Meinten Sie Einschränkungen für den untergeordneten Ansichtscontroller selbst oder seine Unteransichten?
Stefan
Dies ist die beste
ACAkgul
14

Ich glaube, das Problem, auf das Sie stoßen, ist darauf zurückzuführen, dass Einschränkungen erst dann zu ihren Ansichten hinzugefügt werden, wenn AFTER viewDidLoad()aufgerufen wird. Sie haben eine Reihe von Optionen:

A) Sie können Ihre Layouteinschränkungen mit einem IBOutlet verbinden und über diese Referenzen in Ihrem Code darauf zugreifen. Da die Steckdosen vor dem viewDidLoad()Start verbunden sind, sollten die Einschränkungen zugänglich sein und Sie können sie dort weiterhin aktivieren und deaktivieren.

B) Wenn Sie die constraints()Funktion von UIView verwenden möchten, um auf die verschiedenen Einschränkungen zuzugreifen, müssen Sie warten, bis sie gestartet werden viewDidLayoutSubviews(), da dies der erste Punkt nach dem Erstellen eines Ansichtscontrollers aus einer Schreibfeder ist, auf dem alle Einschränkungen installiert sind. Vergessen Sie nicht anzurufen, layoutIfNeeded()wenn Sie fertig sind. Dies hat den Nachteil, dass der Layoutdurchlauf zweimal ausgeführt wird, wenn Änderungen vorgenommen werden müssen, und Sie müssen sicherstellen, dass keine Möglichkeit besteht, dass eine Endlosschleife ausgelöst wird.

Ein kurzes Wort zur Warnung: Deaktivierte Einschränkungen werden von der Methode NICHT zurückgegeben constraints() ! Wenn Sie also eine Einschränkung deaktivieren, um sie später wieder einzuschalten, müssen Sie einen Verweis darauf behalten.

C) Sie können den Storyboard-Ansatz vergessen und Ihre Einschränkungen stattdessen manuell hinzufügen. Da Sie dies in viewDidLoad()tun, gehe ich davon aus, dass die Absicht darin besteht, dies nur einmal für die gesamte Lebensdauer des Objekts zu tun, anstatt das Layout im laufenden Betrieb zu ändern. Daher sollte dies eine akzeptable Methode sein.

Asche
quelle
10

Sie können die priorityEigenschaft auch so einstellen , dass sie aktiviert und deaktiviert wird (750 zum Aktivieren und 250 zum Deaktivieren). Aus irgendeinem Grund hatte das Ändern des activeBOOL keine Auswirkungen auf meine Benutzeroberfläche. Keine Notwendigkeit für layoutIfNeededund kann bei viewDidLoad oder zu einem späteren Zeitpunkt festgelegt und geändert werden.

user2387149
quelle
Ein sehr guter Vorschlag. Das Ändern der Einschränkungspriorität funktioniert in viewWillTransition(to:, with:)oder viewWillLayoutSubviews()und Sie können alle alternativen Einschränkungen als "installiert" in einem Storyboard beibehalten. Die Einschränkungspriorität ändert sich möglicherweise nicht von nicht erforderlich zu erforderlich. Verwenden Sie daher die folgenden Werte 1000. Andererseits funktioniert das Aktivieren (Hinzufügen) und Deaktivieren (Entfernen) von Einschränkungen nur in viewDidLayoutSubviews()und erfordert das Beibehalten von strong @IBOutletVerweisen auf NSLayoutConstraint-s.
Gary
"Aus irgendeinem Grund hatte das Ändern des aktiven BOOL keine Auswirkungen auf meine Benutzeroberfläche." Basierend auf hier . Ich denke, Sie können eine Einschränkung mit einer Priorität von 1000 zur Laufzeit nicht ändern. Wenn Sie es deaktivieren möchten, sollten Sie die anfängliche Priorität auf 999 oder niedriger setzen ....
Honey
Ich bin mit dieser Aussage nicht einverstanden, da sie zu schwer zu debuggenden Problemen führen kann und die Frage nicht beantwortet. Wenn Sie die Priorität auf 250 setzen, wird die Einschränkung nicht "deaktiviert". Sie hat weiterhin Auswirkungen und beeinflusst das Layout. Es scheint, als würde es die Einschränkung in den meisten Fällen "deaktivieren", aber definitiv nicht in allen Fällen. (Insbesondere nicht der Fall, der mich dazu brachte, eine Antwort auf diese Frage zu finden)
Tumata
Dies kann auch zu Abstürzen führen, z. B. "Das Mutieren einer Priorität von erforderlich zu nicht für eine installierte Einschränkung (oder umgekehrt) wird nicht unterstützt. Sie haben die Priorität 250 bestanden und die vorhandene Priorität war 1000."
Karthick Ramesh
8

Der richtige Zeitpunkt, um nicht verwendete Einschränkungen zu deaktivieren:

-(void)viewWillLayoutSubviews{
    [super viewWillLayoutSubviews];

    self.myLittleConstraint.active = NO;
}

Denken Sie daran, dass viewWillLayoutSubviewsdies mehrmals aufgerufen werden kann, also keine schweren Berechnungen hier, okay?

Hinweis: Wenn Sie einige der Einschränkungen später reaktivieren möchten, speichern Sie immer einen strongVerweis darauf.

Laszlo
quelle
2
Für mich ist der einzig zuverlässige Weg, Einschränkungen anzupassen viewDidLayoutSubviews(). Das Anpassen von Einschränkungen in viewWillLayoutSubviews()funktioniert in meinem Fall nicht.
Petrsyn
6

Beim Erstellen einer Ansicht werden die folgenden Lebenszyklusmethoden der Reihe nach aufgerufen:

  1. loadView
  2. viewDidLoad
  3. viewWillAppear
  4. viewWillLayoutSubviews
  5. viewDidLayoutSubviews
  6. viewDidAppear

Nun zu Ihren Fragen.

  1. Warum bekomme ich dieses Verhalten?

Antwort: Wenn Sie versuchen, die Einschränkungen für die Ansichten in viewDidLoadder Ansicht festzulegen, gibt es keine Grenzen. Daher können keine Einschränkungen festgelegt werden. Erst danach sind viewDidLayoutSubviewsdie Grenzen der Ansicht festgelegt.

  1. Ist es möglich, Einschränkungen in viewDidLoad zu aktivieren / deaktivieren?

Antwort: Nein. Grund oben erläutert.

Sumeet
quelle
In Ihrer Beschreibung des viewController-Lebenszyklus haben Sie darüber gesprochen, wie die Ansicht zuerst geladen und dann viewDidLoad aufgerufen wird. Sie sagten jedoch auch, dass die Ansicht nicht zum Zeitpunkt des Aufrufs von viewDidLoad erstellt wird. Dies ist eindeutig ein Widerspruch. Sie können auch selbst testen, ob die Ansicht zum Zeitpunkt des Aufrufs von viewDidLoad erstellt wurde, da Sie der Ansicht Unteransichten hinzufügen können.
ABakerSmith
viewDidLoad sollte in Ordnung sein, da Ansichten erstellt und geladen werden ... In Wirklichkeit hängt die Aktivierung von Einschränkungen hauptsächlich von der Leistung ab. Ich vermute, das ursprüngliche Problem hatte nichts damit zu tun, wo die Einschränkungen aktiviert wurden. stackoverflow.com/questions/19387998/…
Gabe
@ABakerSmith Ich habe meine Antwort bearbeitet, um klarer zu sein.
Sumeet
1

Ich habe festgestellt, solange Sie die Einschränkungen pro Normal in der Überschreibung von - (void)updateConstraints(Ziel c) mit einer strongReferenz für die verwendete Initialität aktive und nicht aktive Einschränkungen eingerichtet haben. Und an anderer Stelle im Ansichtszyklus deaktivieren und / oder aktivieren Sie, was Sie benötigen, und rufen layoutIfNeededSie dann an . Sie sollten keine Probleme haben.

Die Hauptsache ist, die Überschreibung updateConstraintsder Einschränkungen nicht ständig wiederzuverwenden und die Aktivierungen der Einschränkungen zu trennen, solange Sie updateConstraints nach Ihrer ersten Initialisierung und Ihrem ersten Layout aufrufen . Danach scheint es wichtig zu sein, wo im Ansichtszyklus.

Elliotrock
quelle