观察对UIView窗口和超视图属性的更改

时间:2011-04-10 15:47:31

标签: uiview key-value-observing

我正在寻找一种在可见视图层次结构中添加或删除通用UIView时收到通知的方法。在这种情况下,KVO看起来像是完美的东西,但观察视图窗口或超视图属性的变化并没有做任何事情。对frame或backgroundColor等属性的更改按预期工作但更改为与视图层次结构相关的属性似乎从未调用observeValueForKeyPath。

我通过调用automaticNotifiesObserversForKey来检查UIView是否支持KVO,并且UIView为两者报告了YES,让我不知所措。所以我的问题是:

1)有没有办法使用KVO通知与视图层次结构中添加/删除视图相关的事件?

2)如果没有,是否有其他方式可以通知不涉及UIView子类的事件?

4 个答案:

答案 0 :(得分:10)

这是一种方式。这是严重的吗?是。我建议这样的行为吗?不,但我们都是成年人。

要点是你使用method_setImplementation来改变 - [UIView didAddSubview:]的实现,这样你就可以在被调用时得到通知(并且你会为willRemoveSubview做同样的事情:)。不幸的是,您将调用所有视图层次结构更改。您必须添加自己的过滤才能找到您感兴趣的特定视图。

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

要使用,请执行以下操作:

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

答案 1 :(得分:9)

覆盖此方法:

- (void)didMoveToSuperview
{
  UIView *superView = [self superview];
}

您可以在自定义视图中覆盖这些方法以供其他用途:

- (void)willMoveToSuperview:(UIView *)newSuperview;
- (void)didMoveToSuperview;
- (void)willMoveToWindow:(UIWindow *)newWindow;
- (void)didMoveToWindow;

答案 2 :(得分:2)

基于@ doug-richardson的代码,为什么不能让KVO成为superview属性的更干净的东西呢?

//Make views announce their change of superviews
    Method method = class_getInstanceMethod([UIView class], @selector(willMoveToSuperview:));
    IMP originalImp = method_getImplementation(method);

    void (^block)(id, UIView*) = ^(id _self, UIView* superview) {
        [_self willChangeValueForKey:@"superview"];
        originalImp(_self, @selector(willMoveToSuperview:), superview);
        [_self didChangeValueForKey:@"superview"];
    };

    IMP newImp = imp_implementationWithBlock((__bridge void*)block);
    method_setImplementation(method, newImp);

答案 3 :(得分:1)

这是我的解决方案,使用上面的想法,修复了一些错误并使其可扩展。您可以使用此平面监视某个类及其子类的KVO随Superview的变化,它应该即插即用。

用C语言编写,易于理解且快速。您可以将它们修改为NSObject的一些漂亮类别。

示例用法:

add_superview_kvo(UILabel.class);

然后,您必须按照常规用法将自己的观察者添加到实例中。

// simple datatype to neatly manage the various runtime elements
typedef struct {
    Class target;
    SEL cmd;
    Method method;
    IMP imp;
} override_t;

// call to initialize an override struct
static override_t
_override_init(Class target, SEL sel) {
    BOOL instance = YES; // should be able to handle class methods by changing this
    override_t o = {
        .target = target,
        .cmd = sel,
        // note this can return a method from the superclass
        .method = instance ? class_getInstanceMethod(target,sel) : class_getClassMethod(target,sel),
        .imp = method_getImplementation(o.method)
    };
    return o;
};

// applies the runtime patch
static void
_override_patch(override_t o, id _Nonnull block) {
    IMP imp = imp_implementationWithBlock(block);
    // first we try to add the method to the class, if we are
    // dealing with an inherited method from a superclass, our
    // new method will drop right in
    if (!class_addMethod(o.target, o.cmd, imp, method_getTypeEncoding(o.method))){
        // this means we got the original method from the
        // class we're manipulating, so we just overwrite
        // its version
        method_setImplementation(o.method,imp);
    }
}

// pass the class in here that you want to monitor for superview changes
// if you pass in UIView.class it will monitor all views... this may
// generate unnecessary overhead, so you can focus it on a class that
// you want (you will get that class and all its subclasses)
void
add_superview_kvo(Class target)
{
    NSString *keyPath = @"superview";

    override_t override = _override_init(target,@selector(willMoveToSuperview:));
    _override_patch(override,^void(id _self, UIView *superview) {
        [_self willChangeValueForKey:keyPath];
        // note that this is the correct way to call an imp, it must be cast
        ((void(*)(id,SEL,id))override.imp)(_self, override.cmd, superview);
    });

    override = _override_init(target,@selector(didMoveToSuperview));
    _override_patch(override,^void(id _self) {
        ((void(*)(id,SEL))override.imp)(_self, override.cmd);
        [_self didChangeValueForKey:keyPath];
    });
}