繁体   English   中英

layoutSubviews vs frame didSet用于检测视图的框架何时更改

[英]layoutSubviews vs frame didSet to detect when the view's frame changed

假设我有一个UIView子类,当其框架更改时,该子类需要更新某些内容。 例如,当它在偶数像素上时,它将是红色,而当它在奇数像素上时将是蓝色。

我已经看到至少四种方法可以做到这一点:

  1. view.layoutSubviews()

     override func layoutSubviews() { super.layoutSubviews() backgroundColor = calculateColorFromFrame(frame) } 
  2. view.frame didSet

     override var frame: CGRect { didSet { backgroundColor = calculateColorFromFrame(frame) } } 
  3. 在视图的框架上添加一个KVO观察器。

  4. 在视图的包装UIViewController上,实现viewDidLayoutSubviews()

     override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() view.updateBackgroundColorForNewFrame() } 
  5. 您知道其他方式吗?

这些方法中的哪一个是首选,并且在本质上优于另一种? 苹果的官方建议是什么?

  1. layoutSubviews :您可以依靠在大小已更改的视图上调用该方法,也可以依靠该视图接收到setNeedsLayout 如果视图的位置发生变化,但是其大小没有变化,则系统不会自动在该视图上调用layoutSubviews

  2. frame didSet :适用于使用autoresizingMask布局的视图。 它不适用于使用普通约束布局的视图。 相反,自动布局会设置视图的centerbounds 但是,这些是实现细节,可能会在不同版本的UIKit中发生变化。 我在Xcode 10 beta 6随附的iOS 12.0 Simulator上进行了测试。

  3. frame KVO:出于某些原因,该功能在某些情况下与#2相同。 另外, frame没有文档证明是KVO兼容的,因此允许UIKit在不通知观察者的情况下更改frame

  4. viewDidLayoutSubviews :仅当视图是视图控制器的view属性时才有效,然后仅在view收到layoutSubviews消息时才有效(请参阅#1)。 同样重要的是要理解,当调用此方法时,视图控制器的view及其直接子视图已经被布局,但是更深的子视图尚未被布局。 (见这个答案的更全面的解释。)

还要注意,视图的frame是根据其layer某些属性计算的:该图层的positionbounds.sizeanchorPointtransform 如果有任何方法直接设置这些图层属性,则以上方法均无效。

因此,在视图位置发生变化时,没有特别好的通知方法。 技术上正确的方法可能是使用CAAction 当图层检测到其position正在改变时,它会请求其委托(对于视图图层来说,是视图本身)来执行某项操作。 它通过发送actionForLayer:forKey:消息进行询问。 因此,您可以在视图子类中重写action(forLayer:forKey:)以可靠地得到通知。 但是,该层更改其position (并因此更改其frame之前会先请求操作。 更改其position 后,它将运行该动作。 因此,您必须经过以下舞蹈:

override func action(for layer: CALayer, forKey event: String) -> CAAction? {
    class MyAction: CAAction {
        init(view: MyView, nextAction: CAAction?) {
            self.view = view
            self.nextAction = nextAction
        }
        let view: MyView
        let nextAction: CAAction?
        func run(forKey event: String, object: Any, arguments: [AnyHashable : Any]?) {


            // THIS IS WHERE YOU LOOK AT THE NEW FRAME AND ACT ACCORDINGLY.


            print("This is the action. Frame now: \(view.frame)")
            nextAction?.run(forKey: event, object: object, arguments: arguments)
        }
    }

    let action = super.action(for: layer, forKey: event)
    if event == "position" {
        return MyAction(view: self, nextAction: action)
    } else {
        return action
    }
}

actionForLayer:forKey:UIView实现通常返回nil 但是在动画块内(包括当界面在纵向和横向之间旋转时),它返回(对于某些键)一个将CAAnimation安装在图层上的动作。 因此,如果有的话,运行super返回的动作很重要。

我可能对您的问题理解不正确或对Apple文档的理解有误,但以下是我对使用什么来查看镜架变化的看法:

  1. 您不应直接调用view.layoutSubviews()。 只能由UIView的子类覆盖LayoutSubviews方法“仅当子视图的自动调整大小和基于约束的行为无法提供所需的行为时,才可以使用实现直接设置子视图的框架矩形”。 -从Apple文档中复制。 因此,我不会使用此方法,因为它不适用于您要更改颜色的视图,但不适用于您的视图的子视图。
  2. 我认为这是一种非常优雅的使用方式,因为如果移动视图,则框架将设置为新值。 但是,当框架更改大小时,didSet也将被调用,并且框架可能不会移动。 如果您的calculateColorFromFrame(frame)是一种昂贵的方法,并且视图可以更改大小,则可能会导致性能下降。
  3. 我认为这是与第二点相同的繁琐方法。 唯一的事情是,您现在可以使用另一个类来观察视图框架中的更改,并调用calculateColorFromFrame(frame)方法。
  4. 各个视图控制器中的viewDidLayoutSubviews()也是一种好方法,但是,当该视图控制器的主视图中的某些内容发生更改并且该视图必须重新布置其子视图时,此处将调用此方法。 这也意味着当您的视图没有移动或更改并且calculateColorFromFrame(frame)不变时,可以调用此方法。 因此,这可能经常会多次调用此方法。

我很好奇的是,您如何有效地计算奇数和偶数像素,因为帧位置和大小基于点,并且一个点可以跨越一个以上的像素。

我希望这能回答您的问题。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM