[编辑:警告:随后的整个讨论可能会过时,或者至少会被 iOS 8 大大减轻,这可能不再会在应用视图转换时触发布局的错误。]
自动布局与视图变换
自动布局在视图变换中根本无法发挥作用。据我所知,原因是您不应该弄乱具有变换(默认身份变换除外)的视图框架 - 但这正是自动布局所做的。自动布局的工作方式是在layoutSubviews
运行时冲破所有约束并相应地设置所有视图的框架。
换句话说,约束不是魔术。它们只是一个待办事项清单。layoutSubviews
是待办事项列表完成的地方。它通过设置框架来实现。
我不禁认为这是一个错误。如果我将此转换应用于视图:
v.transform = CGAffineTransformMakeScale(0.5,0.5);
我希望看到视图的中心出现在与以前相同的位置,并且大小只有一半。但根据它的限制,这可能不是我所看到的。
[实际上,这里还有第二个惊喜:对视图应用变换会立即触发布局。在我看来,这似乎是另一个错误。或者也许它是第一个错误的核心。我期望的是至少在布局时间之前能够摆脱变换,例如设备旋转 - 就像我可以在布局时间之前摆脱帧动画一样。但实际上布局时间是即时的,这似乎是错误的。]
解决方案 1:无约束
当前的一种解决方案是,如果我要对视图应用半永久性变换(而不仅仅是以某种方式暂时摆动它),则删除影响它的所有约束。不幸的是,这通常会导致视图从屏幕上消失,因为自动布局仍然发生,现在没有约束告诉我们将视图放在哪里。因此,除了删除约束之外,我还将视图设置translatesAutoresizingMaskIntoConstraints
为“是”。该视图现在以旧方式工作,实际上不受自动布局的影响。(很明显,它受自动布局的影响,但是隐式的自动调整掩码约束导致它的行为就像在自动布局之前一样。)
解决方案 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:改用层变换
代替视图变换,使用层变换,它不会触发布局,因此不会立即与约束发生冲突。
例如,这个简单的“悸动”视图动画可能会在自动布局下中断:
[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];