[EDITAR: Advertencia: iOS 8 posiblemente desactualizará o al menos mitigará toda la discusión subsiguiente, lo que ya no puede cometer el error de activar el diseño en el momento en que se aplica una transformación de vista.]
Autolayout vs. Ver transformaciones
La reproducción automática no se reproduce bien con las transformaciones de vista. La razón, por lo que puedo discernir, es que se supone que no debes meterte con el marco de una vista que tiene una transformación (que no sea la transformación de identidad predeterminada), pero eso es exactamente lo que hace la distribución automática. La forma en que funciona el autolayout es que en layoutSubviews
el tiempo de ejecución viene corriendo a través de todas las restricciones y configurando los marcos de todas las vistas en consecuencia.
En otras palabras, las restricciones no son mágicas; son solo una lista de tareas pendientes. layoutSubviews
es donde se hace la lista de tareas pendientes. Y lo hace estableciendo marcos.
No puedo evitar considerar esto como un error. Si aplico esta transformación a una vista:
v.transform = CGAffineTransformMakeScale(0.5,0.5);
Espero ver la vista aparecer con su centro en el mismo lugar que antes y a la mitad del tamaño. Pero dependiendo de sus limitaciones, eso puede no ser lo que veo en absoluto.
[En realidad, hay una segunda sorpresa aquí: aplicar una transformación a una vista activa el diseño de inmediato. Esto me parece ser otro error. O tal vez es el corazón del primer error. Lo que esperaría es poder escapar con una transformación al menos hasta el tiempo de diseño, por ejemplo, el dispositivo gira, al igual que puedo salir con una animación de cuadro hasta el tiempo de diseño. Pero, de hecho, el tiempo de diseño es inmediato, lo que parece incorrecto.]
Solución 1: sin restricciones
Una solución actual es, si voy a aplicar una transformación semipermanente a una vista (y no simplemente moverla temporalmente de alguna manera), para eliminar todas las restricciones que la afectan. Desafortunadamente, esto generalmente hace que la vista desaparezca de la pantalla, ya que la distribución automática todavía tiene lugar, y ahora no hay restricciones para decirnos dónde colocar la vista. Entonces, además de eliminar las restricciones, configuro las vistas translatesAutoresizingMaskIntoConstraints
en SÍ. La vista ahora funciona a la antigua usanza, efectivamente no se ve afectada por la distribución automática. ( Obviamente, se ve afectado por el autolayout, pero las restricciones implícitas de la máscara de autoresizing hacen que su comportamiento sea igual que antes del autolayout).
Solución 2: use solo restricciones apropiadas
Si eso parece un poco drástico, otra solución es establecer las restricciones para que funcionen correctamente con una transformación prevista. Si una vista se dimensiona únicamente por su ancho y altura fijos internos, y se coloca únicamente por su centro, por ejemplo, mi transformación de escala funcionará como esperaba. En este código, elimino las restricciones existentes en una subvista ( otherView
) y las reemplazo con esas cuatro restricciones, dándole un ancho y una altura fijos y fijándolos únicamente por su centro. Después de eso, mi transformación de escala funciona:
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]];
El resultado es que si no tiene restricciones que afecten el marco de una vista, el autolayout no tocará el marco de la vista, que es justo lo que busca cuando se trata de una transformación.
Solución 3: usar una subvista
El problema con ambas soluciones anteriores es que perdemos los beneficios de las restricciones para posicionar nuestra opinión. Así que aquí hay una solución que resuelve eso. Comience con una vista invisible cuyo trabajo sea únicamente actuar como host, y use restricciones para posicionarlo. Dentro de eso, coloque la vista real como una subvista. Use restricciones para colocar la subvista dentro de la vista de host, pero limite esas restricciones a restricciones que no se defiendan cuando apliquemos una transformación.
Aquí hay una ilustración:
La vista en blanco es vista de host; se supone que debes fingir que es transparente y, por lo tanto, invisible. La vista roja es su subvista, posicionada fijando su centro al centro de la vista del host. Ahora podemos escalar y rotar la vista roja alrededor de su centro sin ningún problema, y de hecho la ilustración muestra que lo hemos hecho:
self.otherView.transform = CGAffineTransformScale(self.otherView.transform, 0.5, 0.5);
self.otherView.transform = CGAffineTransformRotate(self.otherView.transform, M_PI/8.0);
Y mientras tanto, las restricciones en la vista del host lo mantienen en el lugar correcto mientras giramos el dispositivo.
Solución 4: utilice transformaciones de capa en su lugar
En lugar de transformaciones de vista, use transformaciones de capa, que no activan el diseño y, por lo tanto, no causan un conflicto inmediato con las restricciones.
Por ejemplo, esta simple animación de vista de "latido" bien puede romperse en autolayout:
[UIView animateWithDuration:0.3 delay:0
options:UIViewAnimationOptionAutoreverse
animations:^{
v.transform = CGAffineTransformMakeScale(1.1, 1.1);
} completion:^(BOOL finished) {
v.transform = CGAffineTransformIdentity;
}];
Aunque al final no hubo cambios en el tamaño de la vista, simplemente configurando el transform
diseño de sus causas, y las restricciones pueden hacer que la vista salte. (¿Se siente como un error o qué?) Pero si hacemos lo mismo con Core Animation (usando una animación CABasic y aplicando la animación a la capa de la vista), el diseño no sucede y funciona 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
sabe su ancho? ¿Está pegando su lado derecho a otra cosa?