如何进行键值观察并在 UIView 的框架上获取 KVO 回调?
我想观察 UIView
的 frame
、bounds
或 center
属性的变化。如何使用键值观察来实现这一目标?
I want to watch for changes in a UIView
's frame
, bounds
or center
property. How can I use Key-Value Observing to achieve this?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
通常存在不支持 KVO 的通知或其他可观察事件。尽管文档说“不”,但观察支持 UIView 的 CALayer 表面上是安全的。观察 CALayer 在实践中的工作原理,因为它广泛使用了 KVO 和适当的访问器(而不是 ivar 操作)。不能保证它能够继续工作。
不管怎样,视图的框架只是其他属性的产物。因此我们需要观察这些:
请参阅此处的完整示例
https://gist.github.com/hfossli/7234623
注意:这并不是说文档中支持,但从今天开始,它适用于迄今为止的所有 iOS 版本(当前为 iOS 2 -> iOS 11)
注意:请注意,在确定最终值之前,您将收到多个回调。例如,更改视图或图层的框架将导致图层更改
位置
和边界
(按顺序)。使用ReactiveCocoa,你可以做
如果你只想知道边界何时改变,你可以做
如果你只想知道框架何时改变,你可以做
There are usually notifications or other observable events where KVO isn't supported. Even though the docs says 'no', it is ostensibly safe to observe the CALayer backing the UIView. Observing the CALayer works in practice because of its extensive use of KVO and proper accessors (instead of ivar manipulation). It's not guaranteed to work going forward.
Anyway, the view's frame is just the product of other properties. Therefore we need to observe those:
See full example here
https://gist.github.com/hfossli/7234623
NOTE: This is not said to be supported in the docs, but it works as of today with all iOS versions this far (currently iOS 2 -> iOS 11)
NOTE: Be aware that you will receive multiple callbacks before it settles at its final value. For example changing the frame of a view or layer will cause the layer to change
position
andbounds
(in that order).With ReactiveCocoa you can do
And if you only want to know when
bounds
changes you can doAnd if you only want to know when
frame
changes you can do编辑:我认为这个解决方案不够彻底。由于历史原因保留此答案。请在此处查看我的最新答案:https://stackoverflow.com/a/19687115/202451
你必须对框架属性执行 KVO。在这种情况下,“self”是一个 UIViewController。
添加观察者(通常在 viewDidLoad 中完成):
删除观察者(通常在 dealloc 或 viewDidDisappear 中完成:):
获取有关更改的信息
EDIT: I don't think this solution is thorough enough. This answer is kept for historical reasons. See my newest answer here: https://stackoverflow.com/a/19687115/202451
You've got to do KVO on the frame-property. "self" is in thise case a UIViewController.
adding the observer (typically done in viewDidLoad):
removing the observer (typically done in dealloc or viewDidDisappear:):
Getting information about the change
目前无法使用 KVO 来观察视图的框架。属性必须符合 KVO 才能被观察。遗憾的是,与任何其他系统框架一样,UIKit 框架的属性通常是不可观察的。
来自 文档:
此规则有一些例外,例如 NSOperationQueue 的
operations
属性,但必须明确记录它们。即使在视图的属性上使用 KVO 当前可能有效,我也不建议在发布代码中使用它。这是一种脆弱的方法,并且依赖于未记录的行为。
Currently it's not possible to use KVO to observe a view's frame. Properties have to be KVO compliant to be observable. Sadly, properties of the UIKit framework are generally not observable, as with any other system framework.
From the documentation:
There are a few exceptions to this rule, like NSOperationQueue's
operations
property but they have to be explicitly documented.Even if using KVO on a view's properties might currently work I would not recommend to use it in shipping code. It's a fragile approach and relies on undocumented behavior.
如果我可以对对话做出贡献:正如其他人指出的那样,
frame
本身并不保证是可观察的键值,CALayer
属性也不是,即使它们看起来是是。您可以做的是创建一个自定义
UIView
子类,该子类覆盖setFrame:
并向委托宣告该收据。设置 autoresizingMask 以便视图具有灵活的一切。将其配置为完全透明且较小(以节省 CALayer 支持的成本,但这并不重要)并将其添加为您想要观看大小变化的视图的子视图。早在 iOS 4 下,当我们第一次指定 iOS 5 作为编码的 API 时,这对我来说就成功了,因此,需要临时模拟
viewDidLayoutSubviews
(尽管重写了layoutSubviews
更合适,但你明白了)。If I might contribute to the conversation: as others have pointed out,
frame
is not guaranteed to be key-value observable itself and neither are theCALayer
properties even though they appear to be.What you can do instead is create a custom
UIView
subclass that overridessetFrame:
and announces that receipt to a delegate. Set theautoresizingMask
so that the view has flexible everything. Configure it to be entirely transparent and small (to save costs on theCALayer
backing, not that it matters a lot) and add it as a subview of the view you want to watch size changes on.This worked successfully for me way back under iOS 4 when we were first specifying iOS 5 as the API to code to and, as a result, needed a temporary emulation of
viewDidLayoutSubviews
(albeit that overridinglayoutSubviews
was more appropriate, but you get the point).如前所述,如果 KVO 不起作用并且您只想观察自己可以控制的视图,则可以创建一个覆盖 setFrame 或 setBounds 的自定义视图。需要注意的是,最终所需的帧值在调用时可能不可用。因此,我向下一个主线程循环添加了 GCD 调用,以再次检查该值。
As mentioned, if KVO doesn't work and you just want to observe your own views which you have control over, you can create a custom view that overrides either setFrame or setBounds. A caveat is that the final, desired frame value may not be available at the point of invocation. Thus I added a GCD call to the next main thread loop to check the value again.
为了不依赖 KVO 观察,您可以执行方法调配,如下所示:
现在观察应用程序代码中 UIView 的帧更改:
To not rely on KVO observing you could perform method swizzling as follows:
Now to observe frame change for a UIView in your application code:
更新了 RxSwift 和 Swift 5 的 @hfossli 答案。
使用 RxSwift,您可以做到
如果您只想知道边界何时发生变化,您可以做到
如果您只想知道框架何时发生变化,您可以做到
Updated @hfossli answer for RxSwift and Swift 5.
With RxSwift you can do
And if you only want to know when
bounds
changes you can doAnd if you only want to know when
frame
changes you can do有一种方法可以在完全不使用 KVO 的情况下实现此目的,并且为了其他人找到这篇文章,我将其添加到此处。
http://www.objc.io/issue-12/animating -custom-layer-properties.html
Nick Lockwood 的这篇优秀教程描述了如何使用核心动画计时函数来驱动任何东西。它远远优于使用计时器或 CADisplay 层,因为您可以使用内置的计时函数,或者相当轻松地创建您自己的三次贝塞尔函数(请参阅随附的文章 (http://www.objc.io/issue-12/animations-explained.html) 。
There is a way to achieve this without using KVO at all, and for the sake of others finding this post, I'll add it here.
http://www.objc.io/issue-12/animating-custom-layer-properties.html
This excellent tutorial by Nick Lockwood describes how to use core animations timing functions to drive anything. It's far superior to using a timer or CADisplay layer, because you can use the built in timing functions, or fairly easily create your own cubic bezier function (see the accompanying article (http://www.objc.io/issue-12/animations-explained.html) .
在某些 UIKit 属性(例如
frame
)中使用 KVO 是不安全的。或者至少苹果是这么说的。我建议使用 ReactiveCocoa,这将帮助你在不使用 KVO 的情况下监听任何属性的变化,这非常简单使用信号开始观察某些事物:
It's not safe to use KVO in some UIKit properties like
frame
. Or at least that's what Apple says.I would recommend using ReactiveCocoa, this will help you listen to changes in any property without using KVO, it's very easy to start observing something using Signals: