मैं मुख्य मूल्य का अवलोकन कैसे कर सकता हूं और UIView के फ्रेम पर KVO कॉलबैक प्राप्त कर सकता हूं?


79

मैं UIView's frame, boundsया centerप्रॉपर्टी में बदलाव देखना चाहता हूं । मैं इसे प्राप्त करने के लिए कुंजी-मूल्य अवलोकन का उपयोग कैसे कर सकता हूं?


3
यह वास्तव में एक सवाल नहीं है।
चरमोत्कर्ष

7
मैं सिर्फ समाधान के लिए अपना जवाब पोस्ट करना चाहता था क्योंकि मैं googling और stackoverflowing द्वारा समाधान नहीं ढूँढ सका :-) ... आहें! ... इतना साझा करने के लिए ...
hfossli

12
यह उन सवालों को पूछने के लिए पूरी तरह से ठीक है जो आपको दिलचस्प लगते हैं, और आपके पास पहले से ही समाधान है। हालाँकि - प्रश्न को और अधिक प्रयास में लगा दिया ताकि यह वास्तव में ऐसा लगे जैसे कि कोई प्रश्न पूछेगा।
बोझो

1
शानदार क्यूए, धन्यवाद hfossil !!!
फटी

जवाबों:


71

आमतौर पर सूचनाएं या अन्य अवलोकन योग्य घटनाएं होती हैं जहां 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");
}];

किसी दृश्य के फ्रेम तो था KVO शिकायत है, यह होगा सिर्फ फ्रेम निरीक्षण करने के लिए पर्याप्त हो। अन्य गुण जो फ़्रेम को प्रभावित करते हैं, वे फ़्रेम पर एक परिवर्तन सूचना को ट्रिगर करेंगे (यह एक निर्भर कुंजी होगी)। लेकिन, जैसा कि मैंने कहा, यह सब केवल मामला नहीं है और केवल दुर्घटना से काम हो सकता है।
निकोलाई रुहे

4
खैर CALayer.h का कहना है कि "CALayer क्लास और उसके उपवर्गों द्वारा परिभाषित सभी ऑब्जेक्टिव C गुणों के लिए मानक NSKeyValueCoding प्रोटोकॉल को लागू करता है ..." इसलिए आप वहां जाते हैं। :) वे अवलोकनीय हैं।
hfossli

2
आप सही हैं कि कोर एनिमेशन ऑब्जेक्ट्स केवीसी के अनुरूप होने के लिए प्रलेखित हैं । यह केवीओ अनुपालन के बारे में कुछ नहीं कहता है, हालांकि। केवीसी और केवीओ सिर्फ अलग-अलग चीजें हैं (भले ही केवीसी अनुपालन केवीओ अनुपालन के लिए एक शर्त है)।
निकोलाई रुहे

3
केवीओ का उपयोग करने के आपके दृष्टिकोण से जुड़ी समस्याओं पर ध्यान आकर्षित करने के लिए मैं नीच हूं। मैंने यह समझाने की कोशिश की है कि एक कामकाजी उदाहरण कोड में कुछ ठीक से करने के तरीके की सिफारिश का समर्थन नहीं करता है। मामले में आप इस तथ्य के एक और संदर्भ के बारे में आश्वस्त नहीं हैं कि मनमाने ढंग से UIKit गुणों का निरीक्षण करना संभव नहीं है
निकोलाई रुहे

5
कृपया एक मान्य संदर्भ पॉइंटर पास करें। ऐसा करने से आप अपनी टिप्पणियों और किसी अन्य वस्तु के बीच अंतर कर सकते हैं। ऐसा न करने से अपरिभाषित व्यवहार हो सकता है, विशेष रूप से पर्यवेक्षक को हटाने पर।
8

62

संपादित करें : मुझे नहीं लगता कि यह समाधान पूरी तरह से पर्याप्त है। यह उत्तर ऐतिहासिक कारणों से रखा गया है। मेरा नवीनतम उत्तर यहां देखें : 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];
        }
    }
}

 

काम नहीं करता है। आप UIView में अधिकांश संपत्तियों के लिए पर्यवेक्षक जोड़ सकते हैं, लेकिन फ्रेम के लिए नहीं। मुझे "संभवतः अपरिभाषित कुंजी पथ 'फ़्रेम' के बारे में एक संकलक चेतावनी मिलती है।" इस चेतावनी को नजरअंदाज करते हुए और इसे वैसे भी करते हुए, ObsValueForKeyPath विधि को कभी भी कॉल नहीं किया जाता है।
n13

खैर, मेरे लिए काम करता है। मैंने अब बाद में और अधिक मजबूत संस्करण यहां भी पोस्ट किया है।
hfossli

3
पुष्टि की, मेरे लिए भी काम करता है। UIView.frame ठीक से देखने योग्य है। काफी मजेदार है, UIView.bounds नहीं है।
तक

