ios - 我如何动画约束更改?




animation ios6 autolayout (11)

我非常感谢所提供的答案,但我认为这样做会更好一点。

文档中的基本块动画

[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方法的动画块

[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。

- (void)updateConstraints
{
    // Update some constraints

    [super updateConstraints];
}

AutoLayout指南留下了许多不足之处,但值得一读。 我自己使用这个作为一个UISwitch一部分,它使用一对简单的细微折叠动画(0.2秒长)切换一对UITextField的子视图。 如上所述,子视图的约束在UIView子类updateConstraints方法中处理。

我正在使用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;
}

横幅的移动与预期完全相同,但没有动画。

更新:我重新观看了涵盖动画的WWDC12视频“ 掌握自动布局的最佳实践 ”。 它讨论了如何使用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;
}

在附注中,我已经检查了很多次,并且正在主线程上执行。


迅捷解决方案:

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

Swift 4解决方案

UIView.animate

三个简单的步骤:

  1. 更改约束,例如:

    heightAnchor.constant = 50
    
  2. 告诉包含的view ,它的布局很脏,自动布局应该重新计算布局:

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

    UIView.animate(withDuration: 0.5) {
        self.view.layoutIfNeeded()
    }
    

完整的最简单的例子:

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

边注

有一个可选的第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()
}

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

由于animatoranimator的封装,我们可以继续参考并稍后调用它。 但是,由于在动画块中我们只是告诉自动布局来重新计算帧,我们必须在调用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()

改变约束和启动动画的顺序非常重要 - 如果我们只是改变约束并将我们的动画制作者留在以后的某个点上,那么下一个重绘周期可以调用自动布局重新计算,并且更改不会动画化。

另外,请记住,一个动画师是不可重用的 - 一旦你运行它,你不能“重新运行”它。 所以我猜想,除非我们用它来控制交互式动画,否则没有什么好的理由让动画师保持活跃。


通常,您只需更新约束并在动画块内调用layoutIfNeeded 。 这可以是更改.constant属性,添加删除约束(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];
}];

样本设置:

争议

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

以下是教授iOS的Martin Pilkington和撰写Auto Layout的Ken Ferry之间的Twitter对话。 Ken解释说,尽管改变动画块之外的常量现在可以工作,但这并不安全,他们应该真正在动画块内部进行更改。 https://twitter.com/kongtomorrow/status/440627401018466305

动画:

示例项目

以下是一个简单的项目,展示如何将视图变成动画。 它使用Objective C并通过更改多个约束的.active属性来为视图添加动画。 https://github.com/shepting/SampleAutoLayoutAnimation


工作解决方案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.
}

故事板,代码,技巧和几个问题

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

将要定位的约束条件放入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!
}()

经过一些实验后,我注意到必须从视图ABOVE (也就是superview)中获取约束条件约束的两个视图。 在下面的示例中(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!
}()

....

一段时间之后...

@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

        }
    }
}

许多错误的转折

这些笔记实际上是我为自己写的一套技巧。 我亲自和痛苦地做了所有的事情。 希望本指南可以省掉其他人。

  1. 小心zPositioning。 有时候什么都没有发生,你应该隐藏一些其他视图或者使用视图调试器来定位你的动画视图。 我甚至发现用户定义的运行时属性在故事板的xml中丢失并导致动画视图被覆盖(工作时)的情况。

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

  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类和子类来构建约束。 这些工作很好,但花了我一段时间才意识到,我需要的所有约束已经存在于故事板中。 如果您在代码中构建约束,那么肯定会使用此方法来聚合您的约束:

使用故事板时AVOID解决方案的快速示例

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的位置,那么很可能什么事情都不会发生:在这种情况下,您可能会有这样一个半熟的解决方案:

注意 - 花点时间阅读下面的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
        }

    }

从AutoLayout指南中摘录(注意第二个片段用于使用OS X)。 顺便说一句 - 据我所知,这已不在当前指南中。 首选技术不断发展。

动画制作自动布局所做的更改

如果您需要完全控制Auto Layout所做的动画更改,则必须以编程方式更改您的约束。 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];

对于那些更好地视觉学习的人来说,请查看Apple的这个早期视频

密切关注

通常在文档中有小笔记或代码片断,这些都会导致更大的想法。 例如,将自动布局约束附加到动态动画设计师是一个很大的想法。

祝你好运,愿军队与你同在。


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

其中,他编码如下:

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

希望能帮助到你。


两个重要提示:

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

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

尝试这个:

Objective-C的

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

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

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

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

斯威夫特3

_addBannerDistanceFromBottomConstraint.constant = 0

UIView.animate(withDuration: 5) {
    self.view.layoutIfNeeded()
}

我试图让Constraints动画,并且很难找到一个好的解释。

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

我在GitHub中创建了一个例子


在约束动画的上下文中,我想提一下在keyboard_opened通知中立即动画约束的具体情况。

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

我无法直接在键盘通知中实现一致的顺畅约束动画。 大约一半的时间观点会跳到新的位置 - 没有动画。

它发生在我身上,可能会有一些额外的布局因键盘打开而发生。 每延迟10ms添加一个简单的dispatch_after块,动画就会运行 - 不会跳跃。


在Swift中:

 func pathForResource(  name: String?, 
                        ofType ext: String?, 
                        inDirectory subpath: String?) -> String?  {

  // **name:** Name of Hmtl
  // **ofType ext:** extension for type of file. In this case "html"
  // **inDirectory subpath:** the folder where are the file. 
  //    In this case the file is in root folder

    let path = NSBundle.mainBundle().pathForResource(             "dados",
                                                     ofType:      "html", 
                                                     inDirectory: "root")
    var requestURL = NSURL(string:path!)
    var request = NSURLRequest(URL:requestURL)

    webView.loadRequest(request)
}




ios animation ios6 autolayout