AutoLayout,约束和动画

5

假设我有这个 UIView

enter image description here

以及这些相对约束:

@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. 将标题更改为“Second”
  2. 等待1秒钟并移动到右下角(删除顶部和左侧边距约束,并添加底部和右侧边距约束)
  3. 将标题更改为“Third”
  4. 等待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: - Glavid
3个回答

11

删除左和上的约束,添加底部和右侧的约束,然后在动画块中调用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,但我不知道这是否会更加“混乱”。 - rdelmar
@FredCollins,我已经更新了我的答案,展示了我如何做到这一点 - 我认为这样更容易阅读和理解。 - rdelmar
很好,rdelmar。我一会儿就试试你的方法。不过用动画关键帧是无法实现的,因为我需要改变各种约束条件,对吧?不管怎样,还是谢谢你。 - Fred Collins
我使用了performSelector:withObject:afterDelay方法,而不是使用该方法的delay参数。 - Fred Collins

1

对于动画效果,你需要改变左侧和顶部约束的常量值,而不是添加新的约束。操作方法如下:

 self.mBtnTopConstraint.constant = yourval1;
    self.mBtmLeftConstraint.constant = yourval2;
    [yourbtn setNeedsUpdateConstraints];

    [UIView animateWithDuration:0.5 animations:^{

            [yourbtn layoutIfNeeded];

        } completion:^(BOOL isFinished){
        }];

希望这有所帮助。

rdelmar所建议的也是完成这个任务的一种方式。 - Nuzhat Zari
是的,实际上这更好,因为我需要在不同时间摆脱一些限制条件。(请查看问题更新) - Fred Collins

-1

你可以创建一个带有所需结束约束的占位符UIView,并使用该占位符UIView的约束替换原始约束。

这样可以保留Storyboard中的布局约束,避免代码混乱。同时,它还可以让你在Xcode预览部分看到动画的最终效果。

如果只用几行代码就能实现这个功能,可以使用cocoapod“SBP”。这里有一个教程


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接