uiviewkey-value-observing

Observing changes to a UIView's window and superview properties


I'm looking for a way to be notified when a generic UIView is added or removed from the visible view hierarchy. KVO looked like the perfect thing to use in this case, but observing changes to a view's window or superview properties doesn't do anything. Changes to properties like frame, or backgroundColor work as expected but changed to properties relating to the view hierarchy doesn't seem to ever call observeValueForKeyPath.

I checked to see if UIView supports KVO on those properties by calling automaticallyNotifiesObserversForKey, and UIView reported YES for both, leaving me at a loss. So my questions are:

1) Is there a way to use KVO to be notified of events relating to a view being added/removed to the view hierarchy?

2) If not is there another way to be notified of such events that doesn't involve sub-classing UIView?


Solution

  • Here is a way. Is it gross? Yes. Do I recommend such behavior? No. But we're all adults here.

    The gist is that you use method_setImplementation to change the implementation of -[UIView didAddSubview:] so you get notified whenever it's called (and you'd do the same thing for willRemoveSubview:). Unfortunately, you will get called for all view hierarchy changes. You'll have to add your own filtering to find the specific views you're interested in.

    static void InstallAddSubviewListener(void (^listener)(id _self, UIView* subview))
    {
        if ( listener == NULL )
        {
            NSLog(@"listener cannot be NULL.");
            return;
        }
    
        Method addSubviewMethod = class_getInstanceMethod([UIView class], @selector(didAddSubview:));
        IMP originalImp = method_getImplementation(addSubviewMethod);
    
        void (^block)(id, UIView*) = ^(id _self, UIView* subview) {
            originalImp(_self, @selector(didAddSubview:), subview);
            listener(_self, subview);
        };
    
        IMP newImp = imp_implementationWithBlock((__bridge void*)block);
        method_setImplementation(addSubviewMethod, newImp);
    }
    

    To use, do something like:

    InstallAddSubviewListener(^(id _self, UIView *subview) {
        NSLog(@"-[UIView didAddSubview:]   self=%@, view=%@", _self, subview);
    });