代码之家  ›  专栏  ›  技术社区  ›  Senseful

layoutSubviews与frame设置为检测视图的帧何时更改

  •  0
  • Senseful  · 技术社区  · 6 年前

    假设我有一个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. 你知道其他的方法吗?

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

    2 回复  |  直到 6 年前
        1
  •  2
  •   rob mayoff    6 年前
    1. layoutSubviews :您可以将此调用依赖于大小已更改的视图,或者如果该视图已接收到 setNeedsLayout . 如果视图的位置发生变化,但其大小不变,则系统不会自动调用 布局子视图 在那个视图上。

    2. frame didSet :这适用于使用 autoresizingMask . 它不适用于使用普通约束布局的视图。相反,自动布局设置视图的 center bounds . 但是,这些是实现细节,可能在不同版本的uikit中发生更改。我在Xcode10测试版6附带的iOS12.0模拟器上进行了测试。

    3. KVon frame :这在某些情况下不会起作用,原因与2相同。也, 框架 未记录为符合kvo,因此允许更改uikit 框架 不通知观察员。

    4. viewDidLayoutSubviews :只有当视图是 view 视图控制器的属性,然后仅当 看法 接收 布局子视图 信息(见1)。另外,重要的是要理解,当调用这个函数时,视图控制器的 看法 它的直接的子视图已经布置好了,但更深层的子视图 已经布置好了。(见 this answer 为了更全面的解释。)

    还要注意,视图 框架 是根据它的一些属性 layer :层的 position , bounds.size , anchorPoint transform . 如果有任何东西直接设置这些层属性,那么上面的方法都不起作用。

    因为这些,没有什么特别的 伟大的 视图位置更改时的通知方式。技术上正确的方法可能是使用 CAAction . 当层检测到 位置 正在更改,它要求其委托(视图本身,视图的层)运行操作。它通过发送 actionForLayer:forKey: 消息。以便您可以覆盖 action(forLayer:forKey:) 在视图中,子类将得到可靠的通知。但是,层请求操作 之前 改变它的 位置 (因此 框架 )它执行动作 之后 改变它的 位置 . 所以你要跳这样的小舞蹈:

    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
        }
    }
    

    这个 UIView 实施 层操作:分叉: 正常返回 nil . 但在动画块内(包括当界面在纵向和横向之间旋转时),它返回(对于某些键)安装 CAAnimation 在层上。所以运行返回的操作很重要 super 如果有的话。

        2
  •  0
  •   MacUserT    6 年前

    我可能理解您的问题错误或误解了Apple文档,但下面是我对如何使用查看帧更改的想法:

    1. 不应直接调用View.LayoutSubviews()。只有当子视图的自动调整和基于约束的行为不提供所需的行为时,才能由uiview的子类重写layoutSubviews方法。您可以使用实现直接设置子视图的框架矩形”。-从苹果文档中复制。因此,我不会使用此方法,因为它不适用于要更改颜色的视图,而是适用于视图的子视图。
    2. 我认为这是一种非常优雅的使用方法,因为如果移动视图,框架将被设置为新值。但是,当帧更改大小时,也会调用didset,并且帧可能不会移动。如果您的CalculateColorFromFrame(Frame)是一种昂贵的方法,并且视图可以更改大小,这可能会导致一些性能损失。
    3. 我认为这是一个相当繁琐的方法来实现同样的第二点。唯一的问题是,现在您可以使用另一个类来观察视图框架中的更改,并调用CalculateColorFromFrame(框架)方法。
    4. 在各自的视图控制器中,viewDidLayoutSubviews()也是一种很好的方法,但是在这里,当视图控制器的主视图中的某些内容发生更改,并且视图必须再次布局其子视图时,调用此方法。这也意味着当视图没有移动或更改,并且CalculateColorFromFrame(Frame)没有更改时,可以调用此方法。因此,这可能会多次调用此方法。

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

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