我正在探索观察UIView
bounds
或frame
更改(提及here和here)的选项,遇到了一个非常奇怪的差异: didSet
和willSet
将根据您将UIView
置于视图层次结构中的不同而触发:
UIView
的属性观察者,我只会从{{1}获得didSet
和willSet
个事件变化。frame
属性观察器子视图,我只会从{{1}获取UIView
和didSet
个事件改变。我首先要注意的是,我明确避免使用here提及的KVO方法,因为它没有得到官方支持。我也不打算使用willSet
提及here,因为这不适用于观察子视图的更改(请参阅doc)。 此问题假定我倾向于使用bounds
和viewDidLayoutSubviews()
来观察didSet
的{{1}} / willSet
更改。
我遇到的最接近的问题是this question,但它只涵盖了初始化短语,也没有提到观察子视图的情况。
要查看此操作,请查看my sample project。
我真的很困惑为什么UIView
观察者有时候没有被调用,所以我也添加了bounds
观察者,甚至有时候frame
观察者也没有被调用。最后,我能够找到不同工作方式的关键设置:视图在视图层次结构中的位置,如上所述。
我如何测试:在两种情况下,旋转设备以更改视图的bounds
/ frame
。
这是我的frame
子类:
public class BoundsObservableView:UIView {
frame
在我的示例代码中,如果您旋转设备,您会看到在我观察根视图的一种情况下(bounds
的{{1}} - 以蓝色显示),我尽管实际发生了变化,但我们永远不会收到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)
}
}
}
更改的通知,尽管它已更改。
我在iPad Air和iPad Pro等设备上使用iOS 11.4 SDK在Xcode 9.3上测试此项目。我还没有尝试过iOS 12测试版。
ViewController
在视图层次结构中的位置不同时,为什么self.view
和bounds
会以不同的方式触发?frame
的{{1}}时,为什么didSet
willSet
不会被触发(对于子视图)?反之亦然(对于根视图)?UIView
更改didSet
?答案 0 :(得分:0)
从我在Apple Developer Forum的转发中,QuinceyMorris
帮助我澄清了这种方法的问题,以及无论我将视图置于视图层次结构中的哪种方法都可行。
... Obj-C属性可以在不调用其setter的情况下更改值。更改实例变量(简单属性)是一种非常常见的Obj-C模式。如果没有额外的工作,它当然不符合KVO标准,但这就是普遍存在KVO合规性的原因。
...只有当更改通过自己的属性时,您的willSet / didSet访问器才会触发。您无法预测或假设将使用哪个属性。即使您现在看到规律性,也可能存在不同的边缘情况,并且将来可能会改变行为。
基于他推荐我覆盖layoutSubviews
,这是我更新的子类(就像this answer):
public protocol ViewBoundsObserving: class {
// Notifies the delegate that view's `bounds` has changed.
// Use `view.bounds` to access current bounds
func boundsDidChange(_ view: BoundsObservableView, from previousBounds: CGRect);
}
/// You can observe bounds change with this view subclass via `ViewBoundsObserving` delegate.
public class BoundsObservableView: UIView {
public weak var boundsDelegate: ViewBoundsObserving?
private var previousBounds: CGRect = .zero
public override func layoutSubviews() {
if (bounds != previousBounds) {
print("Bounds changed from \(previousBounds) to \(bounds)")
boundsDelegate?.boundsDidChange(self, from: previousBounds)
previousBounds = bounds
}
// UIView's implementation will layout subviews for me using Auto Resizing mask or Auto Layout constraints.
super.layoutSubviews()
}
}