How Reliable Is Kvo with Uikit

How reliable is KVO with UIKit

UIKit is mostly NOT KVO compliant. This is mostly because UIView acts as high-level wrapper for CALayer, so when you eg. change the frame property of an UIView, it will change the layers frame but leave eg. the bounds property of the UIView untouched, so no observer will be triggered for the view.bounds path, because it never really changes. And this leads to non KVO compliance.

Only if the property is marked as KVO compliant can you rely on this, otherwise it will not work or break in some weird cases.

iOS: How do I know if a property is KVO-compliant?

Short answer: No.

Long answer: Nothing in UIKit is guaranteed to be KVO-compliant. If you happen to find that KVO-ing a property works, be grateful, it's unintentional. Also: be wary. It could very well break in the future.

If you find that this is something you need, please file an enhancement request.


About your actual code, it's inherently flawed. Do NOT attempt to add a "rootViewController" setter to UIWindow this way. It will break when you compile your code on iOS 4 but someone runs it on an iOS 5 device. Because you compiled using the 4.x SDK, the #if statements will evaluate to true, meaning your category method smasher will be included in the binary. However, when you run it on an iOS 5 device, you're now going to get a method conflict because two methods on UIWindow will have the same method signature, and there's no guarantee as to which one will be used.

Don't screw with the frameworks like this. If you have to have this, use a subclass. THIS IS WHY SUBCLASSING EXISTS.


Your subclass would look something like this:

@interface CustomWindow : UIWindow

@property (nonatomic, retain) UIViewController *rootViewController;

@end

@implementation CustomWindow : UIWindow

static BOOL UIWindowHasRootViewController = NO;

@dynamic rootViewController;

- (void)_findRootViewControllerMethod {
static dispatch_once_t predicate;
dispatch_once(&predicate, ^{
IMP uiwindowMethod = [UIWindow instanceMethodForSelector:@selector(setRootViewController:)];
IMP customWindowMethod = [CustomWindow instanceMethodForSelector:@selector(setRootViewController:)];
UIWindowHasRootViewController = (uiwindowMethod != NULL && uiwindowMethod != customWindowMethod);
});
}

- (UIViewController *)rootViewController {
[self _findRootViewControllerMethod];
if (UIWindowHasRootViewController) {
// this will be a compile error unless you forward declare the property
// i'll leave as an exercise to the reader ;)
return [super rootViewController];
}
// return the one here on your subclass
}

- (void)setRootViewController:(UIViewController *)rootViewController {
[self _findRootViewControllerMethod];
if (UIWindowHasRootViewController) {
// this will be a compile error unless you forward declare the property
// i'll leave as an exercise to the reader ;)
[super setRootViewController:rootViewController];
} else {
// set the one here on your subclass
}
}

Caveat Implementor: I typed this in a browser window

Does Key Value Observing Work on UITextView's Text Property?

UIKit is not guaranteed to be KVO compliant:

Note: Although the classes of the UIKit framework generally do not support KVO, you can still implement it in the custom objects of your application, including custom views.

It might work on some classes + keys but that’s not reliable, and might change across different iOS versions. See Dave’s answer to this question; Dave works on UIKit.

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

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");
}];

can't use KVO to get new value during UIView animation

You're correct in your conclusion that KVO doesn't work during UIView animations.

This is because your scrollview's actual properties are not changing during the course of the animation: Core Animation is simply animating a bitmap which represents the scrollview moving from its start state to its end state. It's not updating the underlying object's properties as it goes, hence no KVO state changes while the animation is in-flight.

The same is unfortunately true if you attempt to observe contentOffset and inset via UIScrollViewDelegate protocol methods, for the same reasons.

A more in-depth (and fairly impenetrable) explanation can be found in Apple's guide here.

Why isn't UIPopoverController KVO compliant?

Actually, it's not just UIPopoverController. Most of UIKit is not KVO-compliant. Instance variables are directly set often. Unfortunately, there's really nothing you can do about it except file a bug requesting KVO support.

UIView KVO: Why don't changes to center cause KVO notifications for frame?

The main reason observing frame isn't working is because no UIKit property is defined to be KVO-compliant unless explicitly documented to be KVO-compliant.

You're not missing anything - Unfortunately UIKit doesn't support what you're trying to do with the observation.



Related Topics



Leave a reply



Submit