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

时间:2018-09-05 18:27:10

标签: ios swift uiview

说我有一个UIView子类,当它的框架改变时,它需要更新某些东西。例如。当它在偶数像素上时,它将是红色,当它在奇数像素上时将是蓝色。

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

  1. view.layoutSubviews()

    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet"/>
    
    <div class='wrapper'>
      <img src='https://via.placeholder.com/350x350' class='image img-fluid'/>
      <span class='icon'></span>
    </div>
  2. view.frame override func layoutSubviews() { super.layoutSubviews() backgroundColor = calculateColorFromFrame(frame) }

    didSet
  3. 在视图的框架上添加KVO观察器。

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

    override var frame: CGRect {
        didSet {
            backgroundColor = calculateColorFromFrame(frame)
        }
    }
    
  5. 您是否知道其他方式?

这些方法中的哪一种是首选,并且一种方法固有地优于另一种方法?苹果的官方建议是什么?

2 个答案:

答案 0 :(得分:2)

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

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

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

  5. viewDidLayoutSubviews:仅当视图是视图控制器的view属性时才有效,然后view收到layoutSubviews消息时才有效(请参阅#1)。同样重要的是要了解,调用此控件时,已经布置了视图控制器的view及其直接子视图,但尚未 布置更深的子视图。 (有关完整说明,请参见this answer。)

还请注意,视图的frame是根据其layer的某些属性计算得出的:图层的positionbounds.sizeanchorPoint和{{1 }}。如果有任何方法直接设置这些图层属性,则上述方法均无效。

由于所有这些,当视图的位置发生变化时,没有特别的通知方式。技术上正确的方法可能是使用transform。当图层检测到其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 } } 的{​​{1}}实现通常返回UIView。但是在动画块中(包括当界面在纵向和横向之间旋转时),它返回(对于某些键)一个在层上安装actionForLayer:forKey:的动作。因此,请执行nil返回的操作(如果有的话)。

答案 1 :(得分:0)

我可能误解了您的问题或对Apple文档的理解有误,但是我对如何使用框架更改有以下想法:

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

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

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