我怎样才能做关键值观察,并得到一个UIView帧的KVOcallback?
我想观察UIView
frame
, bounds
或center
属性的变化。 我怎样才能使用键值观察来实现呢?
编辑 :我不认为这个解决scheme是足够彻底的。 这个答案是由于历史原因保存的。 看到我最新的答案在这里: https : //stackoverflow.com/a/19687115/202451
你必须在框架属性上做KVO。 在这种情况下,“自我”是一个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]; } } }
KVO不受支持通常有通知或其他可观察的事件。 即使文档显示为“否” ,但观察支持UIView的CALayer也是安全的。 由于其广泛使用KVO和适当的访问器(而不是ivar操作),观察CALayer在实践中的工作。 不保证继续前进。
无论如何,视图的框架只是其他属性的产物。 所以我们需要观察那些:
[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)
注意:请注意,您将收到多个callback之前,它达到其最终值。 例如,更改视图或图层的框架将导致图层更改position
和bounds
( 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"); }];
如果你只想知道什么时候可以改变的话
@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"); }];
目前无法使用KVO来观察视图的框架。 属性必须符合KVO才能被观察到。 不幸的是,UIKit框架的属性通常是不可观察的,就像任何其他系统框架一样。
从文档 :
注意:尽pipeUIKit框架的类通常不支持KVO,但您仍然可以在应用程序的自定义对象(包括自定义视图)中实现它。
这个规则有一些例外,比如NSOperationQueue的operations
属性,但是必须明确地logging下来。
即使在视图的属性上使用KVO目前可能工作,我不会推荐在运输代码中使用它。 这是一个脆弱的方法,依靠无证行为。
如果我可以为对话做出贡献:正如其他人所指出的, frame
本身并不能保证是关键值可观察的, CALayer
属性即使看起来也不是。
你可以做的是创build一个覆盖setFrame:
的自定义UIView
子类setFrame:
并将这个接收通知给一个委托。 设置autoresizingMask
,使视图具有灵活性的一切。 将其configuration为完全透明和小型(以节省CALayer
支持的成本,而不是重要),并将其添加为要观看大小更改的视图的子视图。
当我们第一次指定iOS 5作为代码的API并且因此需要临时模拟viewDidLayoutSubviews
(尽pipe重写layoutSubviews
更合适,但是您明白了) 。
如前所述,如果KVO不起作用,而您只想观察自己的控制权,则可以创build一个覆盖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)); }
有一种方法可以在不使用KVO的情况下实现这一点,为了其他人find这篇文章,我将在这里添加它。
http://www.objc.io/issue-12/animating-custom-layer-properties.html
Nick Lockwood的这个优秀的教程描述了如何使用核心animation定时function来驱动任何东西。 它远远优于使用定时器或CAD显示层,因为您可以使用内置的定时function,或者相当容易地创build您自己的立方贝塞尔函数(请参阅随附的文章( http://www.objc.io/issue-12/)animation解释;.html )。
在frame
等UIKit属性中使用KVO是不安全的。 或者至less这是苹果所说的。
我会build议使用ReactiveCocoa ,这将帮助您在不使用KVO的情况下监听任何属性的更改,使用Signals开始观察某些内容非常容易:
[RACObserve(self, frame) subscribeNext:^(CGRect frame) { //do whatever you want with the new frame }];