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

旧的风格,我设置帧在一个动画块。 新样式,我有一个IBOutlet到自动布局约束,它决定了Y的位置,在这种情况下,它是从父视图底部的距离,并修改常数:

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

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

横幅的移动和预期的完全一样,但是没有动画。


更新:我重新看了WWDC 12演讲《掌握自动布局的最佳实践》,其中包括动画。它讨论了如何使用CoreAnimation更新约束:

我尝试了下面的代码,但得到完全相同的结果:

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

另一方面,我已经检查了很多次,这是在主线程上执行的。


当前回答

故事板,代码,技巧和一些陷阱

其他答案都很好,但这一个用最近的例子强调了动画约束的一些相当重要的陷阱。我经历了很多变化,才意识到以下几点:

将目标约束设置为Class变量,以保存强引用。在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!
}()

经过一些实验后,我注意到必须从上面的视图(又名superview)两个视图中获得约束,约束是定义的。在下例中(MNGStarRating和UIWebView都是我创建约束之间的两种类型的项,它们是self.view中的子视图)。

过滤器链

我利用Swift的过滤器方法来分离作为拐点的所需约束。还有更复杂的,但滤镜做得很好。

使用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!
}()

....

一段时间后…

@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

        }
    }
}

许多错误的转弯

这些笔记实际上是我为自己写的一套技巧。我亲自痛苦地做了所有不该做的事。希望这篇指南可以帮助其他人。

Watch out for zPositioning. Sometimes when nothing is apparently happening, you should hide some of the other views or use the view debugger to locate your animated view. I've even found cases where a User Defined Runtime Attribute was lost in a storyboard's xml and led to the animated view being covered (while working). Always take a minute to read the documentation (new and old), Quick Help, and headers. Apple keeps making a lot of changes to better manage AutoLayout constraints (see stack views). Or at least the AutoLayout Cookbook. Keep in mind that sometimes the best solutions are in the older documentation/videos. Play around with the values in the animation and consider using other animateWithDuration variants. Don't hardcode specific layout values as criteria for determining changes to other constants, instead use values that allow you to determine the location of the view. CGRectContainsRect is one example If needed, don't hesitate to use the layout margins associated with a view participating in the constraint definition let viewMargins = self.webview.layoutMarginsGuide: is on example Don't do work you don't have to do, all views with constraints on the storyboard have constraints attached to the property self.viewName.constraints Change your priorities for any constraints to less than 1000. I set mine to 250 (low) or 750 (high) on the storyboard; (if you try to change a 1000 priority to anything in code then the app will crash because 1000 is required) Consider not immediately trying to use activateConstraints and deactivateConstraints (they have their place but when just learning or if you are using a storyboard using these probably means your doing too much ~ they do have a place though as seen below) Consider not using addConstraints / removeConstraints unless you are really adding a new constraint in code. I found that most times I layout the views in the storyboard with desired constraints (placing the view offscreen), then in code, I animate the constraints previously created in the storyboard to move the view around. I spent a lot of wasted time building up constraints with the new NSAnchorLayout class and subclasses. These work just fine but it took me a while to realize that all the constraints that I needed already existed in the storyboard. If you build constraints in code then most certainly use this method to aggregate your constraints:

使用故事板时要避免的解决方案的快速示例

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
    }()

如果你忘记了这些技巧中的一个,或者忘记了更简单的技巧,比如在哪里添加layoutIfNeeded,很可能什么都不会发生:在这种情况下,你可能会有一个不成熟的解决方案,像这样:

注意:花点时间阅读下面的自动布局部分和 最初的指导。有一种方法可以用这些技巧来补充 你的动态动画师。

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

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

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

当你不使用层支持的动画时,你必须使用约束的动画器对常量进行动画:

[[constraint animator] setConstant:42];

为了更好地了解视觉效果,可以看看苹果公司的这段早期视频。

密切关注

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

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

其他回答

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

工作方案100% Swift 5.3

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

self.btnHeightConstraint.constant = 110
UIView.animate(withDuration: 0.27) { [weak self] in 
     self?.view.layoutIfNeeded()
}

