如何为约束更改设置动画?

DBD*_*DBD 934 animation objective-c ios autolayout ios6

我正在更新旧的应用程序,AdBannerView当没有广告时,它会从屏幕上滑落.当有广告时,它会在屏幕上滑动.基本的东西.

旧样式,我在动画块中设置框架.新风格,我有一个IBOutlet确定Y位置的约束,在这种情况下,它与superview底部的距离,并修改常量.

- (void)moveBannerOffScreen {
    [UIView animateWithDuration:5 animations:^{
        _addBannerDistanceFromBottomConstraint.constant = -32;
    }];
    bannerIsVisible = FALSE;
}

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

横幅移动,完全符合预期,但没有动画.

更新:我重新观看了WWDC12视频" 掌握自动布局的最佳实践 ",其中包括动画.它讨论了如何使用更新约束AdBannerView.

在此输入图像描述 在此输入图像描述

我尝试使用以下代码,但得到完全相同的结果.

- (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;
}
Run Code Online (Sandbox Code Playgroud)

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

g3r*_*rv4 1652

两个重要说明:

  1. 你需要layoutIfNeeded在动画块中调用.Apple实际上建议您在动画块之前调用一次,以确保所有挂起的布局操作都已完成

  2. 您需要在父视图(例如self.view)上专门调用它,而不是具有附加约束的子视图.这样做会更新所有受约束的视图,包括动画可能被约束到您更改约束的视图的其他视图(例如,视图B附加到视图A的底部,您刚刚更改了视图A的顶部偏移,您希望视图B用它来制作动画)

试试这个:

Objective-C的

- (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;
}
Run Code Online (Sandbox Code Playgroud)

斯威夫特3

UIView.animate(withDuration: 5) {
    self._addBannerDistanceFromBottomConstraint.constant = 0
    self.view.layoutIfNeeded()
}
Run Code Online (Sandbox Code Playgroud)

  • 你知道吗...你的答案是有效的.WWDC工作....我的愿景失败了.出于某种原因,我花了一个星期才意识到我正在调用`setNeedsLayout`而不是`layoutIfNeeded`.我有点害怕我花了多少时间没注意到我只是键入了错误的方法名称. (189认同)
  • 这对我来说最初没有用,然后我意识到你需要在PARENT视图上调用layoutIfNeeded,而不是约束适用的视图. (73认同)
  • 解决方案有效,但您无需在**动画块中更改约束常量**.在开始动画之前设置约束一次是完全没问题的.你应该编辑你的答案. (21认同)
  • 使用layoutIfNeeded将为所有子视图刷新设置动画,而不仅仅是约束更改.你如何只为约束变化设置动画? (19认同)
  • "Apple实际上建议你在动画块之前调用一次,以确保所有待处理的布局操作都已完成",谢谢你,从来没有想过,但这是有道理的. (10认同)
  • 效果很好 - 谢谢.但是我们很清楚:对于动画来说,基本上存在于完全不同的范围内的约束是纯粹的疯狂.这真的是一些非常干净的旧动画逻辑. (3认同)
  • @ngb设置你不想要动画的所有约束 - >调用`layoutIfNeeded`,然后用你想要动画的那个约束设置动画块,包括另一个`layoutIfNeeded`. (2认同)

Cam*_*mer 107

我很欣赏所提供的答案,但我认为进一步采取它会很好.

文档中的基本块动画

[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
}];
Run Code Online (Sandbox Code Playgroud)

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

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

[self.view layoutIfNeeded];
[self.subView setNeedsUpdateConstraints];
[self.subView updateConstraintsIfNeeded];
[UIView animateWithDuration:1.0f delay:0.0f options:UIViewAnimationOptionLayoutSubviews animations:^{
    [self.view layoutIfNeeded];
} completion:nil];
Run Code Online (Sandbox Code Playgroud)

updateConstraints方法在UIView子类中被重写,并且必须在方法结束时调用super.

- (void)updateConstraints
{
    // Update some constraints

    [super updateConstraints];
}
Run Code Online (Sandbox Code Playgroud)

AutoLayout指南还有很多不足之处,但值得一读.我自己正在使用它作为UISwitch一个用一对UITextField简单而微妙的崩溃动画(0.2秒长)切换子视图的一部分.如上所述,在UIView子类updateConstraints方法中处理子视图的约束.


Ste*_*ing 71

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

示例代码:

[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];
}];
Run Code Online (Sandbox Code Playgroud)

样本设置:

Xcode Project如此示例动画项目.

争议

