UIView के setNeedsLayout, LayoutIfNeeded और LayoutSubviews के बीच क्या संबंध है?


105

क्या कोई भी UIView's setNeedsLayout, layoutIfNeededऔर layoutSubviewsविधियों के बीच संबंध पर एक निश्चित व्याख्या दे सकता है ? और एक उदाहरण कार्यान्वयन जहां तीनों का उपयोग किया जाएगा। धन्यवाद।

मुझे जो उलझन होती है, वह यह है कि अगर मैं अपने कस्टम दृश्य को एक setNeedsLayoutसंदेश भेजता हूं तो यह इस पद्धति के बाद एक बहुत ही अगली चीज है layoutSubviews, जो सही है layoutIfNeeded। डॉक्स से मुझे उम्मीद है कि प्रवाह setNeedsLayout> कारणों layoutIfNeededको बुलाया जाएगा> कारणों layoutSubviewsको बुलाया जाएगा।

जवाबों:


104

मैं अभी भी यह पता लगाने की कोशिश कर रहा हूं, इसलिए इसे कुछ संदेह के साथ लें और अगर इसमें त्रुटियां हैं तो मुझे माफ करें।

setNeedsLayoutएक आसान है: यह सिर्फ UIView में कहीं एक झंडा सेट करता है जो इसे ज़रूरत के लेआउट के रूप में चिह्नित करता है। यह layoutSubviewsअगले लाल होने से पहले दृश्य पर कॉल करने के लिए मजबूर करेगा । ध्यान दें कि कई मामलों में आपको autoresizesSubviewsसंपत्ति की वजह से इसे स्पष्ट रूप से कॉल करने की आवश्यकता नहीं है । यदि वह सेट है (जो कि डिफ़ॉल्ट रूप से है) तो किसी दृश्य के फ़्रेम में कोई भी परिवर्तन उसके सबव्यू को देखने के लिए कारण होगा।

layoutSubviewsवह विधि है जिसमें आप सभी दिलचस्प चीजें करते हैं। drawRectयदि आप करेंगे तो यह लेआउट के लिए बराबर है । एक तुच्छ उदाहरण हो सकता है:

-(void)layoutSubviews {
    // Child's frame is always equal to our bounds inset by 8px
    self.subview1.frame = CGRectInset(self.bounds, 8.0, 8.0);
    // It seems likely that this is incorrect:
    // [self.subview1 layoutSubviews];
    // ... and this is correct:
    [self.subview1 setNeedsLayout];
    // but I don't claim to know definitively.
}

AFAIK layoutIfNeededका आमतौर पर आपके उपवर्ग में ओवरराइड होने का मतलब नहीं है। यह एक विधि है जिसे आप कॉल करना चाहते हैं जब आप एक दृश्य चाहते हैं जिसे अभी बाहर रखा जाना है । Apple का कार्यान्वयन कुछ इस तरह हो सकता है:

-(void)layoutIfNeeded {
    if (self._needsLayout) {
        UIView *sv = self.superview;
        if (sv._needsLayout) {
            [sv layoutIfNeeded];
        } else {
            [self layoutSubviews];
        }
    }
}

आप layoutIfNeededइसे तुरंत लागू करने के लिए इसे (और इसके पर्यवेक्षकों को आवश्यकतानुसार) बाध्य करने के लिए एक दृश्य पर कॉल करेंगे।


2
धन्यवाद - खुशी है कि किसी ने अंततः इसका उत्तर दिया। इस बीच मैं भी setNeedsDisplay में तल्लीन करना पड़ा है - जिसके कारण drawRect को - और contentMode कहा जाता है। केवल कन्टैंटमोड = UIViewContentModeRedraw सेटनएड्सप्लेप्ले का कारण बनेगा जब किसी दृश्य की सीमा बदल जाती है। अन्य contentMode विकल्प केवल दृश्य को छोटा करने, कुछ राशि द्वारा स्थानांतरित करने, या किनारे पर संरेखित करने का कारण बनते हैं। मेरा कस्टम UITableViewCell अभिविन्यास परिवर्तन पर फिर से शुरू नहीं करना चाहता था, यह केवल तब तक ही होगा, जब तक कि मैं सामग्री को UIViewContentModeRedraw में सेट नहीं कर देता।
तारिफा

