ios - layoutSubviews vs frame didSet 检测视图的框架何时改变
问题描述
假设我有一个 UIView 子类,当它的框架发生变化时需要更新一些东西。例如,当它在偶数像素上时,它会是红色的,当它在奇数像素上时会是蓝色的。
我已经看到至少四种方法可以做到这一点:
-
override func layoutSubviews() { super.layoutSubviews() backgroundColor = calculateColorFromFrame(frame) }
view.frame
didSet
:override var frame: CGRect { didSet { backgroundColor = calculateColorFromFrame(frame) } }
在视图的框架上添加一个 KVO 观察者。
在视图的包装 UIViewController 上,实现
viewDidLayoutSubviews()
.override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() view.updateBackgroundColorForNewFrame() }
你知道的还有其他方法吗?
这些方法中的哪一种是首选的,并且一种本质上比另一种更好?苹果官方的推荐是什么?
解决方案
layoutSubviews
:您可以依靠在大小已更改的视图上调用此方法,或者如果视图已收到setNeedsLayout
. 如果视图的位置改变但它的大小没有改变,系统不会自动调用layoutSubviews
那个视图。frame didSet
:这适用于使用autoresizingMask
. 它不适用于使用普通约束布局的视图。相反,自动布局设置视图的center
和bounds
. 然而,这些是在不同版本的 UIKit 中可能发生变化的实现细节。我在 Xcode 10 beta 6 附带的 iOS 12.0 模拟器上进行了测试。KVO on
frame
:这在某些情况下不起作用,原因与 #2 相同。此外,没有记录到与 KVO 兼容,因此 UIKit 可以在不通知观察者的情况下frame
进行更改。frame
viewDidLayoutSubviews
:只有当视图是view
视图控制器的属性时,这才有效,并且只有当view
接收到layoutSubviews
消息时(参见#1)。同样重要的是要理解,当调用它时,视图控制器view
及其直接子视图已经布局,但更深的子视图尚未布局。(有关更全面的解释,请参阅此答案。)
另请注意,视图frame
是根据其 的某些属性计算得出的layer
:图层的position
、bounds.size
、anchorPoint
和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
}
}
执行正常返回UIView
。但是在动画块内(包括当界面在纵向和横向之间旋转时),它返回(对于某些键)一个在层上安装 a 的动作。因此,如果有一个返回的动作,运行它是很重要的。actionForLayer:forKey:
nil
CAAnimation
super
推荐阅读
- flutter - 如何在列表中进行复选框选择、取消选择和全部选择?
- java - 带宽共享(master => workers)
- cookies - Outlook Web 插件 - 关闭任务窗格时清除 cookie win 10
- excel - 使用索引将所有在 col A 中有数量的行从所有工作表复制到 1 个工作表上
- c++ - 使用 powershell 我从 c++ 发送电子邮件并传递参数,如果第一个参数包含“测试邮件”,那么 powershell 将其作为 2 个参数
- docker - Docker-compose jupyterhub 问题
- javascript - redux-form 如何显示异步验证成功
- sql - 将日期 yyyy-mm-dd 转换为 mmm-yy SQL Server 2016
- batch-file - 将最后一个下划线字符之后的所有内容批量替换到文件扩展名的开头
- python - 服务器响应后,如何循环遍历类中的所有函数?