关于ios:如何设置约束变化的动画?

How do I animate constraint changes?

我正在用AdBannerView更新一个旧的应用程序,当没有广告时,它就会从屏幕上滑下来。当有广告时,它会在屏幕上滑动。基本的东西。

旧样式,我在动画块中设置帧。新样式,我有一个IBOutlet到auto-layout约束,该约束决定y的位置,在这种情况下,它是到超级视图底部的距离,并修改常量:

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)moveBannerOffScreen {
    [UIView animateWithDuration:5 animations:^{
        _addBannerDistanceFromBottomConstraint.constant = -32;
    }];
    bannerIsVisible = FALSE;
}

- (void)moveBannerOnScreen {
    [UIView animateWithDuration:5 animations:^{
        _addBannerDistanceFromBottomConstraint.constant = 0;
    }];
    bannerIsVisible = TRUE;
}

横幅会按预期移动,但没有动画。

更新:我重新观看了WWDC12谈论掌握动画自动布局的最佳实践。它讨论了如何使用CoreAnimation更新约束:

><img src=

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)moveBannerOffScreen {
    _addBannerDistanceFromBottomConstraint.constant = -32;
    [UIView animateWithDuration:2 animations:^{
        [self.view setNeedsLayout];
    }];
    bannerIsVisible = FALSE;
}

- (void)moveBannerOnScreen {
    _addBannerDistanceFromBottomConstraint.constant = 0;
    [UIView animateWithDuration:2 animations:^{
        [self.view setNeedsLayout];
    }];
    bannerIsVisible = TRUE;
}

在旁注中,我已经检查了很多次,这是在主线程上执行的。