मुझे लगता है कि शायद आपके कोड में [self.subview1 LayoutSubviews] को [self.subview1 setNeedsLayout] से बदल दिया जाए। चूंकि लेआउटसुब्यू का मतलब ओवरराइड होना है, लेकिन इसका मतलब किसी दूसरे दृश्य से कॉल करना नहीं है। या तो फ्रेमवर्क यह निर्धारित करता है कि इसे कब कॉल किया जाए (दक्षता के लिए एक कॉल में एक साथ कई लेआउट अनुरोधों को समूहीकृत करके) या आप अप्रत्यक्ष रूप से लेआउट कॉल करके देखते हैं। दृश्य पदानुक्रम में कहीं।
तारफा

मेरी उपरोक्त टिप्पणी के लिए सुधार: "... चूंकि लेआउटसुब्यू का मतलब ओवरराइड करना या स्वयं से पुकारा जाना है, लेकिन इसका मतलब यह नहीं है कि इसे दूसरे दृश्य से बुलाया जाए ..."
टार्फा

मैं वास्तव में निश्चित नहीं हूं [subview1 layoutSubviews]। यह बहुत अच्छी तरह से हो सकता है, जैसे कि drawRect, आप इसे सीधे कॉल नहीं करेंगे। लेकिन मुझे यकीन नहीं है कि setNeedsLayoutलेआउट चरण के दौरान कॉल करने पर दृश्य को उसी लेआउट चरण के दौरान निर्धारित किया जाएगा या यदि यह अगले एक तक देरी हो रही है। किसी ऐसे व्यक्ति से जवाब देखना अच्छा होगा जो वास्तव में समझता है कि यह सब कैसे काम करता है ...
n8gray

34

मैं n8gray के उत्तर पर जोड़ना चाहूंगा कि कुछ मामलों में आपको setNeedsLayoutबाद में कॉल करना होगा layoutIfNeeded

उदाहरण के लिए मान लें कि आपने UIView का विस्तार करने वाला एक कस्टम दृश्य लिखा है, जिसमें सबव्यू की स्थिति जटिल है और इसे ऑटोरेस्पिरिंगमास्क या iOS6 AutoLayout के साथ नहीं किया जा सकता है। कस्टम पोजिशन को ओवरराइड करके किया जा सकता है layoutSubviews

एक उदाहरण के रूप में, मान लें कि आपके पास एक कस्टम दृश्य है जिसमें एक contentViewसंपत्ति है और एक edgeInsetsसंपत्ति है जो सामग्री दृश्य के चारों ओर मार्जिन सेट करने की अनुमति देती है। layoutSubviewsइस तरह दिखेगा:

- (void) layoutSubviews {
    self.contentView.frame = CGRectMake(
        self.bounds.origin.x + self.edgeInsets.left,
        self.bounds.origin.y + self.edgeInsets.top,
        self.bounds.size.width - self.edgeInsets.left - self.edgeInsets.right,
        self.bounds.size.height - self.edgeInsets.top - self.edgeInsets.bottom); 
}

यदि आप चाहते हैं कि जब भी आप edgeInsetsसंपत्ति बदलते हैं तो फ्रेम परिवर्तन को चेतन करने में सक्षम हों , आपको edgeInsetsनिम्नानुसार सेटर को ओवरराइड करना होगा और setNeedsLayoutइसके बाद कॉल करना होगा layoutIfNeeded:

- (void) setEdgeInsets:(UIEdgeInsets)edgeInsets {
    _edgeInsets = edgeInsets;
    [self setNeedsLayout]; //Indicates that the view needs to be laid out 
                           //at next update or at next call of layoutIfNeeded, 
                           //whichever comes first 
    [self layoutIfNeeded]; //Calls layoutSubviews if flag is set
}

इस तरह, यदि आप निम्नलिखित करते हैं, यदि आप एनीमेशन ब्लॉक के अंदर किनारे संपत्ति को बदलते हैं, तो सामग्री दृश्य का फ्रेम परिवर्तन एनिमेटेड होगा।

[UIView animateWithDuration:2 animations:^{
    customView.edgeInsets = UIEdgeInsetsMake(45, 17, 18, 34);
}];

यदि आप setEdgeInsets पद्धति में लेआउट में कॉल को जोड़ नहीं पाते हैं, तो एनीमेशन काम नहीं करेगा क्योंकि लेआउटसुब्यू को अगले अपडेट चक्र पर कॉल किया जाएगा, जो एनीमेशन ब्लॉक के बाहर कॉल करने के लिए समान है।

यदि आप setEdgeInsets विधि में केवल लेआउटIfNeeded कहते हैं, तो कुछ भी नहीं होगा क्योंकि setNeedsLayout ध्वज सेट नहीं है।


4
या फिर आपको [self layoutIfNeeded]एज इनसेट सेट करने के बाद एनीमेशन ब्लॉक में कॉल करने में सक्षम होना चाहिए ।
स्कॉट फिस्टर
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.