UIView的bounds和frame属性的属性观察器(Property observers)有不同的响应方式

3

在探索观察 UIViewboundsframe 变化的选项时(在 这里这里 提到),我遇到了一个非常奇怪的差异:didSetwillSet 会根据你把 UIView 放在视图层次结构中的位置而触发不同的事件:

  • 如果我在视图控制器的处使用属性观察器来观察 UIView,我只会从 frame 变化中得到 didSetwillSet 事件。
  • 如果我在视图控制器内的子视图上使用属性观察器来观察 UIView,我只会从 bounds 变化中得到 didSetwillSet 事件。

首先我想指出,我明确避免使用此处提到的KVO方法,因为它没有官方支持。我也不想使用此处提到的viewDidLayoutSubviews()方法,因为它无法观察子视图的变化(请参见文档)。这个问题假设我倾向于使用didSetwillSet来观察UIViewbounds / 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)
        }
    }
}

在我的示例代码中,如果你旋转设备,你会发现在观察根视图(ViewControllerself.view -- 显示为蓝色)的一个情况下,尽管实际上它已经改变了,我永远不会收到bounds更改的通知。相反,在子视图方面则完全相反--尽管其已经更改,我也永远没有收到frame更改的通知。

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

环境

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

我的问题

  • 为什么当UIView在视图层次结构中放置不同位置时,didSetwillSet会以不同的方式触发?
  • 当触发boundsdidSet时,为什么子视图的framedidSet不会被触发?反之亦然(对于根视图)?
  • 是否有一种方法可以确保我始终可以观察到UIViewbounds更改,无论我将其放在视图层次结构的哪个位置?
1个回答

4
从我在Apple Developer Forum中的帖子中,QuinceyMorris帮助我澄清了这种方法以及一种能够在视图层次结构中放置视图的方法。
引用如下:

......一个Obj-C属性可以在没有调用setter的情况下更改值。更改实例变量(简单属性)是非常常见的Obj-C模式。当然,它不是KVO兼容的,除非进行额外的处理,但这就是为什么KVO兼容性并不普遍存在的原因。

......只有当更改通过其自己的属性时,您的willSet/didSet访问器才会触发。对于将使用哪个属性,您无法预测或假设任何内容。即使您现在看到规律,也可能会有不同的边缘案例,并且行为可能会在将来更改。

基于他建议我重写layoutSubviews,这是我的更新后的子类(就像这个答案所述):
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()
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接