【问题标题】:Are NSLayoutConstraints animatable? [duplicate]NSLayoutConstraints 是可动画的吗? [复制]
【发布时间】:2012-10-07 06:04:54
【问题描述】:

我正在尝试为一些视图设置动画,以便它们被横向的巨大键盘挡住。如果我只是为帧设置动画效果很好,但其他人认为这会适得其反,我应该更新 NSLayoutConstraints。但是,它们似乎不是可动画的。有没有人让他们成功地工作?

//heightFromTop is an NSLayoutConstraint referenced from IB
[UIView animateWithDuration:0.25 animations:^{
    self.heightFromTop.constant= 550.f;
}];

结果是瞬间跳到有问题的高度。

【问题讨论】:

  • 既然你已经尝试过了,结果是否定的,很可能它不是(直接)动画的。如果有人告诉你使用约束方法,请直接按动画问题。
  • Heres Apples 关于此的文档。 (一直到底部。)developer.apple.com/library/ios/documentation/UserExperience/…
  • 现在使用布局约束是一种方法。这是一个视频教程,介绍如何主要在情节提要中执行此操作,而不是手动输入和维护布局约束。 youtube.com/watch?v=8KVKXlh6sKI

标签: ios animation autolayout


【解决方案1】:

只需遵循这个确切的模式:

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

[UIView animateWithDuration:0.25f animations:^{
   [myView layoutIfNeeded];
}];

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

在 Swift 2 中:

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

UIView.animateWithDuration(0.25, animations: {
   myView.layoutIfNeeded()
})

【讨论】:

  • 哦,我知道我现在做错了什么(我之前使用过这种模式但仍然没有成功)......我打电话给setNeedsLayout而不是layoutIfNeeded当然是DOH时刻!实际上只是更改约束会自动调用setNeedsLayout,所以我猜layoutIfNeeded 会以某种方式覆盖它。
  • 您是正确的,更新约束会自动调用setNeedsLayout;但是setNeedsUpdateConstraints 的目的是告诉视图树重新计算约束;我们可能还有其他约束取决于我们刚刚更改的约束。在您的情况下,没有它可能没问题,但是如果有另一个视图例如粘在视图的底部,那么该视图将不知道它需要更新。上面的模式是一个安全的模式,我认为应该是很好的做法。
  • 只是一个小补充。我在viewDidLoad 中使用了它,这导致了不良行为。我需要打电话给viewDidAppear
  • 嗨,我的代码有问题。我试图动画化的对象有其他对象与它相关的约束,虽然这些对象也在移动,但它们并没有动画地移动,它们只是瞬间移动到它们的最终位置。我该怎么办?
  • 确保您正在调用最顶层超级视图的layoutIfNeeded 并为其设置动画
【解决方案2】:

我尝试了@Centurion 的方法,但是如果从情节提要中加载,我的视图会以某种方式动画到错误的帧。如果我用updateConstraintsIfNeeded 替换第一个layoutIfNeeded,问题就会消失,尽管我不知道为什么。如果有人能给出解释,将不胜感激。

[self.view updateConstraintsIfNeeded];
[UIView animateWithDuration:1.0 animations:^{
    self.myConstraint.constant= 100;
    [self.view layoutIfNeeded];
}];

【讨论】:

    【解决方案3】:

    Apple 建议的方式有点不同 (See example in "Animating Changes Made by Auto Layout" section)。首先你需要在动画之前调用 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
    
    }];
    

    【讨论】:

    • 感谢百夫长!这个解决方案对我来说效果更好,因为我有其他视图属性需要与约束一起进行动画处理。
    • 这种方法遇到了一些问题。我发布了一个稍作修改的版本,作为单独的答案对我有用。
    • 这种方法对我来说更好。 Johns 解决方案对 UITableView 的分隔符做了有线动画。这个完全可靠。
    【解决方案4】:

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

    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];
        }
    }
    

    调整完整动作 sn-p

    // 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
        // ...
    }
    

    调整小动作 sn-p

    // 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
            // ...
        }
    

    如果您愿意,可以使用 animateWithDuration 代替 transitionWithView。

    希望这会有所帮助。

    【讨论】:

    • 我认为您的视图仍在跳跃,因为您正在为您的视图设置动画,而另一个布局正在为它们设置动画。如果是这样,只需将UIViewAnimationOptionLayoutSubviews 和/或UIViewAnimationOptionBeginFromCurrentState 传递给动画选项。
    猜你喜欢
    • 1970-01-01
    • 2019-08-31
    • 2011-07-29
    • 1970-01-01
    • 1970-01-01
    • 2012-11-17
    • 2018-11-27
    • 1970-01-01
    • 2020-06-08
    相关资源
    最近更新 更多