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
Present Uialertcontroller from Appdelegate
Search Bar and Search Display Controller in Table View
Nsgenericexception Reason Collection <Nsconcretemaptable: Xxx>
Replace a Particular Color Inside an Image with Another Color
Autolayout Views Make App Crash on Popviewcontroller
How Can Split from String to Array by Chunks of Given Size
iOS Web Page Errors Over Cellular Data But Not Over Wifi? Recent Change to At&T Cellular Network
Use Different Googleservice-Info.Plist for Different Build Schemes
Xcode: Could Not Locate Device Support Files
How to Obtain Certificate Signing Request
Could Not Cast Value of Type 'Uitableviewcell' to '(Appname).(Customcellname)'
Codesign Returned Unknown Error -1=Ffffffffffffffff
How to Get Notified in Contact Changed Event in iOS
How to Decode Aac Audio Buffer to Pcm Buffer in iOS
What Do the "M" and "A" Icons in the Project Navigator of Xcode 4 Mean When I Create a New Project
Constant Movement in Spritekit