Quand puis-je activer / désactiver les contraintes de mise en page?

104

J'ai mis en place plusieurs ensembles de contraintes dans IB et j'aimerais basculer entre eux par programmation en fonction d'un état. Il y a une constraintsAcollection de points de vente qui sont tous marqués comme installés à partir d'IB et une constraintsBcollection de points de vente qui sont tous désinstallés dans IB.

Je peux basculer par programmation entre les deux ensembles comme ceci:

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

Mais ... je ne sais pas quand faire ça. Il semble que je devrais pouvoir le faire une fois viewDidLoad, mais je ne peux pas le faire fonctionner. J'ai essayé d'appeler view.updateConstraints()et view.layoutSubviews()après avoir défini les contraintes, mais en vain.

J'ai trouvé que si je définissais les contraintes dans viewDidLayoutSubviewstout fonctionne comme prévu. Je suppose que j'aimerais savoir deux choses ...

  1. Pourquoi ai-je ce comportement?
  2. Est-il possible d'activer / désactiver les contraintes de viewDidLoad?
tybro0103
la source
2
Voulez-vous dire que les paramètres deactivateConstraints et activateConstraints ont fonctionné dans viewWillLayoutSubviews? J'ai essayé cela, et cela n'a pas fonctionné là-bas ou dans viewDidLoad. Cela fonctionnait en quelque sorte dans viewDidAppear; la vue est apparue là où les nouvelles contraintes devraient la placer, mais si je pivote en paysage, la vue est revenue à la position déterminée par les contraintes définies dans IB (et y est restée lorsque je suis retournée en portrait). En enregistrant les contraintes, montrez les bonnes (celles nouvellement activées). Cela me semble être un bug.
rdelmar
1
Oui, ils étaient valides (ils fonctionnaient dans viewDidAppear), et il n'est pas nécessaire d'appeler super car il n'y a pas d'implémentation par défaut de viewWillLayoutSubviews (je l'ai essayé en appelant super de toute façon, mais cela n'a fait aucune différence).
rdelmar
1
@rdelmar J'ai juste eu la chance d'aller tester plus ... Je peux vérifier que j'ai en fait le même comportement que vous avez décrit ... fonctionne d'abord dans viewDidAppear, mais revient ensuite lors de la rotation.
tybro0103
3
Apparemment, vous ne pouvez pas marquer les contraintes comme non installées dans IB à cet effet. J'ai trouvé ces informations ici: stackoverflow.com/questions/27663249/… et cela a résolu le problème pour moi.
Stefan
1
J'ai fait implémenter mes contraintes de la même manière que celle décrite dans la question, sauf que j'ai activé / désactivé certaines d'entre elles dans viewDidAppear. Cela a fonctionné, mais vous pouviez voir les éléments changer rapidement de position (un problème mineur mais indésirable). La modification de viewWillAppear ou viewDidLoad n'a pas fonctionné. Mais après avoir lu cette question, j'ai essayé de faire ce changement dans viewDidLayoutSubviews. Cela a fonctionné et le changement de position n'est plus visible pour l'utilisateur. (Cela a également fonctionné dans viewWillLayoutSubviews). Alors merci pour cette astuce!
peacetype

Réponses:

186

J'active et je désactive NSLayoutConstraints dans viewDidLoad, et je n'ai aucun problème avec cela. Donc ça marche. Il doit y avoir une différence de configuration entre votre application et la mienne :-)

Je vais juste décrire ma configuration - peut-être que cela peut vous donner une piste:

  1. Je mets en place @IBOutletstoutes les contraintes dont j'ai besoin pour activer / désactiver.
  2. Dans le ViewController, j'enregistre les contraintes dans des propriétés de classe qui ne sont pas faibles. La raison en est que j'ai constaté qu'après avoir désactivé une contrainte, je ne pouvais pas la réactiver - c'était nul. Ainsi, il semble être supprimé lorsqu'il est désactivé.
  3. Je n'utilise pas NSLayoutConstraint.deactivate/activatecomme vous, j'utilise constraint.active = YES/ à la NOplace.
  4. Après avoir défini les contraintes, j'appelle view.layoutIfNeeded().
