Наблюдение за изменениями в окне UIView и свойствами супервизора
Я ищу способ уведомления, когда общий UIView добавляется или удаляется из видимой иерархии. KVO выглядел как идеальная вещь для использования в этом случае, но наблюдение за изменениями в окне просмотра или свойства супервизора ничего не делает. Изменения в свойствах, таких как frame или backgroundColor, работают так, как ожидалось, но изменяются на свойства, относящиеся к иерархии представлений, по-видимому, никогда не называются observValueForKeyPath.
Я проверил, поддерживает ли UIView KVO эти свойства, вызывая автоматическиNotifiesObserversForKey, а UIView сообщает ДА для обоих, оставляя меня в убытке. Поэтому мои вопросы:
1) Есть ли способ использовать KVO для уведомления о событиях, связанных с добавлением/удалением представления в иерархию представления?
2) Если нет, есть ли другой способ получать уведомления о таких событиях, которые не связаны с подклассом UIView?
Ответы
Ответ 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];
});
}