自动布局、约束和动画
Posted
技术标签:
【中文标题】自动布局、约束和动画【英文标题】:AutoLayout, Constraints and Animation 【发布时间】:2013-11-11 05:17:22 【问题描述】:假设我有这个UIView
:
具有这些相对约束:
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *leftMarginConstraint;
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *topMarginConstraint;
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *widthConstraint;
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *heightConstraint;
好的,现在让我们假设当用户点击UIButton
时,按钮应该移动到视图的右下角。我们可以轻松地使用两个约束来定义按钮和底部布局指南之间的底部空间以及按钮和视图右边缘之间的右侧空间(尾随空间)。
问题在于UIButton
已经有两个约束(左/上)和两个定义其宽度和高度的约束,因此我们不能添加两个新约束,因为它们会与其他约束冲突。
动画场景的简单常见情况,但它给我带来了一些问题。想法?
编辑
当用户点击UIButton
时,我需要那个按钮:
-
将其标题更改为“第二”
等待 1 秒并移动到右下角(移除顶部和左侧边距约束并添加底部和右侧边距约束)
将其标题更改为“第三”
等待 1 秒并移动到右上角(删除底部边距限制
并添加上边距约束)
我真的一定要使用这个乱七八糟的代码吗?
@implementation ViewController
NSLayoutConstraint *_topMarginConstraint;
NSLayoutConstraint *_leftMarginConstraint;
NSLayoutConstraint *_bottomMarginConstraint;
NSLayoutConstraint *_rightMarginConstraint;
- (IBAction)buttonPressed:(id)sender
UIButton *button = sender;
// 1.
[sender setTitle:@"Second" forState:UIControlStateNormal];
// 2.
double delayInSeconds = 1.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void)
[button removeConstraints:@[self.leftMarginConstraint, self.topMarginConstraint]];
_bottomMarginConstraint = [NSLayoutConstraint constraintWithItem:self.view
attribute:NSLayoutAttributeBottom
relatedBy:0 toItem:button
attribute:NSLayoutAttributeBottom
multiplier:1
constant:20];
[self.view addConstraint:_bottomMarginConstraint];
_rightMarginConstraint = [NSLayoutConstraint constraintWithItem:self.view
attribute:NSLayoutAttributeRight
relatedBy:0 toItem:button
attribute:NSLayoutAttributeRight
multiplier:1
constant:20];
[self.view addConstraint:_rightMarginConstraint];
[UIView animateWithDuration:1 animations:^
[self.view layoutIfNeeded];
completion:^(BOOL finished)
// 3.
[sender setTitle:@"Third" forState:UIControlStateNormal];
// 4.
double delayInSeconds = 1.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void)
[button removeConstraint:_bottomMarginConstraint];
_topMarginConstraint = [NSLayoutConstraint constraintWithItem:self.view
attribute:NSLayoutAttributeTop
relatedBy:0 toItem:button
attribute:NSLayoutAttributeTop
multiplier:1
constant:20];
[UIView animateWithDuration:1 animations:^
[self.view layoutIfNeeded];
];
);
];
);
严重吗? :D
【问题讨论】:
是否有办法避免在删除和添加全等约束之间出现恼人的异常UIViewAlertForUnsatisfiableConstraints:
?
【参考方案1】:
删除左侧和顶部约束,添加底部和右侧约束,然后在动画块中调用 layoutIfNeeded 以动画到新位置。第二种方法 move2 中的代码可以嵌入到 move1 的完成块中,但我发现如果将这两个移动保持在单独的方法中会更容易阅读(它确实需要添加另一个属性 bottomCon):
- (IBAction)move1:(UIButton *)sender
[sender setTitle:@"Second" forState:UIControlStateNormal];
[sender layoutIfNeeded];
[self.view removeConstraints:@[self.leftCon,self.topCon]];
self.bottomCon = [NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeBottom relatedBy:0 toItem:self.button attribute:NSLayoutAttributeBottom multiplier:1 constant:20];
[self.view addConstraint:self.bottomCon];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeRight relatedBy:0 toItem:self.button attribute:NSLayoutAttributeRight multiplier:1 constant:20]];
[UIView animateWithDuration:1 delay:1.0 options:0 animations:^
[self.view layoutIfNeeded];
completion:^(BOOL finished)
[sender setTitle:@"Third" forState:UIControlStateNormal];
[self move2];
];
-(void)move2
[self.view removeConstraint:self.bottomCon];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeTop relatedBy:0 toItem:self.button attribute:NSLayoutAttributeTop multiplier:1 constant:-20]];
[UIView animateWithDuration:1 delay:1.0 options:0 animations:^
[self.view layoutIfNeeded];
completion:nil];
【讨论】:
@FredCollins,好吧,这更像是你改变了你的问题。还有其他方法可以使用 animateWithDuration:animations:completion: 来完成,这不需要使用 dispatch_after,但我不知道这是否会变得不那么“混乱”。 @FredCollins,我已经更新了我的答案以展示我将如何做到这一点——我认为这样更容易阅读和理解。 不错的 rdelmar,我一会儿试试你的。因为我需要更改各种约束,所以无法使用动画关键帧来做到这一点?还是谢谢。 我使用了performSelector:withObject:afterDelay
,而不是使用该方法的delay
参数。【参考方案2】:
对于动画,您需要更改左侧和顶部约束的常量,而不是添加新的约束。如下:
self.mBtnTopConstraint.constant = yourval1;
self.mBtmLeftConstraint.constant = yourval2;
[yourbtn setNeedsUpdateConstraints];
[UIView animateWithDuration:0.5 animations:^
[yourbtn layoutIfNeeded];
completion:^(BOOL isFinished)
];
希望这会有所帮助。
【讨论】:
无论 rdelmar 提出什么建议也是这样做的方式之一。 是的,实际上它更好,因为我需要在不同时间摆脱一些限制。 (检查问题的更新)【参考方案3】:您可以使用您想要的结束约束创建一个占位符 UIView,并将原始约束与占位符 UIView 约束交换。
这样,它可以保持故事板中的布局约束,并防止您的代码混乱。它还为您提供了在 xcode 的预览部分中查看动画最终结果的附加效果。
如果使用 cocoapod “SBP” 只需几行即可。 Here's a tutorial.
【讨论】:
以上是关于自动布局、约束和动画的主要内容,如果未能解决你的问题,请参考以下文章