[英]layoutSubviews vs frame didSet to detect when the view's frame changed
假设我有一个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 Simulator上进行了测试。
frame
KVO:出于某些原因,该功能在某些情况下与#2相同。 另外, frame
没有文档证明是KVO兼容的,因此允许UIKit在不通知观察者的情况下更改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
}
}
actionForLayer:forKey:
的UIView
实现通常返回nil
。 但是在动画块内(包括当界面在纵向和横向之间旋转时),它返回(对于某些键)一个将CAAnimation
安装在图层上的动作。 因此,如果有的话,运行super
返回的动作很重要。
我可能对您的问题理解不正确或对Apple文档的理解有误,但以下是我对使用什么来查看镜架变化的看法:
我很好奇的是,您如何有效地计算奇数和偶数像素,因为帧位置和大小基于点,并且一个点可以跨越一个以上的像素。
我希望这能回答您的问题。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.