关于是否应该在动画块之前或其内部更改约束存在一些问题(请参阅前面的答案).

以下是教授iOS的Martin Pilkington和编写Auto Layout的Ken Ferry之间的Twitter对话.Ken解释说,尽管在动画块之外更改常量当前可能有效,但它并不安全,它们应该在动画块内部进行更改. https://twitter.com/kongtomorrow/status/440627401018466305

动画:

示例项目

这是一个简单的项目,展示了如何动画视图.它使用Objective C并通过更改.active几个约束的属性来动画化视图. https://github.com/shepting/SampleAutoLayoutAnimation


Joh*_*rck 35

// 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];
}];
Run Code Online (Sandbox Code Playgroud)


Mil*_*sáľ 24

Swift 4解决方案

UIView.animate

三个简单的步骤:

  1. 更改约束,例如:

    heightAnchor.constant = 50
    
    Run Code Online (Sandbox Code Playgroud)
  2. 告诉包含view它的布局是脏的,并且autolayout应该重新计算布局:

    self.view.setNeedsLayout()
    
    Run Code Online (Sandbox Code Playgroud)
  3. 在动画块中告诉布局重新计算布局,这相当于直接设置帧(在这种情况下,autolayout将设置帧):

    UIView.animate(withDuration: 0.5) {
        self.view.layoutIfNeeded()
    }
    
    Run Code Online (Sandbox Code Playgroud)

完整最简单的例子:

heightAnchor.constant = 50
self.view.setNeedsLayout()
UIView.animate(withDuration: 0.5) {
    self.view.layoutIfNeeded()
}
Run Code Online (Sandbox Code Playgroud)

边注

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

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()
}
Run Code Online (Sandbox Code Playgroud)

UIViewPropertyAnimator

从iOS 10开始,我们得到了一种新的动画机制 - UIViewPropertyAnimator我们应该知道基本相同的机制适用于它.步骤基本相同:

heightAnchor.constant = 50
self.view.setNeedsLayout()
let animator = UIViewPropertyAnimator(duration: 0.5, timingParameters: UICubicTimingParameters(animationCurve: .linear))
animator.addAnimations {
    self.view.layoutIfNeeded()
}
animator.startAnimation()
Run Code Online (Sandbox Code Playgroud)

由于animator是动画的封装,我们可以继续引用它并稍后调用它.但是,因为在动画块中我们只是告诉autolayout重新计算帧,我们必须在调用之前更改约束startAnimation.因此这样的事情是可能的:

// 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()
Run Code Online (Sandbox Code Playgroud)

更改约束和启动动画师的顺序非常重要 - 如果我们只是更改约束并让我们的动画师保留稍后的点,则下一个重绘周期可以调用自动布局重新计算,并且不会对动画进行动画处理.

此外,请记住,单个动画师是不可重复使用的 - 一旦你运行它,你就不能"重新运行"它.所以我想除非我们用它来控制交互动画,否则没有什么理由让动画师保持不变.


Tom*_* C. 14

故事板,代码,提示和一些陷阱

其他答案都很好,但是这个答案突出了一些使用最近的例子制作动画约束的相当重要的问题.在我意识到以下情况之前,我经历了很多变化:

将要定位的约束设置为类变量以保存强引用.在Swift中我使用了懒惰变量:

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!
}()
Run Code Online (Sandbox Code Playgroud)

经过一些实验,我注意到必须从视图ABOVE(也就是超视图)获得约束定义的两个视图的约束.在下面的示例中(MNGStarRating和UIWebView都是我在其中创建约束的两种类型的项目,它们是self.view中的子视图).

过滤链接

我利用Swift的滤波器方法来分离将作为拐点的所需约束.人们也可能会变得更加复杂,但过滤器在这里做得很好.

使用Swift动画约束

Nota Bene - 这个例子是故事板/代码解决方案,并假设一个人在故事板中制定了默认约束.然后,可以使用代码为更改设置动画.

假设您创建一个属性以使用准确的条件进行过滤并获取动画的特定拐点(当然,如果需要多个约束,您还可以过滤数组并循环):

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!
}()
Run Code Online (Sandbox Code Playgroud)

....

一段时间以后...

@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

        }
    }
}
Run Code Online (Sandbox Code Playgroud)

许多错误的转折

