假设我有一个 UIView 子类,当它的框架改变时需要更新一些东西。例如。当它在偶数像素上时,它将是红色的,当它在奇数像素上时,它将是蓝色的。
我至少见过四种方法:
-
override func layoutSubviews() { super.layoutSubviews() backgroundColor = calculateColorFromFrame(frame) }
view.frame
didSet
:override var frame: CGRect { didSet { backgroundColor = calculateColorFromFrame(frame) } }
在 View 的框架上添加一个 KVO 观察器。
在 View 的包装 UIViewController 上,实现
viewDidLayoutSubviews()
.override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() view.updateBackgroundColorForNewFrame() }
您还知道其他什么方法吗?
首选这些方法中的哪一种,一种本质上优于另一种? Apple 的官方推荐是什么?
最佳答案
layoutSubviews
:您可以依赖它在大小已更改的 View 上调用,或者如果 View 已收到setNeedsLayout
。如果 View 的位置发生变化但其大小没有变化,系统不会自动对该 View 调用layoutSubviews
。frame didSet
:这适用于使用autoresizingMask
布局的 View 。它不适用于使用法线约束布局的 View 。相反,自动布局设置 View 的center
和bounds
。然而,这些是在不同版本的 UIKit 中可能会发生变化的实现细节。我在 Xcode 10 beta 6 附带的 iOS 12.0 模拟器上进行了测试。frame
上的 KVO:出于与 #2 相同的原因,这在某些情况下不起作用。此外,frame
未记录为符合 KVO,因此允许 UIKit 更改frame
而无需通知观察者。viewDidLayoutSubviews
:只有当 View 是 View Controller 的view
属性时,这才有效,并且仅当view
收到layoutSubviews
消息(参见 #1)。同样重要的是要理解,当它被调用时, View Controller 的view
和它的直接 subview 已经被布局,但是更深的 subview 还没有被布局。 (有关更完整的解释,请参阅 this answer。)
另请注意, View 的frame
是根据其layer
的某些属性计算得出的:层的position
、bounds.size
、anchorPoint
和 transform
。如果直接设置这些图层属性,则上述方法均无效。
由于所有这些原因,当 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/