虽然我正在探索观察 UIView
的 bounds
或 frame
更改的选项(提到 here 和 here ) ,我遇到了一个非常奇怪的差异:didSet
和 willSet
将根据您将 UIView
放置在 View 中的位置以不同方式触发层次结构:
- 如果我在 View Controller 的根 为
UIView
使用属性观察器,我只会得到didSet
和willSet
来自frame
变化的事件。 - 如果我为 View Controller 中的
UIView
使用属性观察器,它是一个 subview,我只会得到didSet
和willSet
来自bounds
变化的事件。
我想首先指出,我明确避免提到 here 中的 KVO 方法因为官方不支持。我也不打算使用提到的 viewDidLayoutSubviews()
here因为这不适用于观察 subview 的变化(参见 doc)。 这个问题假定我偏好使用 didSet
和 willSet
来观察 UIView
的 bounds
/frame
更改。
我遇到的最接近的问题是 this question但是只涉及到初始化语句,也没有提到观察 subview 的情况。
详情
要查看实际效果,请查看 🔨 my sample project .
我真的很疑惑为什么有时不调用bounds
观察者,所以我也添加了frame
观察者,甚至有时会调用frame
观察者没有叫。最终,我找到了它们工作方式不同的关键设置: View 在 View 层次结构中的位置,如上所述。
我如何测试:在这两种情况下,旋转设备以更改 View 的 frame
/bounds
。
这是我的 UIView
子类:
公共(public)类 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)
}
}
}
在我的示例代码中,如果您旋转设备,您会看到在我观察 Root View 的一种情况下(ViewController
的 self.view
-- 以蓝色显示),我永远不会收到 bounds
更改的通知,尽管它实际上已更改。 subview 则相反——我从未收到 frame
更改的通知,尽管它已经更改。
环境
我正在 iPad Air 和 iPad Pro 等设备上使用 iOS 11.4 SDK 在 Xcode 9.3 上测试这个项目。我还没有在 iOS 12 beta 上试过。
我的问题
- 当
UIView
在 View 层次结构中的位置不同时,为什么didSet
和willSet
的触发方式不同? - 当
bounds
的didSet
被触发时,为什么frame
的didSet
也不会被触发(对于 subview )?反之亦然(对于 Root View )? - 如果有的话,有什么方法可以确保无论将
UIView
放在 View 层次结构中的哪个位置,我都能始终观察到bounds
的变化?<
最佳答案
来 self 在 Apple Developer Forum 中的转发, QuinceyMorris
帮助我阐明了这种方法的问题,以及一种无论我将 View 放在 View 层次结构中的什么位置都有效的方法。
... an Obj-C property can change value without having its setter called. Changing the instance variable (of simple properties) is a very common Obj-C pattern. It is of course not KVO compliant without additional work, but that's why KVO compliance is not found universally.
... Your willSet/didSet accessors will only trigger when the change goes through their own property. There is nothing you can predict or assume about which property will be used. Even if you see a regularity now, there may be edge cases that are different, and the behavior may change in the future.
根据他建议我覆盖 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()
}
}
关于swift - UIView 边界和框架的属性观察者 react 不同,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50889512/