1
@ ह्फ़ोसली आप सही कह रहे हैं कि आप आँख बंद करके नहीं कह सकते हैं [सुपर] - कि '... संदेश प्राप्त हुआ था, लेकिन संभाला नहीं' की तर्ज पर एक अपवाद फेंक देंगे, जो थोड़ा शर्म की बात है - आपको करना होगा वास्तव में पता है कि सुपरक्लास कॉल करने से पहले विधि को लागू करता है।
रिचर्ड

3
-1: न तो केवीओ अनुपालन कुंजी घोषित UIViewControllerकरता है viewऔर न ही UIViewघोषित करता है frame। कोको और कोको-स्पर्श चाबियों के मनमाने ढंग से अवलोकन की अनुमति नहीं देते हैं। सभी अवलोकनीय कुंजियों को ठीक से प्रलेखित किया जाना है। तथ्य यह है कि काम करने के लिए लगता है कि यह एक वैध (उत्पादन सुरक्षित) तरीके से एक दृश्य पर फ्रेम परिवर्तन का निरीक्षण नहीं करता है।
निकोलाई रुहे

7

वर्तमान में किसी दृश्य के फ़्रेम का अवलोकन करने के लिए KVO का उपयोग करना संभव नहीं है। गुण केवीओ के अनुरूप होना चाहिए अवलोकन होना चाहिए। अफसोस की बात है, UIKit ढांचे के गुण आम तौर पर किसी भी अन्य प्रणाली ढांचे के साथ देखने योग्य नहीं हैं।

से प्रलेखन :

नोट: हालांकि UIKit ढांचे की कक्षाएं आम तौर पर KVO का समर्थन नहीं करती हैं, फिर भी आप इसे अपने एप्लिकेशन की कस्टम ऑब्जेक्ट्स में लागू कर सकते हैं, जिसमें कस्टम दृश्य भी शामिल हैं।

इस नियम के कुछ अपवाद हैं, जैसे NSOperationQueue की operationsसंपत्ति लेकिन उन्हें स्पष्ट रूप से प्रलेखित किया जाना है।

भले ही KVO का उपयोग किसी दृश्य के गुणों पर किया जा सकता है, वर्तमान में काम कर सकता है मैं इसे शिपिंग कोड में उपयोग करने की अनुशंसा नहीं करूंगा। यह एक नाजुक दृष्टिकोण है और यह अनिर्दिष्ट व्यवहार पर निर्भर करता है।


मैं यूवीवाई पर संपत्ति "फ्रेम" पर केवीओ के बारे में आपसे सहमत हूं। मेरे द्वारा दिया गया अन्य उत्तर पूरी तरह से काम करता है।
hfossli

@hfossli ReactiveCocoa केवीओ पर बनाया गया है। इसकी एक ही सीमाएं और समस्याएं हैं। किसी दृश्य के फ्रेम का निरीक्षण करना उचित तरीका नहीं है।
निकोलाई रुहे

हाँ मैं जानता हूं कि। इसलिए मैंने लिखा है कि आप साधारण केवीओ कर सकते हैं। ReactiveCocoa का इस्तेमाल सिर्फ इसे सिंपल रखने के लिए किया गया था।
hfossli

हाय @NikolaiRuhe - यह सिर्फ मेरे लिए हुआ। एप्पल फ्रेम, कैसे हो वे लागू करते हैं KVO नहीं कर सकते stackoverflow.com/a/25727788/294884 विचारों के लिए आधुनिक प्रतिबन्ध ?!
फटी

@JoeBlow Apple को KVO का उपयोग करने की आवश्यकता नहीं है। वे सभी के कार्यान्वयन को नियंत्रित करते हैं UIViewइसलिए वे जो भी तंत्र देखते हैं उसका उपयोग कर सकते हैं।
निकोलाई रुहे सेप

4

अगर मैं बातचीत में योगदान दे सकता हूं: जैसा कि दूसरों ने बताया है, frameइसकी कुंजी-मूल्य का पालन करने की गारंटी नहीं है और न CALayerही वे होने के बावजूद भी गुण हैं।

इसके बजाय आप क्या कर सकते हैं एक कस्टम UIViewउपवर्ग बनाएं जो setFrame:उस रसीद को ओवरराइड और अनाउंस करता है। सेट करें autoresizingMaskताकि दृश्य में सब कुछ लचीला हो। इसे पूरी तरह से पारदर्शी और छोटा होने के लिए कॉन्फ़िगर करें ( CALayerबैकिंग पर लागत बचाने के लिए , यह नहीं कि यह बहुत मायने रखता है) और इसे उस दृश्य के उप-भाग के रूप में जोड़ें जिस पर आप आकार परिवर्तन देखना चाहते हैं।

जब मैंने पहली बार iOS 5 को API के रूप में कोड करने के लिए और इसके परिणामस्वरूप, आईओएस 5 के तहत वापस आने के लिए यह मेरे लिए सफलतापूर्वक काम किया था, तो viewDidLayoutSubviews( अस्थायी रूप से ओवरराइडिंग layoutSubviewsअधिक उपयुक्त थी, लेकिन आपको बात मिलती है)।


आपको परत transformआदि को प्राप्त करने के लिए उप-वर्ग की आवश्यकता होगी
hfossli

