How do I animate constraint changes?
我正在用
旧样式,我在动画块中设置帧。新样式,我有一个
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更新约束:
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; } |
在旁注中,我已经检查了很多次,这是在主线程上执行的。
两个重要注意事项:
您需要在动画块中调用
您需要专门在父视图(例如
试试这个:
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 }]; |
但实际上,这是一个非常简单的场景。如果我想通过
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]; } |
自动布局指导留下了许多需要,但它是值得阅读。我自己把它作为
通常,您只需要更新约束并在动画块内调用
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]; }]; |
样本设置:
争议在动画块之前,还是在动画块内部(请参见前面的答案),存在一些关于是否应更改约束的问题。
下面是教iOS的马丁·皮尔金顿和写汽车布局的肯·费里在Twitter上的对话。Ken解释说,尽管在动画块外更改常量目前可能有效,但这并不安全,它们应该在动画块内进行更改。https://twitter.com/kongtomorrow/status/440627401018466305
动画:样例工程下面是一个简单的项目,演示如何为视图设置动画。它使用了目标C,并通过更改几个约束的
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 |
告诉包含
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步-在更改约束之前,您可能希望调用
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有了一个新的动画机制——
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() |
由于
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中丢失,并导致正在覆盖的视图(工作时)。好的。
务必花一分钟时间阅读文档(新文档和旧文档),快速帮助和标题。苹果不断地做出很多改变管理自动布局约束(请参见堆栈视图)。或者至少是自动布局的食谱。请记住,有时最好的解决方案出现在较旧的文档/视频中。好的。
玩转动画中的值并考虑使用其他具有持续时间变量的动画。好的。
不要将特定的布局值硬编码为确定的标准更改其他常量,而使用允许您确定视图的位置。
使用故事板时要避免的快速解决方案示例好的。
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. } |
我试着对约束进行动画处理,但很难找到一个好的解释。
其他答案是完全正确的:你需要打电话给
我在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(); }); |