Ich möchte für Änderungen in einer sehen UIView
ist frame
, bounds
oder center
Eigentum. Wie kann ich Key-Value Observing verwenden, um dies zu erreichen?
iphone
ios
objective-c
uiview
key-value-observing
hfossli
quelle
quelle
Antworten:
Es gibt normalerweise Benachrichtigungen oder andere beobachtbare Ereignisse, bei denen KVO nicht unterstützt wird. Obwohl die Dokumentation "Nein" sagt , ist es scheinbar sicher, den CALayer zu beobachten, der die UIView unterstützt. Das Beobachten des CALayer funktioniert in der Praxis aufgrund des umfassenden Einsatzes von KVO und geeigneten Accessoren (anstelle der Ivar-Manipulation). Es ist nicht garantiert, dass es in Zukunft funktioniert.
Auf jeden Fall ist der Rahmen der Ansicht nur das Produkt anderer Eigenschaften. Deshalb müssen wir diese beachten:
[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];
Das vollständige Beispiel finden Sie hier https://gist.github.com/hfossli/7234623
HINWEIS: Dies wird in den Dokumenten nicht unterstützt, funktioniert jedoch ab heute mit allen bisherigen iOS-Versionen (derzeit iOS 2 -> iOS 11).
HINWEIS: Beachten Sie, dass Sie mehrere Rückrufe erhalten, bevor der endgültige Wert erreicht ist. Wenn Sie beispielsweise den Rahmen einer Ansicht oder Ebene ändern, ändert sich die Ebene
position
undbounds
(in dieser Reihenfolge).Mit ReactiveCocoa können Sie tun
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"); }];
Und wenn Sie nur wissen möchten, wann
bounds
Sie Änderungen vornehmen können@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"); }];
Und wenn Sie nur wissen möchten, wann
frame
Sie Änderungen vornehmen können@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"); }];
quelle
EDIT : Ich denke nicht, dass diese Lösung gründlich genug ist. Diese Antwort wird aus historischen Gründen aufbewahrt. Meine neueste Antwort finden Sie hier: https://stackoverflow.com/a/19687115/202451
Sie müssen KVO für die Frame-Eigenschaft ausführen. "self" ist in diesem Fall ein UIViewController.
Hinzufügen des Beobachters (normalerweise in viewDidLoad):
[self addObserver:self forKeyPath:@"view.frame" options:NSKeyValueObservingOptionOld context:NULL];
Entfernen des Beobachters (normalerweise in Dealloc oder viewDidDisappear :):
[self removeObserver:self forKeyPath:@"view.frame"];
Informationen über die Änderung erhalten
- (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]; } } }
quelle
UIViewController
deklariertview
nochUIView
deklariertframe
. Kakao und Kakao-Touch erlauben keine willkürliche Beobachtung der Tasten. Alle beobachtbaren Schlüssel müssen ordnungsgemäß dokumentiert werden. Die Tatsache, dass es zu funktionieren scheint, macht dies nicht zu einer gültigen (produktionssicheren) Methode, um Rahmenänderungen in einer Ansicht zu beobachten.Derzeit ist es nicht möglich, KVO zum Beobachten des Rahmens einer Ansicht zu verwenden. Eigenschaften müssen KVO-konform sein , um beobachtbar zu sein. Leider sind die Eigenschaften des UIKit-Frameworks im Allgemeinen nicht wie bei jedem anderen System-Framework zu beobachten.
Aus der Dokumentation :
Es gibt einige Ausnahmen von dieser Regel, wie die
operations
Eigenschaft von NSOperationQueue, die jedoch explizit dokumentiert werden müssen.Selbst wenn die Verwendung von KVO für die Eigenschaften einer Ansicht derzeit möglicherweise funktioniert, würde ich nicht empfehlen, es im Versandcode zu verwenden. Es ist ein fragiler Ansatz und beruht auf undokumentiertem Verhalten.
quelle
UIView
damit sie jeden Mechanismus verwenden können, den sie für richtig halten.Wenn ich zum Gespräch beitragen könnte: Wie andere betont haben,
frame
ist nicht garantiert, dass der Schlüsselwert selbst beobachtbar ist, und dieCALayer
Eigenschaften sind es auch nicht, obwohl sie zu sein scheinen.Sie können stattdessen eine benutzerdefinierte
UIView
Unterklasse erstellen , diesetFrame:
diese Quittung überschreibt und einem Delegaten mitteilt. Stellen Sie dasautoresizingMask
so ein, dass die Ansicht alles flexibel hat. Konfigurieren Sie es so, dass es vollständig transparent und klein ist (um Kosten auf derCALayer
Rückseite zu sparen , nicht dass es sehr wichtig ist), und fügen Sie es als Unteransicht der Ansicht hinzu, in der Sie Größenänderungen beobachten möchten.Dies funktionierte für mich bereits unter iOS 4 erfolgreich, als wir iOS 5 zum ersten Mal als API für den Code spezifizierten und daher eine vorübergehende Emulation von benötigten
viewDidLayoutSubviews
(obwohl das ÜberschreibenlayoutSubviews
angemessener war, aber Sie verstehen, worum es geht).quelle
transform
etcWie bereits erwähnt, können Sie eine benutzerdefinierte Ansicht erstellen, die entweder setFrame oder setBounds überschreibt, wenn KVO nicht funktioniert und Sie nur Ihre eigenen Ansichten beobachten möchten, über die Sie die Kontrolle haben. Eine Einschränkung besteht darin, dass der endgültige, gewünschte Frame-Wert zum Zeitpunkt des Aufrufs möglicherweise nicht verfügbar ist. Daher habe ich der nächsten Haupt-Thread-Schleife einen GCD-Aufruf hinzugefügt, um den Wert erneut zu überprüfen.
-(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... } }); }
quelle
Um sich nicht auf die Beobachtung von KVO zu verlassen, können Sie das Methoden-Swizzling wie folgt durchführen:
@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
So beobachten Sie nun die Rahmenänderung für eine UIView in Ihrem Anwendungscode:
- (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)); }
quelle
Aktualisierte @ hfossli-Antwort für RxSwift und Swift 5 .
Mit RxSwift können Sie tun
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)
Und wenn Sie nur wissen möchten, wann
bounds
Sie Änderungen vornehmen könnenObservable.of(rx.observe(CGRect.self, #keyPath(UIView.layer.bounds))).subscribe(onNext: { _ in print("View bounds changed its geometry") }).disposed(by: rx.disposeBag)
Und wenn Sie nur wissen möchten, wann
frame
Sie Änderungen vornehmen könnenObservable.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)
quelle
Es gibt eine Möglichkeit, dies zu erreichen, ohne KVO überhaupt zu verwenden, und damit andere diesen Beitrag finden, werde ich ihn hier hinzufügen.
http://www.objc.io/issue-12/animating-custom-layer-properties.html
Dieses hervorragende Tutorial von Nick Lockwood beschreibt, wie Sie mithilfe der Timing-Funktionen für Kernanimationen alles steuern können. Es ist der Verwendung eines Timers oder einer CADisplay-Ebene weit überlegen, da Sie die integrierten Timing-Funktionen verwenden oder ganz einfach Ihre eigene kubische Bezier-Funktion erstellen können (siehe den zugehörigen Artikel ( http://www.objc.io/issue-12/). Animationen erklärt.html ).
quelle
Es ist nicht sicher, KVO in einigen UIKit-Eigenschaften wie zu verwenden
frame
. Zumindest sagt das Apple.Ich würde empfehlen, ReactiveCocoa zu verwenden . Dies hilft Ihnen dabei, Änderungen in einer Eigenschaft zu hören, ohne KVO zu verwenden. Es ist sehr einfach, etwas mithilfe von Signalen zu beobachten :
[RACObserve(self, frame) subscribeNext:^(CGRect frame) { //do whatever you want with the new frame }];
quelle