setNeedsLayout vs setNeedsUpdateConstraints et layoutIfNeeded vs updateConstraintsIfNeeded

227

Je sais que la chaîne de mise en page automatique consiste essentiellement en 3 processus différents.

  1. mise à jour des contraintes
  2. vues de mise en page (c'est ici que nous obtenons le calcul des images)
  3. afficher

Ce qui n'est pas totalement clair pour moi, c'est la différence intérieure entre -setNeedsLayoutet -setNeedsUpdateConstraints. Depuis Apple Docs:

setNeedsLayout

Appelez cette méthode sur le thread principal de votre application lorsque vous souhaitez ajuster la disposition des sous-vues d'une vue. Cette méthode prend note de la demande et revient immédiatement. Étant donné que cette méthode ne force pas une mise à jour immédiate, mais attend le cycle de mise à jour suivant, vous pouvez l'utiliser pour invalider la disposition de plusieurs vues avant la mise à jour de l'une de ces vues. Ce comportement vous permet de consolider toutes vos mises à jour de mise en page en un seul cycle de mise à jour, ce qui est généralement meilleur pour les performances.

setNeedsUpdateConstraints

Lorsqu'une propriété de votre vue personnalisée change d'une manière qui aurait un impact sur les contraintes, vous pouvez appeler cette méthode pour indiquer que les contraintes doivent être mises à jour à un moment donné dans le futur. Le système appellera ensuite updateConstraints dans le cadre de sa passe de mise en page normale. La mise à jour simultanée des contraintes juste avant qu'elles ne soient nécessaires garantit que vous ne recalculez pas inutilement les contraintes lorsque plusieurs modifications sont apportées à votre vue entre les étapes de mise en page.

Lorsque je veux animer une vue après avoir modifié une contrainte et animer les changements que j'appelle habituellement par exemple:

[UIView animateWithDuration:1.0f delay:0.0f usingSpringWithDamping:0.5f initialSpringVelocity:1 options:UIViewAnimationOptionCurveEaseInOut animations:^{
        [self.modifConstrView setNeedsUpdateConstraints];
        [self.modifConstrView layoutIfNeeded];
    } completion:NULL];

J'ai découvert que si j'utilise à la -setNeedsLayoutplace de -setNeedsUpdateConstraintstout fonctionne comme prévu, mais si je change -layoutIfNeededavec -updateConstraintsIfNeeded, l'animation ne se produira pas.
J'ai essayé de tirer ma propre conclusion:

  • -updateConstraintsIfNeeded ne met à jour que les contraintes mais ne force pas la mise en page à entrer dans le processus, donc les cadres d'origine sont toujours préservés
  • -setNeedsLayoutappelle également la -updateContraintsméthode

Alors, quand est-il possible d'utiliser l'un au lieu de l'autre? et sur les méthodes de disposition, dois-je les appeler sur la vue qui a changé dans une contrainte ou sur la vue parent?

Andrea
la source
27
Je ne comprends pas vraiment que les gens votent ... Donc, vous devriez faire quelque chose, comme demander une raison comme obligatoire ou ils sont totalement inutiles
Andrea
7
Peut-être qu'ils ont juste besoin d'obtenir l'insigne de critique (premier vote
négatif
1
Je vous recommande fortement de voir ici . La réponse est davantage une solution à un problème réel. Voir aussi cette vidéo
Honey

Réponses:

258

Vos conclusions sont justes. Le schéma de base est le suivant:

  • setNeedsUpdateConstraintsassure un futur appel aux updateConstraintsIfNeededappels updateConstraints.
  • setNeedsLayoutassure un futur appel aux layoutIfNeededappels layoutSubviews.

Quand layoutSubviewsest appelé, il appelle également updateConstraintsIfNeeded, donc l'appeler manuellement est rarement nécessaire dans mon expérience. En fait, je ne l'ai jamais appelé sauf lors du débogage de mises en page.

La mise à jour des contraintes à l'aide setNeedsUpdateConstraintsest également assez rare, objc.io - une lecture incontournable des mises en page automatiques - dit :

Si quelque chose change par la suite et invalide l'une de vos contraintes, vous devez supprimer la contrainte immédiatement et appeler setNeedsUpdateConstraints. En fait, c'est le seul cas où vous devriez avoir à déclencher une passe de mise à jour de contrainte.

De plus, d'après mon expérience, je n'ai jamais eu à invalider les contraintes et à ne pas définir la setNeedsLayoutdans la ligne suivante du code, car les nouvelles contraintes demandent à peu près une nouvelle mise en page.

Les règles de base sont les suivantes:

  • Si vous avez manipulé directement des contraintes, appelez setNeedsLayout .
  • Si vous avez modifié certaines conditions (comme des compensations ou lissée) qui pourraient changer les contraintes dans votre redéfinie updateConstraintsméthode (une méthode recommandée aux contraintes de changement, BTW), appelsetNeedsUpdateConstraints , et la plupart du temps, setNeedsLayoutaprès cela.
  • Si vous avez besoin d'une des actions ci-dessus pour avoir un effet immédiat, par exemple lorsque vous avez besoin d'apprendre la nouvelle hauteur du cadre après un passage de mise en page, ajoutez-la avec un layoutIfNeeded .

De plus, dans votre code d'animation, je pense que cela setNeedsUpdateConstraintsn'est pas nécessaire, car les contraintes sont mises à jour manuellement avant l'animation, et l'animation ne fait que redisposer la vue en fonction des différences entre les anciennes et les nouvelles.

couverture
la source
@coverback, donc objc.io dit "Si quelque chose change plus tard qui invalide l'une de vos contraintes, vous devez supprimer la contrainte immédiatement et appeler setNeedsUpdateConstraints. En fait, c'est le seul cas où vous devriez avoir à déclencher une passe de mise à jour de contrainte." Et puis dans le bloc Animation, il est dit que lorsque je supprime, ajoute ou modifie constraint.contant, je dois appeler setNeedsLayout. Quelle est la différence? Je me sens vraiment stupide :(
pash3r
3
@ pash3r La différence de mise à jour de la constante n'est pas considérée comme une "invalidation". L'invalidation est lorsqu'elle n'est plus pertinente du tout, comme doit être attachée à une autre vue ou supprimée complètement. Constant ne ferait que placer une vue plus près ou plus loin, ou changerait sa taille, d'où la nécessité setNeedsLayout.
coverback
@coverback setNeedsLayouts'assure que layoutSubviewssera appelé dans le prochain cycle de mise à jour, mais cela n'a peut-être rien à voir avec cela layoutIfNeeded?
fujianjin6471
2
@coverback Si vous manipulez directement des contraintes, layoutSubviewssera appelé automatiquement, pas besoin d'appelersetNeedsLayout
fujianjin6471
Oui, la manipulation directe des propriétés d'une contrainte se déclenchera layoutSubviews, donc pas besoin de le faire manuellement. Vous devez cependant appeler layoutIfNeededsi vous avez besoin que les modifications prennent effet immédiatement au lieu du prochain cycle de mise en page
Charlie Martin
89

La réponse par coverback est assez correcte. Cependant, je voudrais ajouter quelques détails supplémentaires.

Voici le schéma d'un cycle UIView typique qui explique d'autres comportements:

Cycle de vie d'UIView

  1. J'ai découvert que si j'utilise à la -setNeedsLayoutplace de -setNeedsUpdateConstraintstout fonctionne comme prévu, mais si je change -layoutIfNeededavec -updateConstraintsIfNeeded, l'animation ne se produira pas.

updateConstraintsne fait généralement rien. Il résout simplement les contraintes, il ne les applique pas jusqu'à ce qu'il layoutSubviewssoit appelé. L'animation nécessite donc un appel à layoutSubviews.

  1. setNeedsLayout appelle également la méthode -updateContraints

Non, ce n'est pas nécessaire. Si vos contraintes n'ont pas été modifiées, UIView ignorera l'appel à updateConstraints. Vous devez appeler explicitementsetNeedsUpdateConstraint pour modifier les contraintes dans le processus.

Pour appeler, updateConstraintsvous devez procéder comme suit:

[view setNeedsUpdateConstraints];
[view setNeedsLayout]; 
[view layoutIfNeeded];
Kunal Balani
la source
Merci, cela a résolu mon problème. J'avais une UIWindow sans UIView parent qui avait des contraintes temporaires ajoutées lors de l'appel de LayoutIfNeeded () avant une animation. L'ajout d'un wrapper de sous-vue à l'UIWindow et l'appel de ces trois méthodes ont résolu mon problème.
masterwok
Je ne pense pas que l'appel de layoutIfNeeded juste après setNeedsLayout soit correct. Parce que les méthodes font de même malgré le fait que l'une provoque la redessine de la mise en page immédiatement et la seconde dans un prochain cycle de mise à jour.
fillky