ios - layoutSubviews vs frame didSet 检测 View 的框架何时改变

标签 ios swift uiview

假设我有一个 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. 在 View 的框架上添加一个 KVO 观察器。

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

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

首选这些方法中的哪一种,一种本质上优于另一种? Apple 的官方推荐是什么?

最佳答案

  1. layoutSubviews:您可以依赖它在大小已更改的 View 上调用,或者如果 View 已收到 setNeedsLayout。如果 View 的位置发生变化但其大小没有变化,系统不会自动对该 View 调用 layoutSubviews

  2. frame didSet:这适用于使用 autoresizingMask 布局的 View 。它不适用于使用法线约束布局的 View 。相反,自动布局设置 View 的 centerbounds。然而,这些是在不同版本的 UIKit 中可能会发生变化的实现细节。我在 Xcode 10 beta 6 附带的 iOS 12.0 模拟器上进行了测试。

  3. frame 上的 KVO:出于与 #2 相同的原因,这在某些情况下不起作用。此外,frame 未记录为符合 KVO,因此允许 UIKit 更改 frame 而无需通知观察者。

  4. viewDidLayoutSubviews:只有当 View 是 View Controller 的 view 属性时,这才有效,并且仅当 view 收到 layoutSubviews 消息(参见 #1)。同样重要的是要理解,当它被调用时, View Controller 的 view 和它的直接 subview 已经被布局,但是更深的 subview 还没有被布局。 (有关更完整的解释,请参阅 this answer。)

另请注意, View 的frame 是根据其layer 的某些属性计算得出的:层的positionbounds.sizeanchorPointtransform。如果直接设置这些图层属性,则上述方法均无效。

由于所有这些原因,当 View 的位置发生变化时,没有特别的方式来通知。技术上正确的方法可能是使用 CAAction。当层检测到它的 position 正在改变时,它会请求它的委托(delegate)(对于 View 的层,它是 View 本身)运行一个 Action 。它通过发送 actionForLayer:forKey: 消息来询问。因此,您可以覆盖 View 子类中的 action(forLayer:forKey:) 以获得可靠的通知。但是,该层在before 更改其position(并因此更改其frame)时要求执行操作。它在 更改其位置 后运行操作。所以你必须跳一段这样的小舞:

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。但在动画 block 内(包括当界面在纵向和横向之间旋转时),它返回(对于某些键)在层上安装 CAAnimation 的操作。因此,如果有的话,运行 super 返回的操作很重要。

关于ios - layoutSubviews vs frame didSet 检测 View 的框架何时改变,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52191384/

相关文章:

ios - 隐藏 Nib 内容直到任务完成

ios - swift NSKeyedArchiver : Do custom members of an NSCoding-conformant class need to conform to NSCoding as well?

iOS - 仅将 UIView 的一部分保存为 PNG

ios - 在没有自动布局的情况下增加 UIView 的高度

ios - MagicalRecord NSFetchesResultsController 部分分组和排序 + 部分内也是如此

ios - ld : framework not found Parse Xcode 7 beta

ios - CoreData 类在单元测试中未匹配

ios - 将图像上传到 S3 无法完成上传

ios - 错误 : Instance method 'onReceive(_:perform:)' requires that 'Int' conform to 'Publisher'

ios - UIStackView 内部的 UIView 动态高度