说我有一个UIView子类,当它的框架改变时,它需要更新某些东西。例如。当它在偶数像素上时,它将是红色,当它在奇数像素上时将是蓝色。
我已经看到至少四种方法可以做到这一点:
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet"/>
<div class='wrapper'>
<img src='https://via.placeholder.com/350x350' class='image img-fluid'/>
<span class='icon'></span>
</div>
view.frame
override func layoutSubviews() {
super.layoutSubviews()
backgroundColor = calculateColorFromFrame(frame)
}
:
didSet
在视图的框架上添加KVO观察器。
在视图的包装UIViewController上,实现viewDidLayoutSubviews()
。
override var frame: CGRect {
didSet {
backgroundColor = calculateColorFromFrame(frame)
}
}
您是否知道其他方式?
这些方法中的哪一种是首选,并且一种方法固有地优于另一种方法?苹果的官方建议是什么?
答案 0 :(得分:2)
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
及其直接子视图,但尚未 布置更深的子视图。 (有关完整说明,请参见this answer。)
还请注意,视图的frame
是根据其layer
的某些属性计算得出的:图层的position
,bounds.size
,anchorPoint
和{{1 }}。如果有任何方法直接设置这些图层属性,则上述方法均无效。
由于所有这些,当视图的位置发生变化时,没有特别的好通知方式。技术上正确的方法可能是使用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
}
}
的{{1}}实现通常返回UIView
。但是在动画块中(包括当界面在纵向和横向之间旋转时),它返回(对于某些键)一个在层上安装actionForLayer:forKey:
的动作。因此,请执行nil
返回的操作(如果有的话)。
答案 1 :(得分:0)
我可能误解了您的问题或对Apple文档的理解有误,但是我对如何使用框架更改有以下想法:
我很好奇的是,由于帧的位置和大小基于点,并且一个点可以跨越一个以上的像素,因此您将如何有效地计算奇数和偶数像素。
我希望这能回答您的问题。