[EDIT: Attention: toute la discussion qui s'ensuivra 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.]
Mise en page automatique et voir les transformations
La mise en page automatique ne fonctionne pas du tout bien avec les transformations de vue. La raison, pour autant que je sache, 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 la mise en page automatique. La façon dont la mise en page automatique fonctionne est que dans layoutSubviews
le runtime vient se précipiter à travers toutes les contraintes et définir les cadres de toutes les vues en conséquence.
En d'autres termes, les contraintes ne sont pas magiques; ils ne sont qu'une liste de choses à faire. layoutSubviews
est l'endroit où la liste de tâches se fait. Et il le fait en définissant des cadres.
Je ne peux pas m'empêcher de considérer cela comme un bug. Si j'applique cette transformation à une vue:
v.transform = CGAffineTransformMakeScale(0.5,0.5);
Je m'attends à voir la vue apparaître avec son centre au même endroit qu'avant et à la moitié de sa taille. Mais selon ses contraintes, ce n'est peut-être pas du tout ce que je vois.
[En fait, il y a une deuxième surprise ici: appliquer une transformation à une vue déclenche immédiatement la mise en page. Cela me semble être un autre bug. Ou peut-être que c'est le cœur du premier bug. Ce à quoi je m'attendrais, c'est de pouvoir m'en tirer avec une transformation au moins jusqu'au moment de la mise en page, par exemple, l'appareil est tourné - tout comme je peux m'en tirer avec une animation d'image jusqu'au moment de la mise en page. Mais en fait, le temps de mise en page est immédiat, ce qui semble tout simplement faux.]
Solution 1: aucune contrainte
Une solution actuelle est, si je vais appliquer une transformation semi-permanente à une vue (et pas simplement la remuer temporairement d'une manière ou d'une autre), pour supprimer toutes les contraintes qui l'affectent. Malheureusement, cela provoque généralement la disparition de la vue de l'écran, car la mise en page automatique a toujours lieu, et il n'y a maintenant aucune contrainte pour nous dire où placer la vue. Donc, en plus de supprimer les contraintes, j'ai défini la vue translatesAutoresizingMaskIntoConstraints
sur OUI. La vue fonctionne désormais à l'ancienne, sans être affectée par la mise en page automatique. (Il est évidemment affecté par la mise en page automatique, mais les contraintes implicites de masque de redimensionnement automatique font que son comportement est exactement comme il l'était avant la mise en page automatique.)
Solution 2: utilisez uniquement les contraintes appropriées
Si cela semble un peu drastique, 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 remplace 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 aucune contrainte qui affecte 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 sous-vue
Le problème avec les deux solutions ci-dessus est que nous perdons les avantages des contraintes pour positionner notre point de vue. Voici donc une solution qui résout ce problème. Commencez par une vue invisible dont le travail consiste uniquement à agir en tant qu'hôte et utilisez des contraintes pour la positionner. À l'intérieur, mettez la vue réelle sous forme de sous-vue. Utilisez des contraintes pour positionner la sous-vue dans la vue hôte, mais limitez ces contraintes aux contraintes qui ne riposteront pas lorsque nous appliquerons une transformation.
Voici une illustration:
La vue blanche est la vue de l'hôte; vous êtes censé prétendre qu'il est transparent et donc invisible. La vue rouge est sa sous-vue, positionnée en épinglant son centre au centre de la vue hôte. Maintenant, nous pouvons mettre à l'échelle et faire pivoter la vue rouge autour de son centre sans aucun problème, et en effet l'illustration montre 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 de l'hôte le maintiennent au bon endroit lorsque nous faisons pivoter l'appareil.
Solution 4: utilisez plutôt les transformations de couche
Au lieu des transformations de vue, utilisez des transformations de couche, qui ne déclenchent pas la mise en page et ne provoquent donc pas de conflit immédiat avec les contraintes.
Par exemple, cette simple animation de vue "throb" peut bien être interrompue lors de la mise en page automatique:
[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 à la fin il n'y a pas eu de changement dans la taille de la vue, le simple fait de définir sa transform
mise en page 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 un CABasicAnimation et en appliquant l'animation au calque de la vue), la mise en page 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];
layerView
connaît sa largeur? S'en tient-il à autre chose?