यह एक अच्छा, फिर से प्रयोग करने योग्य है (अपने UIView उपवर्ग को एक ऐसी विधि दें, जो देखने में बाधा उत्पन्न करने के लिए और देखने के लिए व्यूकंट्रोलर को वापस रिपोर्ट करने के लिए ले जाता है और यह आपको कहीं भी तैनात करना आसान है) समाधान जो अभी भी काम करता है (पाया गया ओवरराइडिंग : मेरे मामले में सबसे प्रभावी)। जब आप viewDidLayoutSubviews का उपयोग नहीं कर सकते हैं, तो विशेष रूप से आसान है: तत्वों को रिले करने की आवश्यकता के कारण दृष्टिकोण।
bcl

0

जैसा कि उल्लेख किया गया है, यदि केवीओ काम नहीं करता है और आप केवल अपने स्वयं के विचारों का निरीक्षण करना चाहते हैं, जिस पर आपका नियंत्रण है, आप एक कस्टम दृश्य बना सकते हैं जो या तो सेटफ़्रेम या सेटबाउंड्स को ओवरराइड करता है। एक चेतावनी यह है कि अंतिम, वांछित फ्रेम मूल्य मंगलाचरण के बिंदु पर उपलब्ध नहीं हो सकता है। इस प्रकार मैंने फिर से मूल्य की जांच करने के लिए अगले मुख्य थ्रेड लूप में एक जीसीडी कॉल जोड़ा।

-(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

केवीओ पर भरोसा न करने के लिए आप निम्नानुसार विधि स्वाइलिंग कर सकते हैं:

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

सिवाय इसके कि आपको न केवल स्विज़ल सेटफ़्रेम, बल्कि लेयर.बाउंड्स, लेयर.ट्रांसफॉर्म, लेयर.पोज़िशन, लेयर.ज़पोज़िशन, लेयर.नचोरपॉइंट, लेयर.आनचोरपॉइंटज़ और लेयर .फ्रेम की ज़रूरत नहीं होगी। केवीओ के साथ क्या गलत है? :)
hfossli

0

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)

-1

केवीओ का उपयोग किए बिना इसे प्राप्त करने का एक तरीका है, और दूसरों के लिए इस पोस्ट को खोजने के लिए, मैं इसे यहां जोड़ूंगा।

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

निक लॉकवुड द्वारा इस उत्कृष्ट ट्यूटोरियल का वर्णन है कि कुछ भी ड्राइव करने के लिए कोर एनिमेशन टाइमिंग फ़ंक्शन का उपयोग कैसे करें। टाइमर या कैडिसप्ले लेयर का उपयोग करना कहीं बेहतर है, क्योंकि आप बिल्ट इन टाइमिंग फंक्शन्स का उपयोग कर सकते हैं, या आसानी से अपना क्यूबिक बेज़ियर फंक्शन बना सकते हैं (साथ में लेख देखें ( http://www.objc.io/issue-12/ एनिमेशन - समझाया . html )।


"केवीओ का उपयोग किए बिना इसे प्राप्त करने का एक तरीका है"। इस संदर्भ में "यह" क्या है? क्या आप थोड़ा और स्पष्ट कर सकते हैं।
hfossli

ओपी ने एक दृश्य के लिए विशिष्ट मान प्राप्त करने का तरीका पूछा, जबकि यह एनिमेट कर रहा है। उन्होंने यह भी पूछा कि क्या यह इन गुणों केवीओ के लिए संभव है, जो यह है, लेकिन यह तकनीकी रूप से समर्थित नहीं है। मैंने लेख की जाँच करने का सुझाव दिया है, जो समस्या का एक मजबूत समाधान प्रदान करता है।
सैम क्लीवलो

क्या आप अधिक विशिष्ट हो सकते हैं? लेख का कौन सा भाग आपको प्रासंगिक लगा?
hfossli

@hfossli इसे देखते हुए, मुझे लगता है कि मैंने गलत सवाल का जवाब दिया होगा, क्योंकि मैं एनिमेशन का कोई भी उल्लेख देख सकता हूं! माफ़ करना!
सैम क्लीवलो

:-) कोई दिक्कत नहीं है। मुझे सिर्फ ज्ञान की भूख थी।
hfossli

-4

कुछ यूआईआईटी संपत्तियों में केवीओ का उपयोग करना सुरक्षित नहीं है frame। या कम से कम एप्पल जो कहता है।

मैं ReactiveCocoa का उपयोग करने की सलाह दूंगा, इससे आपको केवीओ का उपयोग किए बिना किसी भी संपत्ति में बदलाव को सुनने में मदद मिलेगी, सिग्नल का उपयोग करके किसी चीज़ का अवलोकन करना शुरू करना बहुत आसान है :

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

4
हालाँकि, @NikolaiRuhe का कहना है कि "ReactiveCocoa KVO पर बनाया गया है। इसकी सीमाएँ और समस्याएं समान हैं। यह किसी दृश्य के फ्रेम का निरीक्षण करने का उचित तरीका नहीं है"
Fattie
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.