UIView边界和框架的财产观察者的反应不同

时间:2018-06-16 16:04:31

标签: swift uiview properties bounds observers

我正在探索观察UIView boundsframe更改(提及herehere)的选项,遇到了一个非常奇怪的差异: didSetwillSet将根据您将UIView置于视图层次结构中的不同而触发:

  • 如果我在{strong>根视图控制器使用UIView的属性观察者,我只会从{{1}获得didSetwillSet个事件变化。
  • 如果我在视图控制器中使用frame属性观察器子视图,我只会从{{1}获取UIViewdidSet个事件改变。

我首先要注意的是,我明确避免使用here提及的KVO方法,因为它没有得到官方支持。我也不打算使用willSet提及here,因为这不适用于观察子视图的更改(请参阅doc)。 此问题假定我倾向于使用boundsviewDidLayoutSubviews()来观察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) } } } 更改的通知,尽管它已更改。

enter image description here enter image description here enter image description here

环境

我在iPad Air和iPad Pro等设备上使用iOS 11.4 SDK在Xcode 9.3上测试此项目。我还没有尝试过iOS 12测试版。

我的问题

  • ViewController在视图层次结构中的位置不同时,为什么self.viewbounds会以不同的方式触发?
  • 触发frame的{​​{1}}时,为什么didSet willSet不会被触发(对于子视图)?反之亦然(对于根视图)?
  • 有什么方法可以确保无论我在视图层次结构中的位置如何,都可以UIView更改didSet

1 个答案:

答案 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()
    }
}