观察 UIView 的窗口和超级视图属性的变化

发布于 2024-10-31 13:52:42 字数 384 浏览 0 评论 0原文

我正在寻找一种在从可见视图层次结构中添加或删除通用 UIView 时收到通知的方法。 KVO 看起来是在这种情况下使用的完美工具,但观察视图窗口或超级视图属性的更改不会执行任何操作。对frame或backgroundColor等属性的更改按预期工作,但对与视图层次结构相关的属性的更改似乎从未调用observeValueForKeyPath。

我通过调用automaticallyNotifyObserversForKey来检查UIView是否支持这些属性上的KVO,并且UIView对这两个属性都报告为YES,这让我不知所措。所以我的问题是:

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

2)如果没有,是否有另一种方法可以通知此类事件,而不涉及子类化 UIView?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

岛徒 2024-11-07 13:52:42

重写此方法:

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

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

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

Override this method:

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

And you can override these methods in your custom view for other use:

- (void)willMoveToSuperview:(UIView *)newSuperview;
- (void)didMoveToSuperview;
- (void)willMoveToWindow:(UIWindow *)newWindow;
- (void)didMoveToWindow;
心是晴朗的。 2024-11-07 13:52:42

这是一个方法。很恶心吗?是的。我推荐这种行为吗?不,但我们都是成年人了。

要点是,您使用 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);
});

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);
});
后eg是否自 2024-11-07 13:52:42

基于 @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);

based on the code by @doug-richardson, why not something a little cleaner that will allow KVO for the superview property?

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

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

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

Here's my solution, using ideas from above, fixing some errors, and making it extensible. You can use this flat out to monitor the superview changing with KVO of some class and its subclasses, it should be plug and play.

Written in C to be simple to understand and fast. You could modify these to be some slick categories of NSObject.

Example use:

add_superview_kvo(UILabel.class);

Then you would have to add your own observer to instances as per normal use.

// 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];
    });
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文