故事板、代码、提示和一些陷阱
其他答案都很好,但这个答案使用最近的一个例子突出了一些相当重要的动画约束的陷阱。在我意识到以下几点之前,我经历了很多变化:
将您想要定位的约束设置为类变量以保持强引用。在 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!
}()
经过一些实验后,我注意到必须从定义约束的两个视图上方的视图(也称为超级视图)中获取约束。在下面的示例中(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
}
}
}
许多错误的转弯
这些笔记实际上是我为自己写的一组技巧。我个人和痛苦地做了所有不该做的事情。希望本指南可以避免其他人。
注意 zPositioning。有时当什么都没有发生时,您应该隐藏一些其他视图或使用视图调试器来定位您的动画视图。我什至发现了用户定义的运行时属性在故事板的 xml 中丢失并导致动画视图被覆盖(在工作时)的情况。
总是花一点时间阅读文档(新旧)、快速帮助和标题。Apple 不断进行大量更改以更好地管理 AutoLayout 约束(请参阅堆栈视图)。或者至少是AutoLayout Cookbook。请记住,有时最好的解决方案是在较旧的文档/视频中。
使用动画中的值并考虑使用其他 animateWithDuration 变体。
不要将特定布局值硬编码为确定其他常量更改的标准,而是使用允许您确定视图位置的值。CGRectContainsRect
是一个例子
- 如果需要,请不要犹豫,使用与参与约束定义的视图关联的布局边距
let viewMargins = self.webview.layoutMarginsGuide
:例如
- 不要做你不必做的工作,情节提要上所有具有约束的视图都有附加到属性 self.viewName.constraints 的约束
- 将任何约束的优先级更改为小于 1000。我在情节提要上将我的设置为 250(低)或 750(高);(如果您尝试将 1000 优先级更改为代码中的任何内容,则应用程序将崩溃,因为需要 1000)
- 考虑不要立即尝试使用 activateConstraints 和 deactivateConstraints (它们有它们的位置,但是当只是学习或者如果你正在使用情节提要时,使用这些可能意味着你做的太多了~它们确实有一个位置,如下所示)
- 考虑不要使用 addConstraints / removeConstraints ,除非你真的在代码中添加了一个新的约束。我发现大多数时候我在情节提要中使用所需的约束来布局视图(将视图放置在屏幕外),然后在代码中,我为先前在情节提要中创建的约束设置动画以移动视图。
- 我花了很多时间来建立新的 NSAnchorLayout 类和子类的约束。这些工作很好,但我花了一段时间才意识到我需要的所有约束都已经存在于情节提要中。如果您在代码中构建约束,那么肯定会使用此方法来聚合您的约束:
使用情节提要时避免使用的快速解决方案示例
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 Guide 中的片段(注意第二个片段用于使用 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 的这个早期视频。
密切关注
通常在文档中会有一些小注释或代码片段会导致更大的想法。例如,将自动布局约束附加到动态动画师是一个好主意。
祝你好运,愿原力与你同在。