如何动画约束更改?

我正在用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演讲掌握自动布局的最佳实践。它讨论了如何使用核心动画更新约束:

/><img src=

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

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

顺便说一句,我已经检查了很多次,这是在主要线程上执行的。

327160 次浏览

两个重要注意事项:

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

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

试试这个:

Objective-c

- (void)moveBannerOffScreen {[self.view layoutIfNeeded];
[UIView animateWithDuration:5animations:^{self._addBannerDistanceFromBottomConstraint.constant = -32;[self.view layoutIfNeeded]; // Called on parent view}];bannerIsVisible = FALSE;}
- (void)moveBannerOnScreen {[self.view layoutIfNeeded];
[UIView animateWithDuration:5animations:^{self._addBannerDistanceFromBottomConstraint.constant = 0;[self.view layoutIfNeeded]; // Called on parent view}];bannerIsVisible = TRUE;}

Swift3

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

有一篇文章谈到了这一点: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];}];}}

希望有帮助。

// Step 1, update your constraintself.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];}];

我感谢所提供的答案,但我认为再深入一点会很好。

基本块动画从留档

[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方法为子视图约束设置动画怎么办?

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

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

updateConstraint方法在UIView子类中被重写,并且必须在方法末尾调用Super。

- (void)updateConstraints{// Update some constraints
[super updateConstraints];}

自动布局指南还有很多不足之处,但值得一读。我自己将其用作UISwitch的一部分,该UISwitch使用一对UITextField切换子视图,并具有简单而微妙的折叠动画(0.2秒长)。子视图的约束正在UIView子类updateConstraint方法中处理,如上所述。

我试图动画约束,并不容易找到一个好的解释。

其他答案所说的完全正确:您需要在animateWithDuration: animations:中调用[self.view layoutIfNeeded];。然而,另一个重要的一点是为您想要动画的每个NSLayoutConstraint都有指针。

我在GitHub中创建了一个示例

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

示例代码:

[UIView animateWithDuration:0.3 animations:^{// Move to rightself.leadingConstraint.active = false;self.trailingConstraint.active = true;
// Move to bottomself.topConstraint.active = false;self.bottomConstraint.active = true;
// Make the animation happen[self.view setNeedsLayout];[self.view layoutIfNeeded];}];

示例设置:

Xcode项目,因此示例动画项目。

争议

有一些问题是关于约束是否应该改变之前动画块,还是里面它(参见前面的答案)。

以下是Martin Pilkington教授iOS和Ken Ferry编写Auto Layout的Twitter对话。Ken解释说,虽然在动画块目前可能之外更改常量有效,但这并不安全,他们真的应该更改动画块里面https://twitter.com/kongtomorrow/status/440627401018466305

动画:

示例项目

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

故事板、代码、提示和一些Gotchas

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

将您想要针对的约束放入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 }).firstreturn temp!}()

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

过滤器链

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

使用Swift动画约束

注意:此示例是故事板/代码解决方案,并假设可以在故事板中设置默认约束使用代码对更改进行动画处理。

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

lazy var centerYInflection:NSLayoutConstraint = {let temp =  self.view.constraints.filter({ $0.firstItem is MNGStarRating }).filter ( { $0.secondItem is UIWebView }).filter({ $0.firstAttribute == .CenterY }).firstreturn 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-screenif CGRectContainsRect(self.view.frame, self.ratingView.frame) {
//in frame ~ animate away//I play a sound to give the animation some life
self.centerYInflection.constant = aPointAboveSceneself.centerYInflection.priority = UILayoutPriority(950)
} else {
//I play a different sound just to keep the user engaged//out of frame ~ animate into sceneself.centerYInflection.constant = 0self.centerYInflection.priority = UILayoutPriority(950)self.view.setNeedsLayout()self.view.layoutIfNeeded()}) { (success) -> Void in
//do something else
}}}

许多错误的转弯

这些笔记真的是我为自己写的一套技巧。我个人和痛苦地做了所有的不要做的事情。希望本指南能让其他人受益。

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

  2. 总是花一分钟阅读留档(新的和旧的),快速帮助和标题。Apple不断做出很多更改以更好管理AutoLayout约束(参见堆栈视图)。或者至少自动布局食谱。请记住,有时最好的解决方案是在旧的留档/视频中。

  3. 使用动画中的值并考虑使用其他动画持续时间变体。

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

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

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

private var _nc:[NSLayoutConstraint] = []lazy var newConstraints:[NSLayoutConstraint] = {
if !(self._nc.isEmpty) {return self._nc}
let viewMargins = self.webview.layoutMarginsGuidelet minimumScreenWidth = min(UIScreen.mainScreen().bounds.width,UIScreen.mainScreen().bounds.height)
let centerY = self.ratingView.centerYAnchor.constraintEqualToAnchor(self.webview.centerYAnchor)centerY.constant = -1000.0centerY.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}()

如果你忘记了其中一个提示或更简单的提示,例如在哪里添加布局如果需要,很可能什么都不会发生:在这种情况下,你可能会有一个像这样的半生不熟的解决方案:

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

UIView.animateWithDuration(1.0, delay: 0.0, usingSpringWithDamping: 0.3, initialSpringVelocity: 1.0, options: [.CurveEaseOut], animations: { () -> Void in
//if self.starTopInflectionPoint.constant < 0  {//-3000//offscreenself.starTopInflectionPoint.constant = self.navigationController?.navigationBar.bounds.height ?? 0self.changeConstraintPriority([self.starTopInflectionPoint], value: UILayoutPriority(950), forView: self.ratingView)
} else {
self.starTopInflectionPoint.constant = -3000self.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];

对于那些学习更好的视觉检查这个早期来自Apple的视频

密切关注

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

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

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

Constraint定义了从文本字段到容器顶部的顶部空间。打开键盘后,我只需将常量除以2。

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

我突然想到,由于键盘打开,可能会发生一些额外的布局。添加一个具有10毫秒延迟的简单dispatch_after块使动画每次都运行-没有跳跃。

快速解决方案:

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

工作解决方案100% Swift 5.3

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

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

使用Xcode 8.3.3为Swift 3工作并刚刚测试的解决方案:

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上调用. layoutIfNe的(),而不是self.calendar视图

希望这个帮助。

Swift 4解决方案

UIView.animate

三个简单的步骤:

  1. 更改约束,例如:

    heightAnchor.constant = 50
  2. Tell the containing view that its layout is dirty and that the autolayout should recalculate the layout:

    self.view.setNeedsLayout()
  3. In animation block tell the layout to recalculate the layout, which is equivalent of setting the frames directly (in this case the autolayout will set the frames):

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

Complete simplest example:

heightAnchor.constant = 50self.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 immediatelyself.view.layoutIfNeeded()
heightAnchor.constant = 50self.view.setNeedsLayout()UIView.animate(withDuration: 0.5) {self.view.layoutIfNeeded()}

UIViewProperties

由于iOS10我们得到了一个新的动画机制-UIViewPropertyAnimator,我们应该知道基本上相同的机制适用于它。步骤基本相同:

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

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

// prepare the animator first and keep a reference to itlet 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 animatorheightAnchor.constant = 50self.view.setNeedsLayout()animator.startAnimation()

更改约束和启动动画师的顺序很重要-如果我们只是更改约束并将我们的动画师留给以后的某个点,下一个重绘周期可以调用autolayout重新计算,并且更改将不会被动画化。

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