키 값 관찰을 수행하고 UIView의 프레임에서 KVO 콜백을 받으려면 어떻게해야합니까?


79

나는 변화를보고 싶어 UIView'의 frame, bounds또는 center속성입니다. 이를 달성하기 위해 키-값 관찰을 어떻게 사용할 수 있습니까?


3
이것은 실제로 질문이 아닙니다.
extremeboredom

7
난 그냥 인터넷 검색 및 :-) stackoverflowing에 의한 해결책을 찾을 수 없습니다 때문에 솔루션에 내 대답을 게시하고 싶어 ! 한숨 ... 너무 많은 공유를위한 ...
hfossli

12
흥미롭고 이미 해결책이있는 질문을해도 괜찮습니다. 그러나-질문을 표현하는 데 더 많은 노력을 기울여 질문이 실제로 질문하는 것처럼 들리도록하십시오.
Bozho 2011 년

1
환상적인 QA, 감사합니다 hfossil !!!
Fattie

답변:


71

일반적으로 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).

참고 : 최종 값에 도달하기 전에 여러 콜백을 받게됩니다. 예를 들어보기 또는 레이어의 프레임을 바꾸면 층 발생할 positionbounds(순차 참조).


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");
}];

뷰의 프레임이 경우 했다 KVO의 준수, 것입니다 단지 프레임을 관찰하기에 충분합니다. 프레임에 영향을 미치는 다른 속성도 프레임에 대한 변경 알림을 트리거합니다 (종속 키가 됨). 그러나 내가 말했듯이, 그 모든 것은 사실이 아니며 우연히 만 작동 할 수 있습니다.
Nikolai Ruhe 2013 년

4
CALayer.h는 "CALayer는 클래스와 그 하위 클래스에 의해 정의 된 모든 Objective C 속성에 대해 표준 NSKeyValueCoding 프로토콜을 구현합니다."라고 말합니다. :) 그들은 관찰 가능합니다.
hfossli

2
Core Animation 객체가 KVC를 준수하는 것으로 문서화 되어 있다는 것이 맞습니다 . 그러나 이것은 KVO 준수에 대해서는 아무것도 말하지 않습니다. KVC와 KVO는 다른 것입니다 (KVC 준수가 KVO 준수를위한 전제 조건 임에도 불구하고).
Nikolai Ruhe 2013 년

3
KVO 사용에 대한 귀하의 접근 방식과 관련된 문제에 대한 관심을 끌기 위해 반대표를 던집니다. 작업 예제가 코드에서 제대로 작업을 수행하는 방법에 대한 권장 사항을 지원하지 않는다는 것을 설명하려고했습니다. 여기에 임의의 UIKit 속성을 관찰 할 수 없다는 사실에 대한 또 다른 참조가 있습니다 .
Nikolai Ruhe 2013

5
유효한 컨텍스트 포인터를 전달하십시오. 이렇게하면 관찰과 다른 물체의 관찰을 구분할 수 있습니다. 그렇게하지 않으면 특히 관찰자 제거시 정의되지 않은 동작이 발생할 수 있습니다.
진정

62

편집 : 나는이 솔루션이 충분히 철저하다고 생각하지 않습니다. 이 답변은 역사적인 이유로 유지됩니다. 내보기 최신 대답을 여기에 : 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];
        }
    }
}

 

작동하지 않습니다. UIView에서 대부분의 속성에 대한 관찰자를 추가 할 수 있지만 프레임에는 추가 할 수 없습니다. "정의되지 않은 키 경로 '프레임'"에 대한 컴파일러 경고가 표시됩니다. 이 경고를 무시하고 어쨌든 실행하면 observeValueForKeyPath 메서드는 호출되지 않습니다.
n13 2012

글쎄, 나를 위해 작동합니다. 나는 이제 여기에 더 나중에 더 강력한 버전을 게시했습니다.
hfossli

