[EDIT : Attention : Toute la discussion qui suit sera peut-être dépassée ou du moins fortement atténuée par iOS 8, qui ne fera peut-être plus l'erreur de déclencher la mise en page au moment où une transformation de vue est appliquée].
Autolayout et transformations de vues
Autolayout ne joue pas du tout bien avec les transformations de vues. La raison, pour autant que je puisse la discerner, est que vous n'êtes pas censé jouer avec le cadre d'une vue qui a une transformation (autre que la transformation d'identité par défaut) - mais c'est exactement ce que fait autolayout. La façon dont autolayout fonctionne est la suivante : dans la fenêtre layoutSubviews
le runtime passe en revue toutes les contraintes et définit les cadres de toutes les vues en conséquence.
En d'autres termes, les contraintes ne sont pas magiques ; elles ne sont qu'une liste de choses à faire. layoutSubviews
est l'endroit où la liste des choses à faire est faite. Et il le fait en fixant des cadres.
Je ne peux pas aider à considérer ceci comme un bug. Si j'applique cette transformation à une vue :
v.transform = CGAffineTransformMakeScale(0.5,0.5);
Je m'attends à ce que la vue apparaisse avec son centre au même endroit qu'avant et à la moitié de sa taille. Mais en fonction de ses contraintes, ce n'est peut-être pas ce que je vois du tout.
[En fait, il y a une deuxième surprise ici : l'application d'une transformation à une vue déclenche immédiatement la mise en page. Cela me semble être un autre bug. Ou peut-être est-ce le cœur du premier bogue. Je m'attendrais à pouvoir m'en sortir avec une transformation au moins jusqu'au moment de la mise en page, par exemple si le dispositif est tourné - tout comme je peux m'en sortir avec une animation d'image jusqu'au moment de la mise en page. Mais en fait, le moment de la mise en page est immédiat, ce qui ne semble pas correct].
Solution 1 : aucune contrainte
Une solution actuelle consiste, si je dois appliquer une transformation semi-permanente à une vue (et non pas simplement l'agiter temporairement), à supprimer toutes les contraintes qui l'affectent. Malheureusement, cela entraîne généralement la disparition de la vue de l'écran, puisque l'autolayout a toujours lieu et qu'il n'y a plus de contraintes pour nous dire où placer la vue. Donc, en plus de supprimer les contraintes, je règle le paramètre translatesAutoresizingMaskIntoConstraints
à OUI. La vue fonctionne maintenant de l'ancienne manière, sans être affectée par autolayout. (Il es affectée par l'autolayout, évidemment, mais les contraintes implicites du masque d'autoréalisation font que son comportement est identique à ce qu'il était avant l'autolayout).
Solution 2 : N'utilisez que des contraintes appropriées
Si cela semble un peu radical, une autre solution consiste à définir les contraintes pour qu'elles fonctionnent correctement avec une transformation prévue. Si une vue est dimensionnée uniquement par sa largeur et sa hauteur internes fixes, et positionnée uniquement par son centre, par exemple, ma transformation d'échelle fonctionnera comme prévu. Dans ce code, je supprime les contraintes existantes sur une sous-vue ( otherView
) et les remplacer par ces quatre contraintes, en lui donnant une largeur et une hauteur fixes et en l'épinglant uniquement par son centre. Après cela, ma transformation d'échelle fonctionne :
NSMutableArray* cons = [NSMutableArray array];
for (NSLayoutConstraint* con in self.view.constraints)
if (con.firstItem == self.otherView || con.secondItem == self.otherView)
[cons addObject:con];
[self.view removeConstraints:cons];
[self.otherView removeConstraints:self.otherView.constraints];
[self.view addConstraint:
[NSLayoutConstraint constraintWithItem:self.otherView attribute:NSLayoutAttributeCenterX relatedBy:0 toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1 constant:self.otherView.center.x]];
[self.view addConstraint:
[NSLayoutConstraint constraintWithItem:self.otherView attribute:NSLayoutAttributeCenterY relatedBy:0 toItem:self.view attribute:NSLayoutAttributeTop multiplier:1 constant:self.otherView.center.y]];
[self.otherView addConstraint:
[NSLayoutConstraint constraintWithItem:self.otherView attribute:NSLayoutAttributeWidth relatedBy:0 toItem:nil attribute:0 multiplier:1 constant:self.otherView.bounds.size.width]];
[self.otherView addConstraint:
[NSLayoutConstraint constraintWithItem:self.otherView attribute:NSLayoutAttributeHeight relatedBy:0 toItem:nil attribute:0 multiplier:1 constant:self.otherView.bounds.size.height]];
Le résultat est que si vous n'avez pas de contraintes qui affectent le cadre d'une vue, la mise en page automatique ne touchera pas le cadre de la vue, ce qui est exactement ce que vous recherchez lorsqu'une transformation est impliquée.
Solution 3 : utiliser une vue secondaire
Le problème avec les deux solutions ci-dessus est que nous perdons les avantages des contraintes pour positionner notre vue. Voici donc une solution qui résout ce problème. Commencez par une vue invisible dont le rôle est uniquement d'agir en tant qu'hôte, et utilisez les contraintes pour la positionner. À l'intérieur de cette vue, placez la vue réelle en tant que sous-vue. Utilisez des contraintes pour positionner la vue secondaire dans la vue hôte, mais limitez ces contraintes à celles qui ne se défendront pas lorsque nous appliquerons une transformation.
En voici une illustration :
La vue blanche est la vue hôte ; vous êtes censé prétendre qu'elle est transparente et donc invisible. La vue rouge est sa sous-vue, positionnée en épinglant son centre à celui de la vue hôte. Nous pouvons maintenant mettre à l'échelle et faire pivoter la vue rouge autour de son centre sans aucun problème, et l'illustration montre d'ailleurs que nous l'avons fait :
self.otherView.transform = CGAffineTransformScale(self.otherView.transform, 0.5, 0.5);
self.otherView.transform = CGAffineTransformRotate(self.otherView.transform, M_PI/8.0);
Et pendant ce temps, les contraintes sur la vue hôte la maintiennent au bon endroit lorsque nous faisons pivoter le dispositif.
Solution 4 : utilisez plutôt les transformations de calque
Au lieu des transformations de vue, utilisez les transformations de couche, qui ne déclenchent pas de mise en page et n'entraînent donc pas de conflit immédiat avec les contraintes.
Par exemple, cette simple animation de vue "throb" pourrait bien se briser sous autolayout :
[UIView animateWithDuration:0.3 delay:0
options:UIViewAnimationOptionAutoreverse
animations:^{
v.transform = CGAffineTransformMakeScale(1.1, 1.1);
} completion:^(BOOL finished) {
v.transform = CGAffineTransformIdentity;
}];
Même si, en fin de compte, la taille de la vue n'a pas été modifiée, le simple fait de définir son transform
provoque la mise en page, et les contraintes peuvent faire sauter la vue. (Est-ce que cela ressemble à un bug ou quoi ?) Mais si nous faisons la même chose avec Core Animation (en utilisant une CABasicAnimation et en appliquant l'animation au calque de la vue), le layout ne se produit pas, et cela fonctionne bien :
CABasicAnimation* ba = [CABasicAnimation animationWithKeyPath:@"transform"];
ba.autoreverses = YES;
ba.duration = 0.3;
ba.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.1, 1.1, 1)];
[v.layer addAnimation:ba forKey:nil];
0 votes
Compte tenu de ce que vous avez ici, il semble que vous allez vous retrouver avec des mises en page ambiguës de toute façon, même si vous avez réussi à faire fonctionner le code ci-dessus. Comment
layerView
connaître sa largeur ? Est-ce que son côté droit est collé à quelque chose d'autre ?0 votes
C'est couvert dans le
//Size-related constraints that work fine
- la largeur et la hauteur de la vue en couches sont dérivées de celles de la vue principale.