这些笔记实际上是我为自己写的一组提示.我亲自和痛苦地做了所有的不该做的事情.希望本指南可以让其他人放心.

  1. 注意zPositioning.有时,当没有任何事情发生时,您应该隐藏其他一些视图或使用视图调试器来定位您的动画视图.我甚至发现了在故事板的xml中丢失了用户定义的运行时属性并导致动画视图被覆盖(工作时)的情况.

  2. 总是花点时间阅读文档(新旧),快速帮助和标题.Apple不断进行大量更改以更好地管理AutoLayout约束(请参阅堆栈视图).或者至少是AutoLayout Cookbook.请记住,有时最好的解决方案是在较旧的文档/视频中.

  3. 使用动画中的值并考虑使用其他animateWithDuration变体.

  4. 不要将特定布局值硬编码为确定其他常量更改的条件,而是使用允许您确定视图位置的值.CGRectContainsRect就是一个例子

  5. 如果需要,请不要犹豫使用与参与约束定义的视图关联的布局边距 let viewMargins = self.webview.layoutMarginsGuide:例如
  6. 不要做你不必做的工作,故事板上有约束的所有视图都附加了属性self.viewName.constraints的约束
  7. 将任何约束的优先级更改为小于1000.我在故事板上将我的设置设置为250(低)或750(高); (如果您尝试将1000优先级更改为代码中的任何内容,则应用程序将崩溃,因为需要1000)
  8. 考虑不要立即尝试使用activateConstraints和deactivateConstraints(他们有他们的位置但是当你刚刚学习或者如果你正在使用这些故事板时可能意味着你做得太多了〜他们确实有一个地方,如下所示)
  9. 考虑不使用addConstraints/removeConstraints,除非您真的在代码中添加新约束.我发现大多数时候我在故事板中布置了具有所需约束的视图(将视图放在屏幕外),然后在代码中,我为先前在故事板中创建的约束设置动画以移动视图.
  10. 我浪费了很多时间来建立新的NSAnchorLayout类和子类的约束.这些工作很好但我花了一段时间才意识到我需要的所有约束已经存在于故事板中.如果在代码中构建约束,那么大多数肯定使用此方法来聚合约束:

使用Storyboard时避免快速解决方案样本

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
    }()
Run Code Online (Sandbox Code Playgroud)

如果您忘记了其中一个提示或更简单的提示,例如添加layoutIfNeeded的位置,则很可能不会发生任何事情:在这种情况下,您可能会有一个半假的解决方案,如下所示:

注意 - 请花点时间阅读下面的AutoLayout部分和原始指南.有一种方法可以使用这些技术来补充动态动画师.

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
        }

    }
Run Code Online (Sandbox Code Playgroud)

AutoLayout指南中的代码段(请注意第二个代码段用于使用OS X).顺便说一句 - 就我所见,这已不再是现行指南. 优选技术继续发展.

动画自动布局所做的更改

如果您需要完全控制自动布局所做的动画更改,则必须以编程方式更改约束.iOS和OS X的基本概念相同,但存在一些细微差别.

在iOS应用中,您的代码看起来如下所示:

[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
}];
Run Code Online (Sandbox Code Playgroud)

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

[containterView layoutSubtreeIfNeeded];
[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) {
     [context setAllowsImplicitAnimation: YES];
     // Make all constraint changes here
     [containerView layoutSubtreeIfNeeded];
}];
Run Code Online (Sandbox Code Playgroud)

如果不使用图层支持的动画,则必须使用约束的动画制作器为常量设置动画:

[[constraint animator] setConstant:42];
Run Code Online (Sandbox Code Playgroud)

对于那些在视觉上学得更好的人来说,请查看Apple的早期视频.

密切关注

通常在文档中有小笔记或代码片段可以带来更大的想法.例如,将自动布局约束附加到动态动画师是一个很大的想法.

祝你好运,愿力量与你同在.


Naz*_*zlo 9

Swift解决方案:

yourConstraint.constant = 50
UIView.animate(withDuration: 1.0, animations: {
    yourView.layoutIfNeeded
})
Run Code Online (Sandbox Code Playgroud)


C0m*_*ade 6

工作解决方案100% Swift 3.1

我已经阅读了所有答案,并希望共享我在所有应用程序中使用过的代码和行的层次结构以使它们正确地进行动画处理。此处的某些解决方案不起作用,您现在应该在速度较慢的设备(例如iPhone 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.
}
Run Code Online (Sandbox Code Playgroud)


Gab*_*ana 5

我试图对约束进行动画处理,但找到一个好的解释并不容易。

其他答案所说的完全正确:您需要致电[self.view layoutIfNeeded];inside animateWithDuration: animations:。然而,另一个重要的一点是为每个NSLayoutConstraint想要设置动画的对象提供指针。

我在 GitHub 中创建了一个示例