在探索观察 UIView
的 bounds
或 frame
变化的选项时(在 这里 和 这里 提到),我遇到了一个非常奇怪的差异:didSet
和 willSet
会根据你把 UIView
放在视图层次结构中的位置而触发不同的事件:
- 如果我在视图控制器的根处使用属性观察器来观察
UIView
,我只会从frame
变化中得到didSet
和willSet
事件。 - 如果我在视图控制器内的子视图上使用属性观察器来观察
UIView
,我只会从bounds
变化中得到didSet
和willSet
事件。
首先我想指出,我明确避免使用此处提到的KVO方法,因为它没有官方支持。我也不想使用此处提到的viewDidLayoutSubviews()
方法,因为它无法观察子视图的变化(请参见文档)。这个问题假设我倾向于使用didSet
和willSet
来观察UIView
的bounds
/ frame
变化。
我找到的最接近的问题是这个问题,但它只涵盖了初始化阶段,并且也没有提到观察子视图的情况。
详细信息
要查看此操作,请查看我的示例项目。
我真的很困惑为什么bounds
观察者有时不会被调用,所以我添加了frame
观察者,甚至frame
观察者有时也不会被调用。最终,我能够找到它们以不同方式工作的关键设置:视图在视图层次结构中的位置,如上所述。
我的测试方法:在两种情况下,旋转设备以更改视图的frame
/ bounds
。
这是我的UIView
子类:
public class BoundsObservableView:UIView {
public weak var boundsDelegate: ViewBoundsObserving?
public override var bounds: CGRect {
willSet {
print("BOUNDS willSet bounds: \(bounds), frame: \(frame)")
boundsDelegate?.boundsWillChange(self)
}
didSet {
print("BOUNDS didSet bounds: \(bounds), frame: \(frame)")
boundsDelegate?.boundsDidChange(self)
}
}
public override var frame: CGRect {
willSet {
print("FRAME willSet frame: \(frame), bounds: \(bounds)")
boundsDelegate?.boundsWillChange(self)
}
didSet {
print("FRAME didSet frame: \(frame), bounds: \(bounds)")
boundsDelegate?.boundsDidChange(self)
}
}
}
在我的示例代码中,如果你旋转设备,你会发现在观察根视图(
ViewController
的self.view
-- 显示为蓝色)的一个情况下,尽管实际上它已经改变了,我永远不会收到bounds
更改的通知。相反,在子视图方面则完全相反--尽管其已经更改,我也永远没有收到frame
更改的通知。
环境
我正在使用Xcode 9.3和iOS 11.4 SDK在iPad Air和iPad Pro等设备上测试这个项目。我还没有在iOS 12 beta上尝试过。
我的问题
- 为什么当
UIView
在视图层次结构中放置不同位置时,didSet
和willSet
会以不同的方式触发? - 当触发
bounds
的didSet
时,为什么子视图的frame
的didSet
不会被触发?反之亦然(对于根视图)? - 是否有一种方法可以确保我始终可以观察到
UIView
的bounds
更改,无论我将其放在视图层次结构的哪个位置?