故事板,代码,技巧和一些陷阱

其他答案都很好,但这一个用最近的例子强调了动画约束的一些相当重要的陷阱。我经历了很多变化,才意识到以下几点:

将目标约束设置为Class变量,以保存强引用。在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!
}()

经过一些实验后,我注意到必须从上面的视图(又名superview)两个视图中获得约束,约束是定义的。在下例中(MNGStarRating和UIWebView都是我创建约束之间的两种类型的项,它们是self.view中的子视图)。

过滤器链

我利用Swift的过滤器方法来分离作为拐点的所需约束。还有更复杂的,但滤镜做得很好。

使用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!
}()

....

一段时间后…

@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

        }
    }
}

许多错误的转弯

这些笔记实际上是我为自己写的一套技巧。我亲自痛苦地做了所有不该做的事。希望这篇指南可以帮助其他人。

Watch out for zPositioning. Sometimes when nothing is apparently happening, you should hide some of the other views or use the view debugger to locate your animated view. I've even found cases where a User Defined Runtime Attribute was lost in a storyboard's xml and led to the animated view being covered (while working). Always take a minute to read the documentation (new and old), Quick Help, and headers. Apple keeps making a lot of changes to better manage AutoLayout constraints (see stack views). Or at least the AutoLayout Cookbook. Keep in mind that sometimes the best solutions are in the older documentation/videos. Play around with the values in the animation and consider using other animateWithDuration variants. Don't hardcode specific layout values as criteria for determining changes to other constants, instead use values that allow you to determine the location of the view. CGRectContainsRect is one example If needed, don't hesitate to use the layout margins associated with a view participating in the constraint definition let viewMargins = self.webview.layoutMarginsGuide: is on example Don't do work you don't have to do, all views with constraints on the storyboard have constraints attached to the property self.viewName.constraints Change your priorities for any constraints to less than 1000. I set mine to 250 (low) or 750 (high) on the storyboard; (if you try to change a 1000 priority to anything in code then the app will crash because 1000 is required) Consider not immediately trying to use activateConstraints and deactivateConstraints (they have their place but when just learning or if you are using a storyboard using these probably means your doing too much ~ they do have a place though as seen below) Consider not using addConstraints / removeConstraints unless you are really adding a new constraint in code. I found that most times I layout the views in the storyboard with desired constraints (placing the view offscreen), then in code, I animate the constraints previously created in the storyboard to move the view around. I spent a lot of wasted time building up constraints with the new NSAnchorLayout class and subclasses. These work just fine but it took me a while to realize that all the constraints that I needed already existed in the storyboard. If you build constraints in code then most certainly use this method to aggregate your constraints:

使用故事板时要避免的解决方案的快速示例

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
    }()

如果你忘记了这些技巧中的一个,或者忘记了更简单的技巧,比如在哪里添加layoutIfNeeded,很可能什么都不会发生:在这种情况下,你可能会有一个不成熟的解决方案,像这样:

注意:花点时间阅读下面的自动布局部分和 最初的指导。有一种方法可以用这些技巧来补充 你的动态动画师。

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

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

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

当你不使用层支持的动画时,你必须使用约束的动画器对常量进行动画:

[[constraint animator] setConstant:42];

为了更好地了解视觉效果,可以看看苹果公司的这段早期视频。

密切关注

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

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

迅速的解决方案:

yourConstraint.constant = 50
UIView.animate(withDuration: 1.0, animations: {
    yourView.layoutIfNeeded
})

在约束动画的上下文中,我想提一下在keyboard_opened通知中立即对约束进行动画的特定情况。

约束定义了一个从文本框到容器顶部的顶部空间。打开键盘时,我只需将常数除以2。

我无法直接在键盘通知中实现一致的平滑约束动画。大约一半的时间视图会跳转到它的新位置——没有动画。

在我看来,可能会有一些额外的布局发生的结果,键盘打开。 添加一个简单的dispatch_after块和10ms的延迟使得动画每次都运行-没有跳跃。