3
확인, 나에게도 효과적입니다. UIView.frame이 제대로 관찰 가능합니다. 재미있게도 UIView.bounds는 그렇지 않습니다.
까지

1
@hfossli 맹목적으로 [super]를 호출 할 수 없다는 말이 맞습니다. '... 메시지가 수신되었지만 처리되지 않았습니다'라는 줄에 예외가 발생합니다. 이는 약간 아쉽습니다. 실제로 슈퍼 클래스가 메서드를 호출하기 전에 구현한다는 것을 알고 있습니다.
Richard

3
-1 : 어느 쪽도 UIViewController선언되지 view도 않는 UIView선언을 frameKVO 준수 키가 될 수 있습니다. Cocoa 및 Cocoa-touch는 임의의 키 관찰을 허용하지 않습니다. 모든 관찰 가능한 키는 적절하게 문서화되어야합니다. 작동하는 것처럼 보인다는 사실은 뷰에서 프레임 변경을 관찰하는 유효한 (생산 안전) 방법을 제공하지 않습니다.
Nikolai Ruhe 2013 년

7

현재 KVO를 사용하여 뷰의 프레임을 관찰 할 수 없습니다. 속성이 관찰 가능하려면 KVO를 준수 해야합니다. 안타깝게도 UIKit 프레임 워크의 속성은 다른 시스템 프레임 워크와 마찬가지로 일반적으로 관찰 할 수 없습니다.

로부터 문서 :

참고 : UIKit 프레임 워크의 클래스는 일반적으로 KVO를 지원하지 않지만 사용자 정의보기를 포함하여 애플리케이션의 사용자 정의 객체에서이를 구현할 수 있습니다.

이 규칙에는 NSOperationQueue의 operations속성 과 같은 몇 가지 예외가 있지만 명시 적으로 문서화해야합니다.

보기의 속성에서 KVO를 사용하는 것이 현재 작동 할 수 있지만 배송 코드에서 사용하지 않는 것이 좋습니다. 취약한 접근 방식이며 문서화되지 않은 동작에 의존합니다.


UIView의 "프레임"속성에 대한 KVO에 대해 동의합니다. 내가 제공 한 다른 답변은 완벽하게 작동하는 것 같습니다.
hfossli

@hfossli ReactiveCocoa는 KVO를 기반으로합니다. 동일한 한계와 문제점이 있습니다. 뷰의 프레임을 관찰하는 적절한 방법이 아닙니다.
Nikolai Ruhe 2013 년

물론 알지. 그래서 평범한 KVO를 할 수 있다고 썼습니다. ReactiveCocoa를 사용하는 것은 단순성을 유지하기위한 것입니다.
hfossli

안녕하세요 @NikolaiRuhe-방금 발생했습니다. Apple이 프레임을 KVO로 만들 수 없다면, 그들은 어떻게 stackoverflow.com/a/25727788/294884 현대적인 제약 조건을 구현합니까 ?!
Fattie

@JoeBlow Apple은 KVO를 사용할 필요가 없습니다. 그들은 모두의 구현을 제어 UIView하므로 적합하다고 생각되는 모든 메커니즘을 사용할 수 있습니다.
Nikolai Ruhe 2014 년

4

내가 대화에 기여할 수 있다면 : 다른 사람들이 지적했듯이 frame키-값 자체를 관찰 할 수 있다고 보장되지 않으며 CALayer속성이있는 것처럼 보임에도 마찬가지입니다.

대신 할 수있는 일은 영수증을 UIView재정의 setFrame:하고 대리인에게 알리는 사용자 지정 하위 클래스를 만드는 것입니다 . autoresizingMask보기가 모든 것을 유연하게 갖도록 설정하십시오 . 완전히 투명하고 작게 구성하고 ( CALayer많은 문제가 아니라 백업 비용을 절약하기 위해 ) 크기 변경을 보려는보기의 하위보기로 추가합니다.