Joachim Bøggild
la source
132
"enregistrer les contraintes dans des propriétés de classe qui ne sont pas faibles" Vous m'avez fait gagner beaucoup de temps, merci!
OpenUserX03
10
"J'enregistre les contraintes dans des propriétés de classe qui ne sont pas faibles": Cela m'a sauvé des tonnes de chagrin. Je ne savais pas que j'appelais un sélecteur sur un objet nul. Merci!!
static0886
4
Il est important de noter que les contraintes «inactives» ne sont pas ignorées par la mise en page automatique, elles sont supprimées. Activer / désactiver les contraintes les ajoute et les supprime. J'ai passé du temps à déboguer une mise en page automatique en conflit après avoir ajouté les contraintes que j'avais précédemment définies en .active = falsem'attendant à ce qu'elles soient ignorées jusqu'à ce que je les définisse sur actives.
lbarbosa
1
enregistrez les contraintes dans des propriétés de classe qui ne sont pas faibles, ok cela fait gagner beaucoup de temps, j'obtenais des résultats mitigés sans cela. Merci mec!
MegaManX
3
Apples doc dit: L'activation ou la désactivation de la contrainte appelle addConstraint ( :) et removeConstraint ( :) sur la vue qui est l'ancêtre commun le plus proche des éléments gérés par cette contrainte. Utilisez cette propriété au lieu d'appeler directement addConstraint ( :) ou removeConstraint ( :). Ainsi, il semble que lorsqu'une contrainte est désactivée, elle est supprimée et qu'il n'y a plus de références fortes à la contrainte à gauche, sauf si l'IBOutlet est forte. Par conséquent, la contrainte est supprimée. IMHO c'est presque un bug ou du moins un comportement très inattendu.
Olle Raab
52

Peut-être que vous pourriez vérifier votre @properties, remplacer weakparstrong .

Parfois, c'est parce que active = NOdéfini self.yourConstraint = nil, de sorte que vous ne pouvez plus utiliser self.yourConstraint.

Leo
la source
5
Comme indiqué dans le Guide du langage Swift , les propriétés sont fortes par défaut, vous pouvez donc simplement les supprimer weaket cela le fera.
Jonathan Cabrera
30
override func viewDidLayoutSubviews() {
// do it here, after constraints have been materialized
}
Tony Swiftguy
la source
1
Parce que mon contrôleur de vue est un contrôleur de vue enfant - le faire dans "didLayoutSubviews" semble être la seule façon de fonctionner !! FYI.
TalL
C'est la seule réponse valable
Yunus Eren Güzel
@TalL Voulez-vous dire des contraintes sur le contrôleur de vue enfant lui-même, ou sur ses sous-vues?
Stefan
c'est le meilleur
ACAkgul
14

Je pense que le problème que vous rencontrez est dû au fait que des contraintes ne sont ajoutées à leurs opinions qu'APRÈS viewDidLoad() appel d’ . Vous avez plusieurs options:

UNE) Vous pouvez connecter vos contraintes de mise en page à un IBOutlet et y accéder dans votre code par ces références. Les prises étant connectées avant le viewDidLoad()coup d'envoi, les contraintes doivent être accessibles et vous pouvez continuer à les activer et à les désactiver à cet endroit.

B) Si vous souhaitez utiliser la constraints()fonction d' UIView pour accéder aux différentes contraintes, vous devez attendre pour démarrer viewDidLayoutSubviews()et le faire là-bas, car c'est le premier point après la création d'un contrôleur de vue à partir d'une pointe où il aura toutes les contraintes installées. N'oubliez pas d'appeler layoutIfNeeded()lorsque vous avez terminé. Cela présente l'inconvénient que la passe de mise en page sera effectuée deux fois s'il y a des changements à appliquer et vous devez vous assurer qu'il n'y a aucune possibilité qu'une boucle infinie soit déclenchée.

Un petit mot d'avertissement: les contraintes désactivées ne sont PAS renvoyées par la constraints() méthode! Cela signifie que si vous désactivez une contrainte avec l'intention de la réactiver plus tard, vous devrez en garder une référence.

C) Vous pouvez oublier l'approche du storyboard et ajouter vos contraintes manuellement à la place. Puisque vous faites cela dans, viewDidLoad()je suppose que l'intention est de ne le faire qu'une seule fois pendant toute la durée de vie de l'objet plutôt que de changer la mise en page à la volée, donc cela devrait être une méthode acceptable.

Cendre
la source
10

Vous pouvez également ajuster la prioritypropriété pour les «activer» et les «désactiver» (valeur 750 pour activer et 250 pour désactiver par exemple). Pour une raison quelconque, la modification de activeBOOL n'a eu aucun effet sur mon interface utilisateur. Pas besoin layoutIfNeededet peut être défini et modifié à viewDidLoad ou à tout moment par la suite.

