NSLayoutConstraints是否可以动画?

bor*_*den 196 animation ios autolayout

我试图动画一些视图,以便它们被景观中的巨型键盘阻挡.如果我简单地为框架设置动画,它的效果很好,但是其他人认为这会适得其反,我应该更新NSLayoutConstraints.但是,它们似乎不具有动画效果.有没有人让他们成功地工作?

//heightFromTop is an NSLayoutConstraint referenced from IB
[UIView animateWithDuration:0.25 animations:^{
    self.heightFromTop.constant= 550.f;
}];
Run Code Online (Sandbox Code Playgroud)

结果是立即跳到有问题的高度.

Joh*_*pia 472

只需遵循以下确切模式:

self.heightFromTop.constant = 550.0f;
[myView setNeedsUpdateConstraints];

[UIView animateWithDuration:0.25f animations:^{
   [myView layoutIfNeeded];
}];
Run Code Online (Sandbox Code Playgroud)

其中myView是其中视图self.heightFromTop加入.您的视图是"跳跃",因为您在动画块中唯一做的就是设置约束,这不会立即导致布局.在您的代码中,布局在您设置后的下一个运行循环中发生heightFromTop.constant,到那时您已经超出了动画块的范围.

在Swift 2中:

self.heightFromTop.constant = 550
myView.setNeedsUpdateConstraints()

UIView.animateWithDuration(0.25, animations: {
   myView.layoutIfNeeded()
})
Run Code Online (Sandbox Code Playgroud)

  • 确保你正在调用并设置最顶层superview的`layoutIfNeeded`动画 (12认同)
  • 你是正确的,更新约束会自动调用`setNeedsLayout`; 但是`setNeedsUpdateConstraints`的目的是告诉视图树重新计算约束; 我们可能有其他依赖于我们刚改变的约束.在你的情况下,没有它可能没问题,但是如果有另一个视图例如粘在视图的底部,那么该视图将不知道它需要更新.上面的模式是一种安全的模式,我认为应该是一个很好的实践观察. (11认同)
  • 嗨,我的代码有问题.我正在尝试动画的对象有其他具有与之关联的约束的对象,虽然这些对象也在移动,但它们并没有动画,它们只是瞬间移动到最终位置.我该怎么办? (4认同)

Cen*_*ion 80

Apple建议的方式略有不同(请参阅"动画更改由自动布局进行的动画"部分中的示例).首先,您需要在动画之前调用layoutIfNeeded.然后在动画块中添加动画内容,然后再次调用layoutIfNeeded.对于像我这样过渡到自动布局的人来说,它与我们在动画块中的帧所做的以前的动画更相似.我们只需要调用layoutIfNeeded两次 - 在动画之前和动画之后:

[self.view layoutIfNeeded]; // Ensures that all pending layout operations have been completed

[UIView animateWithDuration:1.0f animations:^{

  // Make all constraint changes here
  self.heightFromTop.constant= 550.f;

  [self.view layoutIfNeeded]; // Forces the layout of the subtree animation block and then captures all of the frame changes

}];
Run Code Online (Sandbox Code Playgroud)


Jos*_*Lin 10

我尝试了@ Centurion的方法,但不知何故,如果从故事板加载,我的视图会动画到错误的帧.这个问题消失,如果我更换一次layoutIfNeededupdateConstraintsIfNeeded,虽然我不知道为什么.如果有人能给出解释,我将不胜感激.

[self.view updateConstraintsIfNeeded];
[UIView animateWithDuration:1.0 animations:^{
    self.myConstraint.constant= 100;
    [self.view layoutIfNeeded];
}];
Run Code Online (Sandbox Code Playgroud)


Sco*_*ter 6

我遇到了类似的问题,这个线程对解决它有很大帮助。

erurainon 的回答让我走上了正轨,但我想提出一个稍微不同的答案。来自 erurainon 的建议代码对我不起作用,因为我仍然有一个跳跃而不是动画过渡。cnotethegr8 提供的链接给了我有效的答案:

自动布局指南 https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/AutoLayoutbyExample/AutoLayoutbyExample.html (一直到页面底部)。

与 erurainon 的回答有一些不同:

  1. 在调用动画方法之前在容器视图上调用 layoutIfNeeded(而不是在 myView 上调用 setNeedsUpdateConstraints)。
  2. 在动画块中设置新约束。
  3. 在动画方法中(设置约束后)在容器视图上调用 layoutIfNeeded,而不是在 myView 上。

这将遵循 Apple 在上面链接中建议的模式。

一个例子

我想为特定视图设置动画,单击按钮即可关闭或展开它。由于我使用自动布局并且不想在代码中硬编码任何尺寸(在我的情况下为高度),我决定在 viewDidLayoutSubviews 中捕获高度。使用自动布局时,您需要使用此方法而不是 viewWillAppear。由于 viewDidLayoutSubviews 可能会被多次调用,所以我使用了一个 BOOL 来让我知道我的初始化的第一次执行。

// Code snippets

@property (weak, nonatomic) IBOutlet UIView *topView; // Container for minimalView
@property (weak, nonatomic) IBOutlet UIView *minimalView; // View to animate

@property (nonatomic) CGFloat minimalViewFullHeight; // Original height of minimalView

@property (weak, nonatomic) IBOutlet NSLayoutConstraint *minimalViewHeightConstraint;

@property (nonatomic) BOOL executedViewDidLayoutSubviews;


- (void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];


    // First execution of viewDidLayoutSubviews?
    if(!self.executedViewDidLayoutSubviews){
        self.executedViewDidLayoutSubviews = YES;


        // Record some original dimensions
        self.minimalViewFullHeight = self.minimalView.bounds.size.height;


        // Setup our initial view configuration & let system know that 
        // constraints need to be updated.
        self.minimalViewHeightConstraint.constant = 0.0;
        [self.minimalView setNeedsUpdateConstraints];

        [self.topView layoutIfNeeded];
    }
}
Run Code Online (Sandbox Code Playgroud)

调整完整动作片段的大小

// An action to close our minimal view and show our normal (full) view
- (IBAction)resizeFullAction:(UIButton *)sender {

    [self.topView layoutIfNeeded];

    [UIView transitionWithView:self.minimalView
                  duration:1.0
                   options:UIViewAnimationOptionTransitionCrossDissolve
                animations:^{
                    self.minimalViewHeightConstraint.constant = 0.0;

                    // Following call to setNeedsUpdateConstraints may not be necessary
                    [self.minimalView setNeedsUpdateConstraints];

                    [self.topView layoutIfNeeded];

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

    // Other code to show full view
    // ...
}
Run Code Online (Sandbox Code Playgroud)

调整小动作片段的大小

// An action to open our minimal view and hide our normal (full) view
- (IBAction)resizeSmallAction:(UIButton *)sender {

    [self.topView layoutIfNeeded];

    [UIView transitionWithView:self.minimalView
                  duration:1.0
                   options:UIViewAnimationOptionTransitionCrossDissolve
                animations:^{
                    self.minimalViewHeightConstraint.constant = self.minimalViewFullHeight;
                    [self.minimalView setNeedsUpdateConstraints];

                    [self.topView layoutIfNeeded];

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

        // Other code to hide full view
        // ...
    }
Run Code Online (Sandbox Code Playgroud)

如果您愿意,您可以使用 animateWithDuration 而不是 transitionWithView。

希望这可以帮助。