키 값 관찰을 수행하고 UIView의 프레임에서 KVO 콜백을 받으려면 어떻게해야합니까?
나는 변화를보고 싶어 UIView'의 frame, bounds또는 center속성입니다. 이를 달성하기 위해 키-값 관찰을 어떻게 사용할 수 있습니까?
일반적으로 KVO가 지원되지 않는 알림 또는 기타 관찰 가능한 이벤트가 있습니다. 문서에 'no' 라고되어 있지만 UIView를 지원하는 CALayer를 관찰하는 것이 표면 상 안전합니다. 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");
}];
편집 : 나는이 솔루션이 충분히 철저하다고 생각하지 않습니다. 이 답변은 역사적인 이유로 유지됩니다. 내보기 최신 대답을 여기에 : 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];
}
}
}
현재 KVO를 사용하여 뷰의 프레임을 관찰 할 수 없습니다. 속성이 관찰 가능하려면 KVO를 준수 해야합니다. 안타깝게도 UIKit 프레임 워크의 속성은 다른 시스템 프레임 워크와 마찬가지로 일반적으로 관찰 할 수 없습니다.
로부터 문서 :
참고 : UIKit 프레임 워크의 클래스는 일반적으로 KVO를 지원하지 않지만 사용자 정의보기를 포함하여 애플리케이션의 사용자 정의 객체에서이를 구현할 수 있습니다.
이 규칙에는 NSOperationQueue의 operations속성 과 같은 몇 가지 예외가 있지만 명시 적으로 문서화해야합니다.
보기의 속성에서 KVO를 사용하는 것이 현재 작동 할 수 있지만 배송 코드에서 사용하지 않는 것이 좋습니다. 취약한 접근 방식이며 문서화되지 않은 동작에 의존합니다.
내가 대화에 기여할 수 있다면 : 다른 사람들이 지적했듯이 frame키-값 자체를 관찰 할 수 있다고 보장되지 않으며 CALayer속성이있는 것처럼 보임에도 마찬가지입니다.
대신 할 수있는 일은 영수증을 UIView재정의 setFrame:하고 대리인에게 알리는 사용자 지정 하위 클래스를 만드는 것입니다 . autoresizingMask보기가 모든 것을 유연하게 갖도록 설정하십시오 . 완전히 투명하고 작게 구성하고 ( CALayer많은 문제가 아니라 백업 비용을 절약하기 위해 ) 크기 변경을 보려는보기의 하위보기로 추가합니다.
This worked successfully for me way back under iOS 4 when we were first specifying iOS 5 as the API to code to and, as a result, needed a temporary emulation of viewDidLayoutSubviews (albeit that overriding layoutSubviews was more appropriate, but you get the point).
As mentioned, if KVO doesn't work and you just want to observe your own views which you have control over, you can create a custom view that overrides either setFrame or setBounds. A caveat is that the final, desired frame value may not be available at the point of invocation. Thus I added a GCD call to the next main thread loop to check the value again.
-(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...
}
});
}
There is a way to achieve this without using KVO at all, and for the sake of others finding this post, I'll add it here.
http://www.objc.io/issue-12/animating-custom-layer-properties.html
This excellent tutorial by Nick Lockwood describes how to use core animations timing functions to drive anything. It's far superior to using a timer or CADisplay layer, because you can use the built in timing functions, or fairly easily create your own cubic bezier function (see the accompanying article (http://www.objc.io/issue-12/animations-explained.html) .
To not rely on KVO observing you could perform method swizzling as follows:
@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
Now to observe frame change for a UIView in your application code:
- (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));
}
It's not safe to use KVO in some UIKit properties like frame. Or at least that's what Apple says.
I would recommend using ReactiveCocoa, this will help you listen to changes in any property without using KVO, it's very easy to start observing something using Signals:
[RACObserve(self, frame) subscribeNext:^(CGRect frame) {
//do whatever you want with the new frame
}];
'Program Club' 카테고리의 다른 글
| 어떤 TensorFlow 및 CUDA 버전 조합이 호환 되나요? (0) | 2020.10.16 |
|---|---|
| yywrap에 대한 정의되지 않은 참조 (0) | 2020.10.16 |
| Windows PowerShell : 명령 프롬프트 변경 (0) | 2020.10.16 |
| URL (ASP.NET MVC)에서보기 내 현재 경로 ID를 얻는 방법 (0) | 2020.10.15 |
| Devise의 token_authenticatable은 안전한가요? (0) | 2020.10.15 |