Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I do Key Value Observing and get a KVO callback on a UIView's frame?

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?

like image 681
hfossli Avatar asked Feb 02 '11 12:02

hfossli


People also ask

What is key-value observing?

Overview. Key-value observing is a Cocoa programming pattern you use to notify objects about changes to properties of other objects. It's useful for communicating changes between logically separated parts of your app—such as between models and views.

What is Key-Value Coding and Key-Value observing?

KVO and KVC or Key-Value Observing and Key-Value Coding are mechanisms originally built and provided by Objective-C that allows us to locate and interact with the underlying properties of a class that inherits NSObject at runtime.

What is KVO Swift?

Key-value observing is a mechanism that enables an object to be notified directly when a property of another object changes. It is a mode of communication between objects in applications designed in conformance with the Model-View-Controller design pattern.


1 Answers

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:

[self.view addObserver:self forKeyPath:@"frame" options:0 context:NULL]; [self.view.layer addObserver:self forKeyPath:@"bounds" options:0 context:NULL]; [self.view.layer addObserver:self forKeyPath:@"transform" options:0 context:NULL]; [self.view.layer addObserver:self forKeyPath:@"position" options:0 context:NULL]; [self.view.layer addObserver:self forKeyPath:@"zPosition" options:0 context:NULL]; [self.view.layer addObserver:self forKeyPath:@"anchorPoint" options:0 context:NULL]; [self.view.layer addObserver:self forKeyPath:@"anchorPointZ" options:0 context:NULL]; [self.view.layer addObserver:self forKeyPath:@"frame" options:0 context:NULL]; 

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 and bounds (in that order).


With ReactiveCocoa you can do

RACSignal *signal = [RACSignal merge:@[   RACObserve(view, frame),   RACObserve(view, layer.bounds),   RACObserve(view, layer.transform),   RACObserve(view, layer.position),   RACObserve(view, layer.zPosition),   RACObserve(view, layer.anchorPoint),   RACObserve(view, layer.anchorPointZ),   RACObserve(view, layer.frame),   ]];  [signal subscribeNext:^(id x) {     NSLog(@"View probably changed its geometry"); }]; 

And if you only want to know when bounds changes you can do

@weakify(view); RACSignal *boundsChanged = [[signal map:^id(id value) {     @strongify(view);     return [NSValue valueWithCGRect:view.bounds]; }] distinctUntilChanged];  [boundsChanged subscribeNext:^(id ignore) {     NSLog(@"View bounds changed its geometry"); }]; 

And if you only want to know when frame changes you can do

@weakify(view); RACSignal *frameChanged = [[signal map:^id(id value) {     @strongify(view);     return [NSValue valueWithCGRect:view.frame]; }] distinctUntilChanged];  [frameChanged subscribeNext:^(id ignore) {     NSLog(@"View frame changed its geometry"); }]; 
like image 75
hfossli Avatar answered Sep 30 '22 08:09

hfossli