Chapter 6, Introduction to Auto Layout
主要介绍一些基本的auto layout布局,没什么好说的。
Chapter 7, Animating Constraints
本章主要是介绍如果对约束添加动画。和普通的对UIView属性做动画不同,对约束的动画经常是创建一个新的约束替换老的约束,然后让Auto Layout在两个约束间做动画。
1 动态改变布局
假如存在一个高度的约束
@IBOutlet weak var someViewHeightConstraint: NSLayoutConstraint!
现在需要对高度做动画,更改为高度为 200
代码如下:
someViewHeightConstraint.constant = 200
如果只是单纯的执行上面的代码,视图的高度会变成200,但是不会有动画效果。此时需要调用 layoutIfNeeded() 方法,因为当改变约束constant变量的值的时候,iOS还没有机会去更新此时的布局,此时iOS只是把当前的约束标记为脏的状态。
添加如下代码,使约束添加动态效果:
someViewHeightConstraint.constant = 200
UIView.animate(withDuration: 1.0) {
self.view.layoutIfNeeded()
}
2 检查和对约束做动画
对于一些不能通过类似StoryBoard做可视化链接或者不想添加可视化的约束,可以通过代码创建。
在一些情况如果想在runtime改变一个视图的约束的值,而有没有该约束的引用,可以通过遍历视图的constraints属性
someView.constraints
比如,如果想对一个在 container view 内水平居中的 titleLabel 动态修改约束值。可以做如下尝试:
titleLabel.superview?.constraints.forEach { constraint in
if constraint.firstItem === titleLabel && constraint.firstAttribute == .centerX {
constraint.constant = -100.0
return
}
}
上面的代码实现的是水平居中约束左移100。
当然也可以在创建约束的时候,对该约束的属性Identifier设置一个字符串标示。比如对如上的titleLabel水平居中约束添加标示为 titleCenterX。那么上面的代码可以替换为:
titleLabel.superview?.constraints.forEach { constraint in
if constraint.identifier == "titleCenterX" {
constraint.constant = -100
return
}
}
3 替换已有约束做动画
目前上面内容都是结束的对已经的约束改变属性constant的值做动画
如果要实现一些更加复杂的动画效果呢,这时可能创建一个新的约束,然后替换已经存在的约束可能会实现起来更加的容易。
在开始前需要知道的是,对于某个view已经存在的一个约束比如constraintA
如果做如下处理:
constraintA.isActive = false
这会使当前视图层级把constraintA移除,如果此时外面又没有对constraintA的引用,那么constraintA将会被内存删除。
对于一个在视图内居中的view来说,它的表现形式为:
someView.centerX = multiple * superView.centerX + constant
multiple 是倍数,一般等于1
constant 移动距离,一般等于0
在之前介绍的水平居中的titleLabel左移100的则可如下表示:
titleLabel.centerX = 1.0*superView.centerX + -100.0
如果titleLabel需要一个0.5倍水平居中的约束替换已有的水平居中约束,则可创建如下新约束:
let newConstraint = NSLayoutConstraint(item: titleLabel,
attribute: .centerX,
relatedBy: .equal,
toItem: titleLabel.superview!,
attribute: .centerX,
multiplier: 0.5,
constant: 0)
newConstraint.identifier = "titleCenterX"
newConstraint.isActive = true
newConstraint 的identifiers属性设置了一个字符串标示,这样是为了更好的调试。
设置 newConstraint.isActive 告诉iOS在当前布局使用该约束。
如果一次性创建了大量的约束,没必要对每一个约束动都设置一下
someConstraint.isActive = true
使用 NSLayoutConstraint.activate([NSLayoutConstraint]) 会更好的管理代码。
4 整理
1: 如果要对一个约束动态该constant属性值,记得在 UIView.animate(...)里调用
self.view.layoutIfNeeded()告诉iOS需要重新布局了。
2: 对于一个已经存在的约束,可以通过 IBOutlets ,代码引用, 或者简单粗暴的遍历查看 identifier 或者属性详情查找。
3:对于一个约束,
设置属性 isActive = false 会让该约束的view调用 view.removeConstraint(someConstraint);
设置属性 isActive = true 会让该约束的view调用 view.addConstraint(someConstraint)。
4:最后一个小知识
一个需求,创建一个图片 imageView, imageView的初始约束为 屏幕水平居中,在屏幕外的正下方。想要对约束做动画移动到屏幕中间。
此时如果改变约束后调用 UIView.animate( self.view.layoutIfNeeded() ) 会看到imageView 直接从屏幕左上角(0, 0)处做动画到屏幕中间。
因为虽然设置了初始约束位置,但是视图一直没有执行初始化布局,所以imageView的初始化布局位置会是默认左上角(0,0)
修复问题: 在 UIView.animate( self.view.layoutIfNeeded() ) 之前再次调用一下 self.view.layoutIfNeeded() ,告诉iOS更新一下初始化布局。
// set imageView initial layout
self.view.layoutIfNeeded()
// animating update imageView layout
UIView.animate(withDuration: 1.0, animations: {
self.view.layoutIfNeeded()
})