两个重要注意事项:

  • 您需要在动画块中调用layoutIfNeeded。苹果公司实际上建议在动画块之前调用一次,以确保所有挂起的布局操作都已完成。

  • 您需要专门在父视图(例如self.view上调用它,而不是在子视图上附加约束。这样做将更新所有受约束的视图,包括设置可能受约束到更改了约束的视图的其他视图的动画(例如,视图B附加到视图A的底部,而您刚刚更改了视图A的顶部偏移,并且您希望视图B与之一起设置动画)。

  • 试试这个:

    Objtovi-C

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    - (void)moveBannerOffScreen {
        [self.view layoutIfNeeded];

        [UIView animateWithDuration:5
            animations:^{
                self._addBannerDistanceFromBottomConstraint.constant = -32;
                [self.view layoutIfNeeded]; // Called on parent view
            }];
        bannerIsVisible = FALSE;
    }

    - (void)moveBannerOnScreen {
        [self.view layoutIfNeeded];

        [UIView animateWithDuration:5
            animations:^{
                self._addBannerDistanceFromBottomConstraint.constant = 0;
                [self.view layoutIfNeeded]; // Called on parent view
            }];
        bannerIsVisible = TRUE;
    }

    斯威夫特3

    1
    2
    3
    4
    UIView.animate(withDuration: 5) {
        self._addBannerDistanceFromBottomConstraint.constant = 0
        self.view.layoutIfNeeded()
    }


    我很感激你提供的答案,但我认为最好再进一步。

    文档中的基本块动画

    1
    2
    3
    4
    5
    [containerView layoutIfNeeded]; // Ensures that all pending layout operations have been completed
    [UIView animateWithDuration:1.0 animations:^{
         // Make all constraint changes here
         [containerView layoutIfNeeded]; // Forces the layout of the subtree animation block and then captures all of the frame changes
    }];

    但实际上,这是一个非常简单的场景。如果我想通过updateConstraints方法来设置子视图约束的动画怎么办?

    调用子视图updateConstraints方法的动画块

    1
    2
    3
    4
    5
    6
    [self.view layoutIfNeeded];
    [self.subView setNeedsUpdateConstraints];
    [self.subView updateConstraintsIfNeeded];
    [UIView animateWithDuration:1.0f delay:0.0f options:UIViewAnimationOptionLayoutSubviews animations:^{
        [self.view layoutIfNeeded];
    } completion:nil];

    updateConstraints方法在uiView子类中被重写,并且必须在方法末尾调用super。

    1
    2
    3
    4
    5
    6
    - (void)updateConstraints
    {
        // Update some constraints

        [super updateConstraints];
    }

    自动布局指导留下了许多需要,但它是值得阅读。我自己把它作为UISwitch的一部分,它用一对UITextField和一个简单而微妙的折叠动画(0.2秒长)来切换子视图。子视图的约束在上述uiview子类updateConstraints方法中处理。


    通常,您只需要更新约束并在动画块内调用layoutIfNeeded。这可以是更改NSLayoutConstraint.constant属性、添加删除约束(ios 7)或更改约束的.active属性(ios 8&9)。

    样例代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    [UIView animateWithDuration:0.3 animations:^{
        // Move to right
        self.leadingConstraint.active = false;
        self.trailingConstraint.active = true;

        // Move to bottom
        self.topConstraint.active = false;
        self.bottomConstraint.active = true;

        // Make the animation happen
        [self.view setNeedsLayout];
        [self.view layoutIfNeeded];
    }];

    样本设置:

    Xcode Project so sample animation project.

    争议

    在动画块之前,还是在动画块内部(请参见前面的答案),存在一些关于是否应更改约束的问题。

    下面是教iOS的马丁·皮尔金顿和写汽车布局的肯·费里在Twitter上的对话。Ken解释说,尽管在动画块外更改常量目前可能有效,但这并不安全,它们应该在动画块内进行更改。https://twitter.com/kongtomorrow/status/440627401018466305

    动画:

    样例工程

    下面是一个简单的项目,演示如何为视图设置动画。它使用了目标C,并通过更改几个约束的.active属性来动画视图。https://github.com/shepting/sampleautolayoutanimation(https://github.com/shepting/sampleautolayoutanimation)


    1
    2
    3
    4
    5
    6
    7
    8
    9
    // Step 1, update your constraint
    self.myOutletToConstraint.constant = 50; // New height (for example)

    // Step 2, trigger animation
    [UIView animateWithDuration:2.0 animations:^{

        // Step 3, call layoutIfNeeded on your animated view's parent
        [self.view layoutIfNeeded];
    }];

    Swift 4解决方案

    动画制作

    三个简单步骤:

  • 更改约束,例如:

    1
    heightAnchor.constant = 50
  • 告诉包含view的对象其布局已变脏,自动布局应重新计算布局:

    1
    self.view.setNeedsLayout()
  • 在动画块中,告诉布局重新计算布局,这相当于直接设置帧(在这种情况下,自动布局将设置帧):

    1
    2
    3
    UIView.animate(withDuration: 0.5) {
        self.view.layoutIfNeeded()
    }
  • 最简单的例子:

    1
    2
    3
    4
    5
    heightAnchor.constant = 50
    self.view.setNeedsLayout()
    UIView.animate(withDuration: 0.5) {
        self.view.layoutIfNeeded()
    }

    旁白

    有一个可选的第0步-在更改约束之前,您可能希望调用self.view.layoutIfNeeded(),以确保动画的起始点来自应用了旧约束的状态(如果存在一些其他不应包括在动画中的约束更改):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    otherConstraint.constant = 30
    // this will make sure that otherConstraint won't be animated but will take effect immediately
    self.view.layoutIfNeeded()

    heightAnchor.constant = 50
    self.view.setNeedsLayout()
    UIView.animate(withDuration: 0.5) {
        self.view.layoutIfNeeded()
    }

    uiviewPropertyAnimator

    因为iOS10有了一个新的动画机制——UIViewPropertyAnimator,我们应该知道基本上相同的机制也适用于它。步骤基本相同:

    1
    2
    3
    4
    5
    6
    7
    heightAnchor.constant = 50
    self.view.setNeedsLayout()
    let animator = UIViewPropertyAnimator(duration: 0.5, timingParameters: UICubicTimingParameters(animationCurve: .linear))
    animator.addAnimations {
        self.view.layoutIfNeeded()
    }
    animator.startAnimation()

    由于animator是对动画的封装,我们可以保留对它的引用,稍后再调用它。但是,由于在动画块中,我们只是告诉Autolayout重新计算帧,因此在调用startAnimation之前必须更改约束。因此类似的事情是可能的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // prepare the animator first and keep a reference to it
    let animator = UIViewPropertyAnimator(duration: 0.5, timingParameters: UICubicTimingParameters(animationCurve: .linear))
    animator.addAnimations {
        self.view.layoutIfNeeded()
    }

    // at some other point in time we change the constraints and call the animator
    heightAnchor.constant = 50
    self.view.setNeedsLayout()
    animator.startAnimation()

    更改约束和启动动画师的顺序很重要-如果我们只是更改约束并将动画师保留一段时间,则下一个重绘循环可以调用自动布局重新计算,并且更改将不会被设置动画。

    另外,记住一个动画师是不可重用的-一旦你运行它,你就不能"重新运行"它。所以我想没有一个很好的理由让动画师呆在身边,除非我们用它来控制交互式动画。


    故事板、代码、提示和一些要点

    其他的答案也很好,但这一个强调了一些相当重要的问题,即使用最近的示例来设置约束的动画。我经历了很多变化之后才意识到:好的。

    使您想要针对的约束成为类变量以保持强引用。在Swift中,我使用了惰性变量:好的。

    1
    2
    3
    4
    lazy var centerYInflection:NSLayoutConstraint = {
           let temp =  self.view.constraints.filter({ $0.firstItem is MNGStarRating }).filter ( { $0.secondItem is UIWebView }).filter({ $0.firstAttribute == .CenterY }).first
            return temp!
    }()

    在一些实验之后,我注意到,必须从上面的视图(也就是超级视图)中获得约束,这两个视图定义了约束。在下面的示例中(mngstarrating和uiwebview都是我在它们之间创建约束的两种类型的项,它们是self.view中的子视图)。好的。

    过滤链好的。

    我利用Swift的过滤方法来分离作为拐点的所需约束。一个也可能变得更复杂,但过滤器在这里做得很好。好的。

    使用swift设置约束动画好的。

    Nota Bene - This example is the storyboard/code solution and assumes
    one has made default constraints in the storyboard. One can then
    animate the changes using code.

    Ok.

    假设您创建了一个属性来使用精确的条件进行过滤,并获得动画的特定拐点(当然,如果需要多个约束,您也可以对数组进行过滤并循环通过):< OK >。

    1
    2
    3
    4
    lazy var centerYInflection:NSLayoutConstraint = {
        let temp =  self.view.constraints.filter({ $0.firstItem is MNGStarRating }).filter ( { $0.secondItem is UIWebView }).filter({ $0.firstAttribute == .CenterY }).first
        return temp!
    }()

    …好的。

    以后的某个时候…好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    @IBAction func toggleRatingView (sender:AnyObject){

        let aPointAboveScene = -(max(UIScreen.mainScreen().bounds.width,UIScreen.mainScreen().bounds.height) * 2.0)

        self.view.layoutIfNeeded()


        //Use any animation you want, I like the bounce in springVelocity...
        UIView.animateWithDuration(1.0, delay: 0.0, usingSpringWithDamping: 0.3, initialSpringVelocity: 0.75, options: [.CurveEaseOut], animations: { () -> Void in

            //I use the frames to determine if the view is on-screen
            if CGRectContainsRect(self.view.frame, self.ratingView.frame) {

                //in frame ~ animate away
                //I play a sound to give the animation some life

                self.centerYInflection.constant = aPointAboveScene
                self.centerYInflection.priority = UILayoutPriority(950)

            } else {

                //I play a different sound just to keep the user engaged
                //out of frame ~ animate into scene
                self.centerYInflection.constant = 0
                self.centerYInflection.priority = UILayoutPriority(950)
                self.view.setNeedsLayout()
                self.view.layoutIfNeeded()
             }) { (success) -> Void in

                //do something else

            }
        }
    }

    许多错误的转弯

    这些笔记真的是我为自己写的一套小贴士。我亲自做了所有不该做的事。希望这本指南能让其他人受益。好的。

  • 当心Zpositioning。有时当没有明显的发生这种情况时,您应该隐藏一些其他视图或使用该视图用于定位动画视图的调试器。我甚至发现了用户定义的运行时属性在情节提要的XML中丢失,并导致正在覆盖的视图(工作时)。好的。

  • 务必花一分钟时间阅读文档(新文档和旧文档),快速帮助和标题。苹果不断地做出很多改变管理自动布局约束(请参见堆栈视图)。或者至少是自动布局的食谱。请记住,有时最好的解决方案出现在较旧的文档/视频中。好的。

  • 玩转动画中的值并考虑使用其他具有持续时间变量的动画。好的。

  • 不要将特定的布局值硬编码为确定的标准更改其他常量,而使用允许您确定视图的位置。CGRectContainsRect是一个例子好的。

  • 如果需要,请不要犹豫使用与参与约束定义的视图let viewMargins = self.webview.layoutMarginsGuide:在示例中
  • 不做你不必做的工作,所有视图都有限制情节提要具有附加到属性的约束self.viewname.constraints
  • 将任何约束的优先级更改为小于1000。我设定我在故事板上的优先级是250(低)或750(高);(如果您试图将1000优先级更改为代码中的任何内容,那么应用程序将崩溃,因为需要1000优先级)
  • 考虑不要立即尝试使用ActivateConstraints和停用约束(它们有自己的位置,但当你只是学习或使用故事板时,使用这些可能意味着你做了太多~它们确实有一个位置,如下所示)
  • 考虑不使用addconstraints/removeconstraints,除非在代码中添加新的约束。我发现大多数时候在情节提要中使用所需约束布局视图(放置在屏幕外的视图),然后在代码中,我对之前在故事板中创建的约束进行动画处理,以移动视图。
  • 我花了很多时间用新的nsachorlayout类和子类。这些工作很好,但它我花了一段时间才意识到我需要的所有约束已经存在于故事板中。如果在代码中构建约束然后,最肯定的是使用此方法来聚合约束:
  • 使用故事板时要避免的快速解决方案示例好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    private var _nc:[NSLayoutConstraint] = []
        lazy var newConstraints:[NSLayoutConstraint] = {

            if !(self._nc.isEmpty) {
                return self._nc
            }

            let viewMargins = self.webview.layoutMarginsGuide
            let minimumScreenWidth = min(UIScreen.mainScreen().bounds.width,UIScreen.mainScreen().bounds.height)

            let centerY = self.ratingView.centerYAnchor.constraintEqualToAnchor(self.webview.centerYAnchor)
            centerY.constant = -1000.0
            centerY.priority = (950)
            let centerX =  self.ratingView.centerXAnchor.constraintEqualToAnchor(self.webview.centerXAnchor)
            centerX.priority = (950)

            if let buttonConstraints = self.originalRatingViewConstraints?.filter({

                ($0.firstItem is UIButton || $0.secondItem is UIButton )
            }) {
                self._nc.appendContentsOf(buttonConstraints)

            }

            self._nc.append( centerY)
            self._nc.append( centerX)

            self._nc.append (self.ratingView.leadingAnchor.constraintEqualToAnchor(viewMargins.leadingAnchor, constant: 10.0))
            self._nc.append (self.ratingView.trailingAnchor.constraintEqualToAnchor(viewMargins.trailingAnchor, constant: 10.0))
            self._nc.append (self.ratingView.widthAnchor.constraintEqualToConstant((minimumScreenWidth - 20.0)))
            self._nc.append (self.ratingView.heightAnchor.constraintEqualToConstant(200.0))

            return self._nc
        }()

    如果您忘记了这些技巧中的一个,或者更简单的技巧,比如在需要的地方添加布局,那么很可能什么都不会发生:在这种情况下,您可能会有一个这样的半烘焙解决方案:好的。

    NB - Take a moment to read the AutoLayout Section Below and the
    original guide. There is a way to use these techniques to supplement
    your Dynamic Animators.

    Ok.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    UIView.animateWithDuration(1.0, delay: 0.0, usingSpringWithDamping: 0.3, initialSpringVelocity: 1.0, options: [.CurveEaseOut], animations: { () -> Void in

                //
                if self.starTopInflectionPoint.constant < 0  {
                    //-3000
                    //offscreen
                    self.starTopInflectionPoint.constant = self.navigationController?.navigationBar.bounds.height ?? 0
                    self.changeConstraintPriority([self.starTopInflectionPoint], value: UILayoutPriority(950), forView: self.ratingView)

                } else {

                    self.starTopInflectionPoint.constant = -3000
                     self.changeConstraintPriority([self.starTopInflectionPoint], value: UILayoutPriority(950), forView: self.ratingView)
                }

            }) { (success) -> Void in

                //do something else
            }

        }

    自动布局指南中的代码段(请注意,第二个代码段用于使用OS X)。顺便说一句,据我所见,这已经不在当前的指南中了。首选的技术继续发展。好的。

    设置自动布局所做更改的动画好的。

    如果需要对自动布局所做的更改进行完全控制,则必须以编程方式更改约束。iOS和OSX的基本概念都是一样的,但是有一些细微的区别。好的。

    在iOS应用程序中,您的代码如下所示:好的。

    1
    2
    3
    4
    5
    [containerView layoutIfNeeded]; // Ensures that all pending layout operations have been completed
    [UIView animateWithDuration:1.0 animations:^{
         // Make all constraint changes here
         [containerView layoutIfNeeded]; // Forces the layout of the subtree animation block and then captures all of the frame changes
    }];

    在OS X中,使用支持层的动画时使用以下代码:好的。

    1
    2
    3
    4
    5
    6
    [containterView layoutSubtreeIfNeeded];
    [NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) {
         [context setAllowsImplicitAnimation: YES];
         // Make all constraint changes here
         [containerView layoutSubtreeIfNeeded];
    }];

    如果不使用基于层的动画,则必须使用约束的动画师为常量设置动画:好的。

    1
    [[constraint animator] setConstant:42];

    对于那些能更好地从视觉上学习的人来说,看看苹果公司的早期视频。好的。密切关注

    通常在文档中会有一些小的注释或代码片段,它们会导致更大的想法。例如,将自动布局约束附加到动态动画师是一个很好的主意。好的。

    祝你好运,愿原力与你同在。好的。好啊。


    快速解决方案:

    1
    2
    yourConstraint.constant = 50
    UIView.animateWithDuration(1, animations: yourView.layoutIfNeeded)

    工作溶液100%Swift 3.1

    我已经阅读了所有答案,并希望共享我在所有应用程序中使用的代码和行的层次结构,以正确地对它们进行动画处理,这里的一些解决方案不起作用,您应该在速度较慢的设备上检查它们,例如现在的iPhone 5。

    1
    2
    3
    4
    5
    self.view.layoutIfNeeded() // Force lays of all subviews on root view
    UIView.animate(withDuration: 0.5) { [weak self] in // allowing to ARC to deallocate it properly
           self?.tbConstraint.constant = 158 // my constraint constant change
           self?.view.layoutIfNeeded() // Force lays of all subviews on root view again.
    }

    我试着对约束进行动画处理,但很难找到一个好的解释。

    其他答案是完全正确的:你需要打电话给animateWithDuration: animations:里面的[self.view layoutIfNeeded];。然而,另一个重要的点是为你想要动画的每个NSLayoutConstraint都有指针。

    我在Github中创建了一个示例。


    使用xcode 8.3.3的Swift 3工作和刚测试的解决方案:

    1
    2
    3
    4
    5
    6
    self.view.layoutIfNeeded()
    self.calendarViewHeight.constant = 56.0

    UIView.animate(withDuration: 0.5, delay: 0.0, options: UIViewAnimationOptions.curveEaseIn, animations: {
            self.view.layoutIfNeeded()
        }, completion: nil)

    请记住self.calendarViewHeight是一个引用自定义视图(calendarView)的约束。我在self.view而不是self.calendarView上调用了.layoutifneeded()。

    希望这有帮助。


    有一篇文章讨论了这个问题:http://weblog.invasivecode.com/post/42362079291/auto-layout-and-core-animation-auto-layout-was

    在其中,他这样编码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    - (void)handleTapFrom:(UIGestureRecognizer *)gesture {
        if (_isVisible) {
            _isVisible = NO;
            self.topConstraint.constant = -44.;    // 1
            [self.navbar setNeedsUpdateConstraints];  // 2
            [UIView animateWithDuration:.3 animations:^{
                [self.navbar layoutIfNeeded]; // 3
            }];
        } else {
            _isVisible = YES;
            self.topConstraint.constant = 0.;
            [self.navbar setNeedsUpdateConstraints];
            [UIView animateWithDuration:.3 animations:^{
                [self.navbar layoutIfNeeded];
            }];
        }
    }

    希望它有帮助。


    在约束动画的上下文中,我想提到一种特定的情况,在这种情况下,我将立即在键盘打开的通知中为约束设置动画。

    约束定义了从文本字段到容器顶部的顶部空间。当键盘打开时,我只需要把常数除以2。

    我无法在键盘通知内直接实现圆锥形平滑约束动画。大约有一半的时间视图只会跳到新的位置,而不显示动画。

    在我看来,键盘打开可能会导致一些额外的布局。添加一个简单的调度一个接一个的块和10毫秒的延迟使动画每次运行-没有跳跃。


    对于Xamarin.ios/c版本:

    1
    2
    3
    4
    5
    UIView.Animate(5, () =>
    {
        _addBannerDistanceFromBottomConstraint.Constant = 0;
        View.LayoutIfNeeded();
    });