मैं UIView
's frame
, bounds
या center
प्रॉपर्टी में बदलाव देखना चाहता हूं । मैं इसे प्राप्त करने के लिए कुंजी-मूल्य अवलोकन का उपयोग कैसे कर सकता हूं?
मैं UIView
's frame
, bounds
या center
प्रॉपर्टी में बदलाव देखना चाहता हूं । मैं इसे प्राप्त करने के लिए कुंजी-मूल्य अवलोकन का उपयोग कैसे कर सकता हूं?
जवाबों:
आमतौर पर सूचनाएं या अन्य अवलोकन योग्य घटनाएं होती हैं जहां KVO समर्थित नहीं होता है। भले ही डॉक्स 'ना' कहे , लेकिन यह अस्थाई रूप से काइलेव का निरीक्षण करने के लिए सुरक्षित है। केवीओ के व्यापक उपयोग और उचित एक्सेसर्स (आइवर हेरफेर के बजाय) के कारण कैलेयर का अभ्यास करना व्यवहार में है। यह आगे काम करने की गारंटी नहीं है।
वैसे भी, दृश्य का फ्रेम सिर्फ अन्य गुणों का उत्पाद है। इसलिए हमें उन पर ध्यान देने की आवश्यकता है:
[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 करने को मिल गया है। "स्व" इस मामले में एक UIViewController है।
पर्यवेक्षक को जोड़ना (आमतौर पर viewDidLoad में किया गया):
[self addObserver:self forKeyPath:@"view.frame" options:NSKeyValueObservingOptionOld context:NULL];
प्रेक्षक को हटाने (आमतौर पर डीलडॉक या व्यूडिडिसैपियर में किया गया :):
[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 का उपयोग करना संभव नहीं है। गुण केवीओ के अनुरूप होना चाहिए अवलोकन होना चाहिए। अफसोस की बात है, UIKit ढांचे के गुण आम तौर पर किसी भी अन्य प्रणाली ढांचे के साथ देखने योग्य नहीं हैं।
से प्रलेखन :
नोट: हालांकि UIKit ढांचे की कक्षाएं आम तौर पर KVO का समर्थन नहीं करती हैं, फिर भी आप इसे अपने एप्लिकेशन की कस्टम ऑब्जेक्ट्स में लागू कर सकते हैं, जिसमें कस्टम दृश्य भी शामिल हैं।
इस नियम के कुछ अपवाद हैं, जैसे NSOperationQueue की operations
संपत्ति लेकिन उन्हें स्पष्ट रूप से प्रलेखित किया जाना है।
भले ही KVO का उपयोग किसी दृश्य के गुणों पर किया जा सकता है, वर्तमान में काम कर सकता है मैं इसे शिपिंग कोड में उपयोग करने की अनुशंसा नहीं करूंगा। यह एक नाजुक दृष्टिकोण है और यह अनिर्दिष्ट व्यवहार पर निर्भर करता है।
UIView
इसलिए वे जो भी तंत्र देखते हैं उसका उपयोग कर सकते हैं।
अगर मैं बातचीत में योगदान दे सकता हूं: जैसा कि दूसरों ने बताया है, frame
इसकी कुंजी-मूल्य का पालन करने की गारंटी नहीं है और न CALayer
ही वे होने के बावजूद भी गुण हैं।
इसके बजाय आप क्या कर सकते हैं एक कस्टम UIView
उपवर्ग बनाएं जो setFrame:
उस रसीद को ओवरराइड और अनाउंस करता है। सेट करें autoresizingMask
ताकि दृश्य में सब कुछ लचीला हो। इसे पूरी तरह से पारदर्शी और छोटा होने के लिए कॉन्फ़िगर करें ( CALayer
बैकिंग पर लागत बचाने के लिए , यह नहीं कि यह बहुत मायने रखता है) और इसे उस दृश्य के उप-भाग के रूप में जोड़ें जिस पर आप आकार परिवर्तन देखना चाहते हैं।
जब मैंने पहली बार iOS 5 को API के रूप में कोड करने के लिए और इसके परिणामस्वरूप, आईओएस 5 के तहत वापस आने के लिए यह मेरे लिए सफलतापूर्वक काम किया था, तो viewDidLayoutSubviews
( अस्थायी रूप से ओवरराइडिंग layoutSubviews
अधिक उपयुक्त थी, लेकिन आपको बात मिलती है)।
transform
आदि को प्राप्त करने के लिए उप-वर्ग की आवश्यकता होगी
जैसा कि उल्लेख किया गया है, यदि केवीओ काम नहीं करता है और आप केवल अपने स्वयं के विचारों का निरीक्षण करना चाहते हैं, जिस पर आपका नियंत्रण है, आप एक कस्टम दृश्य बना सकते हैं जो या तो सेटफ़्रेम या सेटबाउंड्स को ओवरराइड करता है। एक चेतावनी यह है कि अंतिम, वांछित फ्रेम मूल्य मंगलाचरण के बिंदु पर उपलब्ध नहीं हो सकता है। इस प्रकार मैंने फिर से मूल्य की जांच करने के लिए अगले मुख्य थ्रेड लूप में एक जीसीडी कॉल जोड़ा।
-(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...
}
});
}
केवीओ पर भरोसा न करने के लिए आप निम्नानुसार विधि स्वाइलिंग कर सकते हैं:
@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)
केवीओ का उपयोग किए बिना इसे प्राप्त करने का एक तरीका है, और दूसरों के लिए इस पोस्ट को खोजने के लिए, मैं इसे यहां जोड़ूंगा।
http://www.objc.io/issue-12/animating-custom-layer-properties.html
निक लॉकवुड द्वारा इस उत्कृष्ट ट्यूटोरियल का वर्णन है कि कुछ भी ड्राइव करने के लिए कोर एनिमेशन टाइमिंग फ़ंक्शन का उपयोग कैसे करें। टाइमर या कैडिसप्ले लेयर का उपयोग करना कहीं बेहतर है, क्योंकि आप बिल्ट इन टाइमिंग फंक्शन्स का उपयोग कर सकते हैं, या आसानी से अपना क्यूबिक बेज़ियर फंक्शन बना सकते हैं (साथ में लेख देखें ( http://www.objc.io/issue-12/ एनिमेशन - समझाया . html )।
कुछ यूआईआईटी संपत्तियों में केवीओ का उपयोग करना सुरक्षित नहीं है frame
। या कम से कम एप्पल जो कहता है।
मैं ReactiveCocoa का उपयोग करने की सलाह दूंगा, इससे आपको केवीओ का उपयोग किए बिना किसी भी संपत्ति में बदलाव को सुनने में मदद मिलेगी, सिग्नल का उपयोग करके किसी चीज़ का अवलोकन करना शुरू करना बहुत आसान है :
[RACObserve(self, frame) subscribeNext:^(CGRect frame) {
//do whatever you want with the new frame
}];