[EDIT: Warning:接下来的整个讨论可能会过时,或者至少会被 iOS 8 大大缓解,iOS 8 可能不再会在应用视图变换时犯下触发布局的错误。]
自动布局与视图变换
自动布局与视图变换根本不能很好地配合。据我所知,原因是您不应该弄乱具有变换(默认标识变换除外)的视图的框架 - 但这正是自动布局的作用。自动布局的工作方式是layoutSubviews
运行时会冲破所有约束并相应地设置所有视图的框架。
换句话说,约束并不是魔法,而是魔法。它们只是一个待办事项列表。layoutSubviews
是完成待办事项列表的地方。它是通过设置框架来实现的。
我忍不住认为这是一个错误。如果我将此转换应用于视图:
v.transform = CGAffineTransformMakeScale(0.5,0.5);
我希望看到视图的中心与之前的位置相同,大小只有之前的一半。但根据其限制,这可能根本不是我所看到的。
[实际上,这里还有第二个惊喜:对视图应用转换会立即触发布局。在我看来,这是另一个错误。或者也许这是第一个错误的核心。我期望的是至少在布局时间之前能够摆脱转换,例如设备被旋转 - 就像我可以在布局时间之前摆脱帧动画一样。但事实上布局时间是立即的,这似乎是错误的。]
解决方案 1:无限制
当前的一个解决方案是,如果我要对视图应用半永久变换(而不仅仅是以某种方式暂时摆动它),则删除影响它的所有约束。不幸的是,这通常会导致视图从屏幕上消失,因为自动布局仍然发生,而且现在没有任何限制告诉我们将视图放在哪里。因此,除了删除约束之外,我还设置了视图的translatesAutoresizingMaskIntoConstraints
是。该视图现在以旧方式工作,实际上不受自动布局的影响。 (它is显然,受自动布局的影响,但隐式自动调整大小掩码约束导致其行为与自动布局之前一样。)
解决方案 2:仅使用适当的约束
如果这看起来有点激进,另一种解决方案是设置约束以正确地处理预期的转换。例如,如果视图的大小纯粹由其内部固定宽度和高度决定,并且纯粹由其中心定位,则我的缩放变换将按我的预期工作。在此代码中,我删除了子视图上的现有约束(otherView
)并将它们替换为这四个约束,赋予其固定的宽度和高度,并纯粹通过其中心固定它。之后,我的比例变换起作用了:
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]];
结果是,如果没有影响视图框架的约束,自动布局将不会触及视图的框架 - 这正是涉及转换时所追求的。
解决方案 3:使用子视图
上述两种解决方案的问题在于,我们失去了限制观点的好处。所以这里有一个解决方案可以解决这个问题。从一个不可见的视图开始,它的工作只是充当宿主,并使用约束来定位它。在其中,将真实视图作为子视图。使用约束将子视图定位在主视图中,但将这些约束限制为在我们应用变换时不会反击的约束。
这是一个例子:
白色视图为宿主视图;你应该假装它是透明的,因此是不可见的。红色视图是它的子视图,通过将其中心固定到主视图的中心来定位。现在我们可以毫无问题地围绕其中心缩放和旋转红色视图,事实上,插图显示我们已经这样做了:
self.otherView.transform = CGAffineTransformScale(self.otherView.transform, 0.5, 0.5);
self.otherView.transform = CGAffineTransformRotate(self.otherView.transform, M_PI/8.0);
同时,当我们旋转设备时,主机视图上的约束使其保持在正确的位置。
解决方案 4:改用图层变换
使用图层变换而不是视图变换,它不会触发布局,因此不会立即与约束发生冲突。
例如,这个简单的“throb”视图动画很可能在自动布局下崩溃:
[UIView animateWithDuration:0.3 delay:0
options:UIViewAnimationOptionAutoreverse
animations:^{
v.transform = CGAffineTransformMakeScale(1.1, 1.1);
} completion:^(BOOL finished) {
v.transform = CGAffineTransformIdentity;
}];
尽管最终视图的大小没有改变,只是设置了它的大小transform
导致布局发生,约束可以使视图跳跃。 (这感觉像是一个错误还是什么?)但是如果我们对 Core Animation 做同样的事情(使用 CABasicAnimation 并将动画应用到视图的层),布局就不会发生,并且工作正常:
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];