Я хочу , щоб стежити за змінами в UIView
російській frame
, bounds
або center
власності. Як я можу використовувати спостереження за ключовими значеннями для досягнення цього?
Я хочу , щоб стежити за змінами в UIView
російській frame
, bounds
або center
власності. Як я можу використовувати спостереження за ключовими значеннями для досягнення цього?
Відповіді:
Зазвичай є повідомлення або інші видимі події, коли KVO не підтримується. Незважаючи на те, що в документації сказано "ні" , нібито безпечно спостерігати, як CALayer підтримує UIView. Спостереження за CALayer працює на практиці завдяки широкому використанню KVO та належних аксесуарів (замість маніпуляцій з ivar). Це не гарантовано працюватиме вперед.
У будь-якому випадку, кадр виду - це лише продукт інших властивостей. Тому нам потрібно дотримуватися таких:
[self.view addObserver:self forKeyPath:@"frame" options:0 context:NULL];
[self.view.layer addObserver:self forKeyPath:@"bounds" options:0 context:NULL];
[self.view.layer addObserver:self forKeyPath:@"transform" options:0 context:NULL];
[self.view.layer addObserver:self forKeyPath:@"position" options:0 context:NULL];
[self.view.layer addObserver:self forKeyPath:@"zPosition" options:0 context:NULL];
[self.view.layer addObserver:self forKeyPath:@"anchorPoint" options:0 context:NULL];
[self.view.layer addObserver:self forKeyPath:@"anchorPointZ" options:0 context:NULL];
[self.view.layer addObserver:self forKeyPath:@"frame" options:0 context:NULL];
Повний приклад дивіться тут https://gist.github.com/hfossli/7234623
ПРИМІТКА. Зазначено, що це не підтримується в документації, але на сьогоднішній день воно працює з усіма версіями iOS на даний момент (в даний час iOS 2 -> iOS 11)
ПРИМІТКА. Майте на увазі, що ви отримаєте декілька зворотних дзвінків, перш ніж вони встановлять остаточне значення. Наприклад, зміна кадру подання або шару призведе до зміни шаруposition
та bounds
(у такому порядку).
З ReactiveCocoa ви можете це зробити
RACSignal *signal = [RACSignal merge:@[
RACObserve(view, frame),
RACObserve(view, layer.bounds),
RACObserve(view, layer.transform),
RACObserve(view, layer.position),
RACObserve(view, layer.zPosition),
RACObserve(view, layer.anchorPoint),
RACObserve(view, layer.anchorPointZ),
RACObserve(view, layer.frame),
]];
[signal subscribeNext:^(id x) {
NSLog(@"View probably changed its geometry");
}];
І якщо ви хочете знати лише коли bounds
ви можете внести зміни
@weakify(view);
RACSignal *boundsChanged = [[signal map:^id(id value) {
@strongify(view);
return [NSValue valueWithCGRect:view.bounds];
}] distinctUntilChanged];
[boundsChanged subscribeNext:^(id ignore) {
NSLog(@"View bounds changed its geometry");
}];
І якщо ви хочете знати лише, коли frame
ви можете внести зміни
@weakify(view);
RACSignal *frameChanged = [[signal map:^id(id value) {
@strongify(view);
return [NSValue valueWithCGRect:view.frame];
}] distinctUntilChanged];
[frameChanged subscribeNext:^(id ignore) {
NSLog(@"View frame changed its geometry");
}];
EDIT : Я не думаю, що це рішення є достатньо ґрунтовним. Ця відповідь зберігається з історичних причин. Дивіться мою найновішу відповідь тут: https://stackoverflow.com/a/19687115/202451
Вам потрібно зробити KVO на властивості фрейма. "self" - у цьому випадку UIViewController.
додавання спостерігача (зазвичай це робиться у viewDidLoad):
[self addObserver:self forKeyPath:@"view.frame" options:NSKeyValueObservingOptionOld context:NULL];
видалення спостерігача (зазвичай це робиться у dealloc або viewDidDisappear :):
[self removeObserver:self forKeyPath:@"view.frame"];
Отримання інформації про зміни
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if([keyPath isEqualToString:@"view.frame"]) {
CGRect oldFrame = CGRectNull;
CGRect newFrame = CGRectNull;
if([change objectForKey:@"old"] != [NSNull null]) {
oldFrame = [[change objectForKey:@"old"] CGRectValue];
}
if([object valueForKeyPath:keyPath] != [NSNull null]) {
newFrame = [[object valueForKeyPath:keyPath] CGRectValue];
}
}
}
UIViewController
заявляє, view
ні UIView
заявляє, frame
що відповідає ключам KVO. Какао та какао-дотик не дозволяють довільно спостерігати за клавішами. Усі спостережувані ключі повинні бути належним чином задокументовані. Той факт, що це, здається, працює, не вважає цей дійсний (безпечний для виробництва) спосіб спостереження за змінами кадрів у поданні.
В даний час неможливо використовувати KVO для спостереження за рамкою вигляду. Властивості повинні відповідати KVO, щоб їх можна було спостерігати. На жаль, властивості фреймворку UIKit, як правило, не можна спостерігати, як і будь-який інший фреймворк.
З документації :
Примітка: Хоча класи фреймворку UIKit, як правило, не підтримують KVO, ви все одно можете реалізувати його у спеціальних об'єктах вашої програми, включаючи власні подання.
Є кілька винятків із цього правила, наприклад властивість NSOperationQueue, operations
але вони повинні бути чітко задокументовані.
Навіть якщо використання KVO у властивостях представлення в даний час може працювати, я б не рекомендував використовувати його в коді доставки. Це тендітний підхід і спирається на поведінку без документів.
UIView
щоб вони могли використовувати будь-який механізм, який вважають за потрібне.
Якби я міг взяти участь у розмові: як зазначали інші, frame
не гарантується, що сам по собі можна буде спостерігати ключ-значення, як і CALayer
властивості, навіть якщо вони видаються.
Натомість ви можете зробити власний UIView
підклас, який замінює setFrame:
та повідомляє про отримання делегату. Встановіть autoresizingMask
так, щоб у поданні було все гнучко. Налаштуйте його повністю прозорим і невеликим (щоб заощадити витрати наCALayer
підтримку, не те що це має велике значення) і додайте його як підпрогляд подання, на якому ви хочете спостерігати за змінами розміру.
Це успішно спрацювало для мене ще під iOS 4, коли ми вперше вказували iOS 5 як API для кодування, і, як результат, потрібна була тимчасова емуляція viewDidLayoutSubviews
(хоча, перевизначення layoutSubviews
було більш доречним, але ви розумієте).
transform
тощо
Як вже зазначалося, якщо KVO не працює, і ви просто хочете спостерігати за власними поданнями, над якими ви маєте контроль, ви можете створити власний вигляд, який замінює setFrame або setBounds. Застереження полягає в тому, що остаточне, бажане значення кадру може бути недоступним у точці виклику. Таким чином, я додав виклик GCD до наступного циклу основного потоку, щоб перевірити значення ще раз.
-(void)setFrame:(CGRect)frame
{
NSLog(@"setFrame: %@", NSStringFromCGRect(frame));
[super setFrame:frame];
// final value is available in the next main thread cycle
__weak PositionLabel *ws = self;
dispatch_async(dispatch_get_main_queue(), ^(void) {
if (ws && ws.superview)
{
NSLog(@"setFrame2: %@", NSStringFromCGRect(ws.frame));
// do whatever you need to...
}
});
}
Щоб не покладатися на спостереження KVO, ви можете виконати зміни методів наступним чином:
@interface UIView(SetFrameNotification)
extern NSString * const UIViewDidChangeFrameNotification;
@end
@implementation UIView(SetFrameNotification)
#pragma mark - Method swizzling setFrame
static IMP originalSetFrameImp = NULL;
NSString * const UIViewDidChangeFrameNotification = @"UIViewDidChangeFrameNotification";
static void __UIViewSetFrame(id self, SEL _cmd, CGRect frame) {
((void(*)(id,SEL, CGRect))originalSetFrameImp)(self, _cmd, frame);
[[NSNotificationCenter defaultCenter] postNotificationName:UIViewDidChangeFrameNotification object:self];
}
+ (void)load {
[self swizzleSetFrameMethod];
}
+ (void)swizzleSetFrameMethod {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
IMP swizzleImp = (IMP)__UIViewSetFrame;
Method method = class_getInstanceMethod([UIView class],
@selector(setFrame:));
originalSetFrameImp = method_setImplementation(method, swizzleImp);
});
}
@end
Тепер, щоб спостерігати за зміною кадру для UIView в коді вашої програми:
- (void)observeFrameChangeForView:(UIView *)view {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(viewDidChangeFrameNotification:) name:UIViewDidChangeFrameNotification object:view];
}
- (void)viewDidChangeFrameNotification:(NSNotification *)notification {
UIView *v = (UIView *)notification.object;
NSLog(@"View '%@' did change frame to %@", v, NSStringFromCGRect(v.frame));
}
Оновлена відповідь @hfossli для RxSwift та Swift 5 .
За допомогою RxSwift ви можете це зробити
Observable.of(rx.observe(CGRect.self, #keyPath(UIView.frame)),
rx.observe(CGRect.self, #keyPath(UIView.layer.bounds)),
rx.observe(CGRect.self, #keyPath(UIView.layer.transform)),
rx.observe(CGRect.self, #keyPath(UIView.layer.position)),
rx.observe(CGRect.self, #keyPath(UIView.layer.zPosition)),
rx.observe(CGRect.self, #keyPath(UIView.layer.anchorPoint)),
rx.observe(CGRect.self, #keyPath(UIView.layer.anchorPointZ)),
rx.observe(CGRect.self, #keyPath(UIView.layer.frame))
).merge().subscribe(onNext: { _ in
print("View probably changed its geometry")
}).disposed(by: rx.disposeBag)
І якщо ви хочете знати лише, коли bounds
ви можете внести зміни
Observable.of(rx.observe(CGRect.self, #keyPath(UIView.layer.bounds))).subscribe(onNext: { _ in
print("View bounds changed its geometry")
}).disposed(by: rx.disposeBag)
І якщо ви хочете знати лише, коли frame
ви можете внести зміни
Observable.of(rx.observe(CGRect.self, #keyPath(UIView.layer.frame)),
rx.observe(CGRect.self, #keyPath(UIView.frame))).merge().subscribe(onNext: { _ in
print("View frame changed its geometry")
}).disposed(by: rx.disposeBag)
Є спосіб досягти цього, не використовуючи KVO взагалі, і заради того, щоб інші знайшли цю публікацію, я додаю її тут.
http://www.objc.io/issue-12/animating-custom-layer-properties.html
Цей чудовий підручник Ніка Локвуда описує, як використовувати основні функції синхронізації анімації для керування чим-небудь. Це набагато перевершує використання таймера або шару CADisplay, оскільки ви можете використовувати вбудовані функції синхронізації або досить легко створити власну кубічну функцію безьє (див. Супровідну статтю ( http://www.objc.io/issue-12/ анімації-пояснено.html ).
Небезпечно використовувати KVO у деяких властивостях UIKit, таких як frame
. Або принаймні так говорить Apple.
Я б порекомендував використовувати ReactiveCocoa , це допоможе вам прослухати зміни в будь-якому властивості, не використовуючи KVO, дуже легко почати спостерігати щось за допомогою сигналів:
[RACObserve(self, frame) subscribeNext:^(CGRect frame) {
//do whatever you want with the new frame
}];