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?
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?
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
und bounds
(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");
}];
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];
}
}
}
UIViewController
deklariert view
noch UIView
deklariert frame
. 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 :
Hinweis: Obwohl die Klassen des UIKit-Frameworks KVO im Allgemeinen nicht unterstützen, können Sie es dennoch in den benutzerdefinierten Objekten Ihrer Anwendung implementieren, einschließlich benutzerdefinierter Ansichten.
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.
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 die CALayer
Eigenschaften sind es auch nicht, obwohl sie zu sein scheinen.
Sie können stattdessen eine benutzerdefinierte UIView
Unterklasse erstellen , die setFrame:
diese Quittung überschreibt und einem Delegaten mitteilt. Stellen Sie das autoresizingMask
so ein, dass die Ansicht alles flexibel hat. Konfigurieren Sie es so, dass es vollständig transparent und klein ist (um Kosten auf der CALayer
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 Überschreiben layoutSubviews
angemessener war, aber Sie verstehen, worum es geht).
transform
etc
Wie 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...
}
});
}
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));
}
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önnen
Observable.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önnen
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)
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 ).
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
}];