나는 변화를보고 싶어 UIView
'의 frame
, bounds
또는 center
속성입니다. 이를 달성하기 위해 키-값 관찰을 어떻게 사용할 수 있습니까?
나는 변화를보고 싶어 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];
}
}
}
UIViewController
선언되지 view
도 않는 UIView
선언을 frame
KVO 준수 키가 될 수 있습니다. Cocoa 및 Cocoa-touch는 임의의 키 관찰을 허용하지 않습니다. 모든 관찰 가능한 키는 적절하게 문서화되어야합니다. 작동하는 것처럼 보인다는 사실은 뷰에서 프레임 변경을 관찰하는 유효한 (생산 안전) 방법을 제공하지 않습니다.
현재 KVO를 사용하여 뷰의 프레임을 관찰 할 수 없습니다. 속성이 관찰 가능하려면 KVO를 준수 해야합니다. 안타깝게도 UIKit 프레임 워크의 속성은 다른 시스템 프레임 워크와 마찬가지로 일반적으로 관찰 할 수 없습니다.
로부터 문서 :
참고 : UIKit 프레임 워크의 클래스는 일반적으로 KVO를 지원하지 않지만 사용자 정의보기를 포함하여 애플리케이션의 사용자 정의 객체에서이를 구현할 수 있습니다.
이 규칙에는 NSOperationQueue의 operations
속성 과 같은 몇 가지 예외가 있지만 명시 적으로 문서화해야합니다.
보기의 속성에서 KVO를 사용하는 것이 현재 작동 할 수 있지만 배송 코드에서 사용하지 않는 것이 좋습니다. 취약한 접근 방식이며 문서화되지 않은 동작에 의존합니다.
UIView
하므로 적합하다고 생각되는 모든 메커니즘을 사용할 수 있습니다.
내가 대화에 기여할 수 있다면 : 다른 사람들이 지적했듯이 frame
키-값 자체를 관찰 할 수 있다고 보장되지 않으며 CALayer
속성이있는 것처럼 보임에도 마찬가지입니다.
대신 할 수있는 일은 영수증을 UIView
재정의 setFrame:
하고 대리인에게 알리는 사용자 지정 하위 클래스를 만드는 것입니다 . autoresizingMask
보기가 모든 것을 유연하게 갖도록 설정하십시오 . 완전히 투명하고 작게 구성하고 ( CALayer
많은 문제가 아니라 백업 비용을 절약하기 위해 ) 크기 변경을 보려는보기의 하위보기로 추가합니다.
이것은 iOS 5를 처음 코딩 할 API로 지정했을 때 iOS 4에서 성공적으로 작동했으며 그 결과 임시 에뮬레이션이 필요했습니다 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));
}
RxSwift 및 Swift 5에 대한 @hfossli 답변 업데이트 .
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
Nick Lockwood의이 훌륭한 튜토리얼은 핵심 애니메이션 타이밍 기능을 사용하여 무엇이든 구동하는 방법을 설명합니다. 타이머 또는 CADisplay 레이어를 사용하는 것보다 훨씬 낫습니다. 내장 된 타이밍 함수를 사용하거나 자신 만의 큐빅 베 지어 함수를 상당히 쉽게 만들 수 있기 때문입니다 (첨부 문서 ( http://www.objc.io/issue-12/ 참조). animations-explained.html ).
다음과 같은 일부 UIKit 속성에서 KVO를 사용하는 것은 안전하지 않습니다. frame
. 또는 적어도 애플이 말하는 것입니다.
ReactiveCocoa를 사용하는 것이 좋습니다. KVO를 사용하지 않고 모든 속성의 변경 사항을 듣는 데 도움이됩니다 . Signals를 사용하여 관찰 을 시작하는 것은 매우 쉽습니다 .
[RACObserve(self, frame) subscribeNext:^(CGRect frame) {
//do whatever you want with the new frame
}];