[EDIT: Aviso: toda a discussão que se segue será possivelmente obsoleta ou pelo menos fortemente mitigada pelo iOS 8, que pode não mais cometer o erro de acionar o layout no momento em que uma transformação de exibição é aplicada.]
Autolayout vs. Exibir transformações
O autolayout não funciona bem com as transformações de exibição. A razão, pelo que pude discernir, é que você não deve mexer com o quadro de uma exibição que possui uma transformação (que não seja a transformação de identidade padrão) - mas é exatamente isso que a execução automática faz. A maneira como o autolayout funciona é que, no layoutSubviews
tempo de execução, percorre todas as restrições e define os quadros de todas as visualizações de acordo.
Em outras palavras, as restrições não são mágicas; eles são apenas uma lista de tarefas. layoutSubviews
é onde a lista de tarefas é concluída. E faz isso definindo quadros.
Não posso deixar de considerar isso como um bug. Se eu aplicar essa transformação a uma exibição:
v.transform = CGAffineTransformMakeScale(0.5,0.5);
Espero ver a vista aparecer com o centro no mesmo lugar que antes e com a metade do tamanho. Mas, dependendo de suas restrições, pode não ser o que eu vejo.
[Na verdade, há uma segunda surpresa aqui: aplicar uma transformação a uma exibição aciona o layout imediatamente. Isso me parece outro bug. Ou talvez seja o coração do primeiro bug. O que eu esperaria é ser capaz de realizar uma transformação pelo menos até o tempo de layout, por exemplo, o dispositivo é girado - assim como eu posso executar uma animação de quadro até o tempo de layout. Mas, na verdade, o tempo de layout é imediato, o que parece errado.]
Solução 1: sem restrições
Uma solução atual é, se vou aplicar uma transformação semipermanente a uma visualização (e não apenas mexer temporariamente de alguma forma), para remover todas as restrições que a afetam. Infelizmente, isso geralmente faz com que a exibição desapareça da tela, pois o autolayout ainda ocorre e agora não há restrições para nos dizer onde colocar a exibição. Portanto, além de remover as restrições, defino as visualizações translatesAutoresizingMaskIntoConstraints
como YES. Agora, a exibição funciona da maneira antiga, efetivamente não afetada pela execução automática. (Ela é afetada por autolayout, obviamente, mas as restrições máscara redimensionamento automático implícita causar o seu comportamento ser como era antes autolayout.)
Solução 2: use apenas restrições apropriadas
Se isso parecer um pouco drástico, outra solução é definir as restrições para funcionarem corretamente com a transformação pretendida. Se uma vista é dimensionada exclusivamente por sua largura e altura fixas internas e posicionada apenas por seu centro, por exemplo, minha transformação de escala funcionará conforme o esperado. Neste código, removo as restrições existentes em uma subview ( otherView
) e as substituo por essas quatro restrições, fornecendo largura e altura fixas e fixando-as apenas pelo centro. Depois disso, minha transformação 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]];
O resultado é que, se você não tiver restrições que afetem o quadro de uma vista, o autolayout não tocará no quadro da vista - que é exatamente o que você procura quando uma transformação está envolvida.
Solução 3: Use uma subvisão
O problema com ambas as soluções acima é que perdemos os benefícios de restrições para posicionar nossa visão. Então, aqui está uma solução que resolve isso. Comece com uma visão invisível, cuja função é apenas atuar como host, e use restrições para posicioná-la. Dentro disso, coloque a visão real como uma subvisão. Use restrições para posicionar a subvisão na visualização do host, mas limite essas restrições a restrições que não serão revertidas quando aplicarmos uma transformação.
Aqui está uma ilustração:
A exibição em branco é a exibição do host; você deve fingir que é transparente e, portanto, invisível. A visualização em vermelho é sua subvisão, posicionada fixando seu centro no centro da visualização do host. Agora podemos escalar e girar a vista vermelha em torno de seu centro sem nenhum problema, e de fato a ilustração mostra que fizemos isso:
self.otherView.transform = CGAffineTransformScale(self.otherView.transform, 0.5, 0.5);
self.otherView.transform = CGAffineTransformRotate(self.otherView.transform, M_PI/8.0);
Enquanto isso, as restrições na exibição do host mantêm-no no lugar certo à medida que giramos o dispositivo.
Solução 4: use transformações de camada
Em vez de visualizar transformações, use transformações de camada, que não acionam o layout e, portanto, não causam conflito imediato com restrições.
Por exemplo, esta simples animação de exibição "pulsar" pode ser interrompida no layout automático:
[UIView animateWithDuration:0.3 delay:0
options:UIViewAnimationOptionAutoreverse
animations:^{
v.transform = CGAffineTransformMakeScale(1.1, 1.1);
} completion:^(BOOL finished) {
v.transform = CGAffineTransformIdentity;
}];
Mesmo que no final não tenha havido alteração no tamanho da visualização, basta definir o transform
layout de suas causas, e restrições podem fazer a visualização saltar. (Isso parece um bug ou o quê?) Mas se fizermos o mesmo com a Core Animation (usando um CABasicAnimation e aplicando a animação à camada da exibição), o layout não acontecerá e funcionará bem:
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 sua largura? Está colocando o lado direito em outra coisa?