Подтвердить что ты не робот

Наблюдение за изменениями в окне UIView и свойствами супервизора

Я ищу способ уведомления, когда общий UIView добавляется или удаляется из видимой иерархии. KVO выглядел как идеальная вещь для использования в этом случае, но наблюдение за изменениями в окне просмотра или свойства супервизора ничего не делает. Изменения в свойствах, таких как frame или backgroundColor, работают так, как ожидалось, но изменяются на свойства, относящиеся к иерархии представлений, по-видимому, никогда не называются observValueForKeyPath.

Я проверил, поддерживает ли UIView KVO эти свойства, вызывая автоматическиNotifiesObserversForKey, а UIView сообщает ДА ​​для обоих, оставляя меня в убытке. Поэтому мои вопросы:

1) Есть ли способ использовать KVO для уведомления о событиях, связанных с добавлением/удалением представления в иерархию представления?

2) Если нет, есть ли другой способ получать уведомления о таких событиях, которые не связаны с подклассом UIView?

4b9b3361

Ответ 1

Вот способ. Это брутто? Да. Я рекомендую такое поведение? Нет. Но мы все здесь взрослые.

Суть в том, что вы используете 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);
});

Ответ 2

Отмените этот метод:

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

И вы можете переопределить эти методы в своем пользовательском представлении для другого использования:

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

Ответ 3

на основе кода @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);

Ответ 4

Здесь мое решение, используя идеи сверху, исправляя некоторые ошибки и делая его расширяемым. Вы можете использовать эту плоскость, чтобы отслеживать изменение суперпредставления с 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];
    });
}