user2387149
la source
Une très bonne suggestion. La modification de la priorité des contraintes fonctionne dans viewWillTransition(to:, with:)ou viewWillLayoutSubviews()et vous pouvez conserver toutes vos contraintes alternatives comme «installées» dans un storyboard. La priorité de contrainte ne peut pas changer de non obligatoire à obligatoire, utilisez donc les valeurs ci-dessous 1000. D'autre part, l'activation (l'ajout) et la désactivation (la suppression) des contraintes ne fonctionnent que dans viewDidLayoutSubviews()et nécessitent de conserver les strong @IBOutletréférences à NSLayoutConstraint-s.
Gary
"Pour une raison quelconque, changer le BOOL actif n'a eu aucun effet sur mon interface utilisateur". Basé sur ici . Je pense que vous ne pouvez pas changer une contrainte avec une priorité de 1000 pendant l'exécution. Si vous souhaitez le désactiver, vous devez définir la priorité initiale sur 999 ou moins ....
Honey
Je ne suis pas d'accord avec cette déclaration car elle peut entraîner des problèmes de débogage difficiles et ne répond pas à la question. La définition de la priorité à 250 ne "désactivera" pas la contrainte, elle aura toujours effet et influera sur la mise en page. Il peut sembler que cela "désactive" la contrainte dans la plupart des cas, mais certainement pas dans tous les cas. (en particulier, pas le cas qui m'a amené à trouver la réponse à cette question)
Tumata
Cela peut également entraîner des plantages tels que "La mutation d'une priorité de obligatoire à non sur une contrainte installée (ou vice-versa) n'est pas prise en charge. Vous avez passé la priorité 250 et la priorité existante était 1 000."
Karthick Ramesh
8

Le bon moment pour désactiver les contraintes inutilisées:

-(void)viewWillLayoutSubviews{
    [super viewWillLayoutSubviews];

    self.myLittleConstraint.active = NO;
}

Gardez à l'esprit que cela viewWillLayoutSubviewspourrait être appelé plusieurs fois, donc pas de calculs lourds ici, d'accord?

Remarque: si vous souhaitez réactiver certaines des contraintes ultérieurement, enregistrez toujours la strongréférence à celles-ci.

Laszlo
la source
2
Pour moi, le seul moyen fiable est d'ajuster les contraintes viewDidLayoutSubviews(). L'ajustement des contraintes dans viewWillLayoutSubviews()ne fonctionne pas dans mon cas.
petrsyn
6

Lorsqu'une vue est créée, les méthodes de cycle de vie suivantes sont appelées dans l'ordre:

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

Passons maintenant à vos questions.

  1. Pourquoi ai-je ce comportement?

Réponse: Parce que lorsque vous essayez de définir les contraintes sur les vues dans viewDidLoadla vue n'a pas ses limites, par conséquent les contraintes ne peuvent pas être définies. Ce n'est qu'après viewDidLayoutSubviewsque les limites de la vue sont finalisées.

  1. Est-il possible d'activer / désactiver les contraintes de viewDidLoad?

Réponse: Non. Raison expliquée ci-dessus.

Sumeet
la source
Dans votre description du cycle de vie de viewController, vous avez expliqué comment la vue est d'abord chargée, puis viewDidLoad est appelée. Pourtant, vous avez également dit que la vue n'est pas créée au moment où viewDidLoad est appelé, c'est clairement une contradiction. En outre, vous pouvez tester par vous-même et voir que la vue a été créée au moment où viewDidLoad est appelé, car vous pouvez ajouter des sous-vues à la vue.
ABakerSmith
viewDidLoad devrait convenir puisque les vues sont créées et chargées ... En réalité, là où vous activez des contraintes se résume principalement à la performance. Je suppose que le problème d'origine n'était pas lié à l'endroit où les contraintes étaient activées. stackoverflow.com/questions/19387998/…
Gabe
@ABakerSmith j'ai édité ma réponse pour être plus claire.
Sumeet
1

J'ai trouvé tant que vous avez mis en place les contraintes par normale dans le remplacement de - (void)updateConstraints(objectif c), avec une strongréférence pour l'initialité utilisée des contraintes actives et inactives. Et ailleurs dans le cycle de visualisation, désactivez et / ou activez ce dont vous avez besoin, puis appelez layoutIfNeeded, vous ne devriez avoir aucun problème.

L'essentiel est de ne pas réutiliser constamment le remplacement updateConstraintset de séparer les activations des contraintes, tant que vous appelez updateConstraints après votre première initialisation et mise en page. Il semble important après cela, où dans le cycle de visualisation.

Elliotrock
la source