이것은 iOS 5를 처음 코딩 할 API로 지정했을 때 iOS 4에서 성공적으로 작동했으며 그 결과 임시 에뮬레이션이 필요했습니다 viewDidLayoutSubviews(재정의 layoutSubviews가 더 적절했지만 요점을 알 수 있습니다).


transform등 을 얻으려면 레이어도 하위 클래스로
만들어야합니다

이것은 멋지고 재사용이 가능합니다. (UIView 하위 클래스에 제약 조건을 빌드하는 뷰를 가져오고 viewcontroller가 변경 사항을 다시보고 할 수있는 init 메서드를 제공하고 필요한 곳에 배포하기 쉽습니다) 여전히 작동하는 솔루션 (setBounds 재정의를 찾았습니다.) : 내 경우에 가장 효과적입니다). 요소를 중계해야하므로 viewDidLayoutSubviews : 접근 방식을 사용할 수 없을 때 특히 유용합니다.
bcl

0

앞서 언급했듯이 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...
      }
   });
}

0

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));
}

setFrame뿐만 아니라 layer.bounds, layer.transform, layer.position, layer.zPosition, layer.anchorPoint, layer.anchorPointZ 및 layer.frame도 스위 즐해야한다는 점을 제외하고. KVO의 문제점은 무엇입니까? :)
hfossli

0

RxSwiftSwift 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)

-1

KVO를 전혀 사용하지 않고이를 달성 할 수있는 방법이 있으며 다른 사람들이이 게시물을 찾을 수 있도록 여기에 추가하겠습니다.

http://www.objc.io/issue-12/animating-custom-layer-properties.html

Nick Lockwood의이 훌륭한 튜토리얼은 핵심 애니메이션 타이밍 기능을 사용하여 무엇이든 구동하는 방법을 설명합니다. 타이머 또는 CADisplay 레이어를 사용하는 것보다 훨씬 낫습니다. 내장 된 타이밍 함수를 사용하거나 자신 만의 큐빅 베 지어 함수를 상당히 쉽게 만들 수 있기 때문입니다 (첨부 문서 ( http://www.objc.io/issue-12/ 참조). animations-explained.html ).


"KVO를 사용하지 않고이를 달성하는 방법이 있습니다". 이 맥락에서 "this"는 무엇입니까? 좀 더 구체적으로 말씀해 주시겠습니까?
hfossli

OP는 애니메이션하는 동안 뷰에 대한 특정 값을 얻을 수있는 방법을 요청했습니다. 그들은 또한 이러한 속성을 KVO 할 수 있는지 물었습니다. 그러나 기술적으로 지원되지는 않습니다. 문제에 대한 강력한 솔루션을 제공하는 기사를 확인하는 것이 좋습니다.
Sam Clewlow

더 자세하게 얘기해 주 시겠어요? 기사의 어떤 부분이 관련이 있다고 생각 했습니까?
hfossli

@hfossli 그것을 보면 애니메이션에 대한 언급을 볼 수 있으므로 잘못된 질문에 대한 대답을 제시했을 수 있습니다! 죄송합니다!
Sam Clewlow

:-) 문제 없어요. 나는 단지 지식에 굶주 렸다.
hfossli

-4

다음과 같은 일부 UIKit 속성에서 KVO를 사용하는 것은 안전하지 않습니다. frame . 또는 적어도 애플이 말하는 것입니다.

ReactiveCocoa를 사용하는 것이 좋습니다. KVO를 사용하지 않고 모든 속성의 변경 사항을 듣는 데 도움이됩니다 . Signals를 사용하여 관찰 을 시작하는 것은 매우 쉽습니다 .

[RACObserve(self, frame) subscribeNext:^(CGRect frame) {
    //do whatever you want with the new frame
}];

4
그러나 @NikolaiRuhe는 "ReactiveCocoa는 KVO를 기반으로합니다. 동일한 한계와 문제가 있습니다. 뷰의 프레임을 관찰하는 적절한 방법이 아닙니다."
Fattie
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.