触发时机(参考):
- 自身被
init(frame: CGRect)
创建出来且CGRect不会zero时。
- 自身被
- addSubview的时候。
- 当自身或子视图size发生改变的时候。
- 滑动UIScrollView的时候。
- 旋转Screen会触发父UIView上的layoutSubviews事件。
着重讨论下比较复杂的第二条情况(子视图size发生改变的时候)。
建立如下白板Demo
ViewController中添加橙色父视图,其中含有
Change
按钮、紫色子视图,且给紫色视图添加autolayout。image.png
完整代码:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
}
class OrangeView: UIView {
@IBOutlet weak var purpleView: UIView!
@IBOutlet weak var purpleViewH: NSLayoutConstraint!
override func layoutSubviews() {
super.layoutSubviews()
print(#function)
}
@IBAction func clickBtn(_ sender: Any) {
changeConstraint()
}
func changeConstraint() {
if purpleViewH.constant == 30.0 {
purpleViewH.constant = 160.0
} else {
purpleViewH.constant = 30.0
}
print(purpleView.frame.size.height)
}
}
此时点击Change
按钮,紫色视图变为160高度,打印:
可以看到即使更改了
purpleViewH.constant = 160.0
但是purpleView.frame.size.height
仍为30.0
,这种情况是在purpleViewH.constant = 160.0
时橙色视图被标记为“需要刷新”,在当前runloop结束时才会调用layoutSubviews
方法进行真正刷新,故打印仍为30.0
。更改
changeConstraint ()
为:
func changeConstraint() {
if purpleViewH.constant == 30.0 {
purpleViewH.constant = 160.0
} else {
purpleViewH.constant = 30.0
}
self.layoutIfNeeded()
print(purpleView.frame.size.height)
}
紫色视图变为160高度。打印:
purpleViewH.constant = 160.0
将橙色视图被标记为“需要刷新”,layoutIfNeeded()
会把“需要刷新”的视图立即去调用layoutSubviews()
,purpleView.frame.size.height
也被更新为160.0
。更改
changeConstraint ()
为:
func changeConstraint() {
self.layoutIfNeeded()
print(purpleView.frame.size.height)
}
打印:
此时
layoutSubviews()
不会被调用,紫色视图依旧也给30.0高度。更改
changeConstraint ()
为:
func changeConstraint() {
self.setNeedsLayout()
self.layoutIfNeeded()
print(purpleView.frame.size.height)
}
打印:
setNeedsLayout()
的作用就是标记当前视图为“需要刷新”(即使其实没必要刷新的),layoutIfNeeded ()
才会立即去调用了layoutSubviews()
。
将紫色视图的autoLayout删掉,同时更改代码为:
@IBAction func clickBtn(_ sender: Any) {
changeFrame()
}
func changeFrame() {
if purpleView.bounds.height == 30.0 {
purpleView.bounds = CGRect.init(x: 0, y: 0, width: purpleView.bounds.width, height: 160.0)
} else {
purpleView.bounds = CGRect.init(x: 0, y: 0, width: purpleView.bounds.width, height: 30.0)
}
print(purpleView.frame.size.height)
}
紫色视图变为高度160.0,打印:
用bounds、frame进行布局,会立即改变,在当前runloop结束时调用
layoutSubviews ()
。更改
changeFrame ()
为:
func changeFrame() {
if purpleView.bounds.height == 30.0 {
purpleView.bounds = CGRect.init(x: 0, y: 0, width: purpleView.bounds.width, height: 160.0)
} else {
purpleView.bounds = CGRect.init(x: 0, y: 0, width: purpleView.bounds.width, height: 30.0)
}
self.layoutIfNeeded()
print(purpleView.frame.size.height)
}
紫色视图变为高度160.0,打印:
如果自身已经被标记了“需要刷新”(可能是由于改变autolayout、bounds等的值被系统自动标记,也可能是
setNeedsLayout
强制标记)layoutIfNeeded ()
就会立即调用layoutSubviews()
更改
changeFrame ()
为:
func changeFrame() {
self.setNeedsLayout()
print(purpleView.frame.size.height)
}
打印:
setNeedsLayout()
将当前视图强制标记为“需要刷新”,在runloop当前周期结束前调用layoutSubviews()
可以得出结论
setNeedsLayout()
是将当前视图强制标记为“需要刷新”,在runloop当前周期结束前调用layoutSubviews()
如果视图已经被标记了“需要刷新”,layoutIfNeeded()
就会立即调用layoutSubviews()
,同时将视图标记为“不需要刷新”,以免在runloop当前周期结束前又调用次layoutSubviews()
。
能将视图标记为“需要刷新”的方式就是开头说的那几种。
网友评论