Xib के साथ एक पुन: प्रयोज्य UIView बनाना (और स्टोरीबोर्ड से लोड करना)


81

ठीक है, इस बारे में StackOverflow पर दर्जनों पोस्ट हैं, लेकिन समाधान पर विशेष रूप से कोई भी स्पष्ट नहीं है। मैं UIViewएक xib फ़ाइल के साथ एक रिवाज बनाना चाहता हूँ । आवश्यकताएं हैं:

  • अलग नहीं UIViewController- एक पूरी तरह से स्व-निहित वर्ग
  • मुझे देखने के गुण सेट / प्राप्त करने की अनुमति देने के लिए कक्षा में आउटलेट

ऐसा करने के लिए मेरा वर्तमान तरीका है:

  1. अवहेलना -(id)initWithFrame:

    -(id)initWithFrame:(CGRect)frame {
        self = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
                                              owner:self
                                            options:nil] objectAtIndex:0];
        self.frame = frame;
        return self;
    }
    
  2. -(id)initWithFrame:मेरे दृश्य नियंत्रक में प्रोग्रामेटिक रूप से उपयोग करना

    MyCustomView *myCustomView = [[MyCustomView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height)];
    [self.view insertSubview:myCustomView atIndex:0];
    

यह ठीक काम करता है (हालांकि कभी भी कॉल नहीं किया जाता है [super init]और केवल लोड किए गए नीब की सामग्री का उपयोग करके ऑब्जेक्ट सेट करना थोड़ा संदिग्ध लगता है - इस मामले में एक सबव्यू जोड़ने के लिए यहां सलाह है जो ठीक भी काम करता है)। हालाँकि, मैं स्टोरीबोर्ड से भी दृश्य को तुरंत सक्षम करना चाहूंगा। ताकि मैं कर सकूं:

  1. UIViewस्टोरीबोर्ड में एक मूल दृश्य पर रखें
  2. इसके लिए कस्टम क्लास सेट करें MyCustomView
  3. ओवरराइड -(id)initWithCoder:- जिस कोड को मैंने सबसे अधिक बार देखा है, वह निम्न जैसे एक पैटर्न को फिट करता है:

    -(id)initWithCoder:(NSCoder *)aDecoder {
        self = [super initWithCoder:aDecoder];
        if (self) {
            [self initializeSubviews];
        }
        return self;
    }
    
    -(id)initWithFrame:(CGRect)frame {
        self = [super initWithFrame:frame];
        if (self) {
            [self initializeSubviews];
        }
        return self;
    }
    
    -(void)initializeSubviews {
        typeof(view) view = [[[NSBundle mainBundle]
                             loadNibNamed:NSStringFromClass([self class])
                                    owner:self
                                  options:nil] objectAtIndex:0];
        [self addSubview:view];
    }
    

बेशक, यह काम नहीं करता है, जैसे कि मैं ऊपर दिए गए दृष्टिकोण का उपयोग करता हूं, या क्या मैं प्रोग्राम के लिए त्वरित रूप से उपयोग करता हूं, दोनों पुनरावर्ती रूप से कॉल -(id)initWithCoder:करने -(void)initializeSubviewsऔर फ़ाइल से नायब को लोड करने पर कॉल करते हैं।

कई अन्य एसओ प्रश्न इस तरह के रूप में यहाँ , यहाँ , यहाँ और यहाँ के साथ सौदा । हालाँकि, दिए गए उत्तरों में से कोई भी संतोषजनक रूप से समस्या को हल नहीं करता है:

  • ऐसा लगता है कि एक सामान्य सुझाव पूरे वर्ग को एक UIViewController में एम्बेड करने के लिए है, और वहाँ नीब लोडिंग करते हैं, लेकिन यह मुझे सब-अप्टीमल लगता है क्योंकि इसमें एक रैपर के रूप में एक और फ़ाइल जोड़ने की आवश्यकता होती है

क्या कोई इस समस्या को हल करने के बारे में सलाह दे सकता है, और UIViewन्यूनतम उपद्रव / कोई पतली नियंत्रक आवरण के साथ कस्टम में काम कर रहे आउटलेट प्राप्त कर सकता है ? या क्या न्यूनतम बॉयलरप्लेट कोड के साथ चीजों को करने का एक वैकल्पिक, स्वच्छ तरीका है?


1
क्या आपको कभी इसके लिए संतोषजनक उत्तर मिला? मैं फिलहाल इसके लिए संघर्ष कर रहा हूं। अन्य सभी उत्तर काफी अच्छे नहीं लगते, जैसा कि आप उल्लेख करते हैं। यदि आपने पिछले कुछ महीनों में कुछ भी पाया है तो आप हमेशा इस सवाल का जवाब दे सकते हैं।
माइक मेयर्स

13
IOS में पुन: प्रयोज्य दृश्य बनाना इतना मुश्किल क्यों है?
घड़ी की कल


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

1
इस बहुत पुराने QA के बारे में, Apple ने आखिरकार STORYBOARD REFERENCES ... developer.apple.com/library/ios/recipes/… ... को पेश किया, ताकि यह है, भाई!
फेटी

जवाबों:


13

आपकी समस्या loadNibNamed:(एक वंशज) से पुकार रही है initWithCoder:loadNibNamed:आंतरिक रूप से कहता है initWithCoder:। यदि आप स्टोरीबोर्ड कोडर को ओवरराइड करना चाहते हैं, और हमेशा अपने एक्सिब कार्यान्वयन को लोड करते हैं, तो मैं निम्नलिखित तकनीक का सुझाव देता हूं। अपने दृश्य वर्ग में एक संपत्ति जोड़ें, और xib फ़ाइल में, इसे एक पूर्व निर्धारित मूल्य (उपयोगकर्ता निर्धारित रनटाइम विशेषताओं में) पर सेट करें। अब, कॉल करने के बाद [super initWithCoder:aDecoder];संपत्ति के मूल्य की जांच करें। यदि यह पूर्व निर्धारित मूल्य है, तो कॉल न करें [self initializeSubviews];

तो, कुछ इस तरह से:

-(instancetype)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];

    if (self && self._xibProperty != 666)
    {
        //We are in the storyboard code path. Initialize from the xib.
        self = [self initializeSubviews];

        //Here, you can load properties that you wish to expose to the user to set in a storyboard; e.g.:
        //self.backgroundColor = [aDecoder decodeObjectOfClass:[UIColor class] forKey:@"backgroundColor"];
    }

    return self;
}

-(instancetype)initializeSubviews {
    id view =   [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil] firstObject];

    return view;
}

धन्यवाद @LeoNatan! मैं इस उत्तर को स्वीकार कर रहा हूं क्योंकि यह समस्या का सबसे अच्छा समाधान है जैसा कि मूल रूप से कहा गया है। हालाँकि, ध्यान दें कि स्विफ्ट में यह अब संभव नहीं है - मैंने उस मामले में संभावित कार्य पर कुछ अलग नोट जोड़े हैं।
केन चैटफील्ड

@KenChatfield मैंने देखा कि मेरी स्विफ्ट उप परियोजना में, और इससे नाराज हो गया। मुझे यकीन नहीं है कि वे क्या सोच रहे हैं, क्योंकि इसके बिना स्विफ्ट में बहुत सारे कोको / कोको टच आंतरिक कार्यान्वयन असंभव है। मेरी शर्त है कि कुछ गतिशील विशेषताएं होंगी जब उनके पास वास्तव में बग के बजाय सुविधाओं पर ध्यान केंद्रित करने का समय होगा। स्विफ्ट बिल्कुल तैयार नहीं है, और सबसे खराब अपराधी विकास उपकरण हैं।
सिंह नटण

हाँ, आप सही हैं, स्विफ्ट में अभी बहुत सारे खुरदुरे किनारे हैं। ऐसा कहने के बाद, ऐसा लगता है कि सीधे स्वयं को असाइन करना थोड़ा हैक था, इसलिए मैं इस तरह से खुश हूं कि कंपाइलर अब इस तरह की चीजों के खिलाफ चेतावनी देता है (इस तंग प्रकार की जाँच से लगता है कि यह अच्छी चीजों में से एक है भाषा: हिन्दी)। हालाँकि, आप सही कह रहे हैं कि यह xibs के साथ कस्टम दृश्यों को जोड़ने में बहुत गड़बड़ है और थोड़ा असंतोषजनक है। आशा करते हैं कि जैसा कि आप कहते हैं कि एक बार जब वे बग्स को छाँटना समाप्त कर लेते हैं, तो हमें इस से कुछ अधिक सामान के साथ कुछ और गतिशील सुविधाएँ देखने को मिलेंगी!
केन चैटफील्ड

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

1
किसी तरह यह मेरे लिए काम नहीं किया (iOS8.1 एसडीके)। मैंने XIB में एक रनटाइम एट्रिब्यूट के बजाय एक रेस्टोरेशनइंटेंटिफायर सेट किया, इसके मुकाबले यह काम किया। उदाहरण के लिए मैं xib में "MyViewRestorationID" सेट, initWithCoder की तुलना में: मैं जाँच की है कि [[आत्म restorationIdentifier] isEqualToString: @ "MyViewRestorationID"]!
ingaham

26

ध्यान दें कि यह क्यूए (कई की तरह) वास्तव में केवल ऐतिहासिक हित है।

आजकल के वर्षों में और अब iOS में सब कुछ बस एक कंटेनर दृश्य है। पूर्ण ट्यूटोरियल यहाँ

(वास्तव में Apple ने कुछ समय पहले स्टोरीबोर्ड सन्दर्भों को अंत में जोड़ा , जिससे यह बहुत आसान हो गया।)

यहाँ हर जगह कंटेनर विचारों के साथ एक विशिष्ट स्टोरीबोर्ड है। सब कुछ एक कंटेनर दृश्य है। यह सिर्फ आप ऐप कैसे बनाते हैं।

यहाँ छवि विवरण दर्ज करें

(एक जिज्ञासा के रूप में, केसीसी का जवाब बिल्कुल दिखाता है कि, यह एक एक्सिब को एक प्रकार के आवरण दृश्य को लोड करने के लिए किया जाता था, क्योंकि आप वास्तव में "स्वयं को असाइन नहीं कर सकते हैं।")


इसके साथ समस्या यह है कि आप बहुत सारे ViewControllers को समाप्त कर देंगे, जो कि आप सभी सामग्री के विचारों को उलझा देंगे।
बोगदान ओनू

हाय @ बोगदानू! आपके पास कई, कई, कई व्यू कंट्रोलर होने चाहिए। "सबसे छोटी" चीज़ के लिए - आपके पास एक दृश्य नियंत्रक होना चाहिए।
फेटी

2
यह बहुत उपयोगी है - धन्यवाद @JoeBlow। कंटेनर दृश्यों का उपयोग निश्चित रूप से एक वैकल्पिक दृष्टिकोण लगता है, और सीधे तौर पर xibs से निपटने की सभी जटिलताओं से बचने का एक सरल तरीका है। हालाँकि, यह परियोजनाओं के वितरण / उपयोग के लिए पुन: प्रयोज्य घटकों को बनाने के लिए 100% संतोषजनक विकल्प नहीं लगता है, क्योंकि इसके लिए सभी UI डिज़ाइन को सीधे स्टोरीबोर्ड में एम्बेड करने की आवश्यकता होती है।
केन चाटफील्ड

3
मुझे इस मामले में एक अतिरिक्त ViewController के उपयोग के साथ एक समस्या है, क्योंकि इसमें सिर्फ प्रोग्राम लॉजिक होगा जो अन्यथा xib मामले में कस्टम व्यू क्लास से संबंधित होगा, लेकिन स्टोरीबोर्ड के साथ तंग युग्मन का मतलब है कि मैं सुनिश्चित नहीं है कि कंटेनर दृश्य इस समस्या को पूरी तरह से हल कर सकते हैं। शायद अधिकांश व्यावहारिक स्थितियों में दृश्य परियोजना-विशिष्ट होते हैं, और इसलिए यह सबसे अच्छा और सबसे 'मानक' समाधान है, लेकिन मुझे आश्चर्य है कि स्टोरीबोर्ड के उपयोग के लिए पैकेजिंग कस्टम विचारों का कोई आसान तरीका अभी भी नहीं है। मेरे प्रोग्रामर को अलग-अलग घटकों में विभाजित करने और जीतने के लिए आवेग खुजली है;)
केन चैटफ़ील्ड

1
इसके अलावा, Xcode 6 में कस्टम UIView उपवर्गों के लाइव रेंडरिंग की शुरुआत के साथ, मुझे यकीन नहीं है कि क्या मैं इस तरह से xibs का उपयोग करने वाले दृश्यों को बनाने वाला आधार अब खरीद रहा हूं
Ken Chatfield 1

24

मैं इसे स्विफ्ट की रिलीज़ के साथ स्थिति को अपडेट करने के लिए एक अलग पोस्ट के रूप में जोड़ रहा हूं। लियोनाटन द्वारा वर्णित दृष्टिकोण उद्देश्य-सी में पूरी तरह से काम करता है। हालाँकि, कड़ी संकलन समय जाँच selfस्विफ्ट में xib फ़ाइल से लोड करने के लिए असाइन किए जाने से रोकती है ।

परिणामस्वरूप, xib फ़ाइल से लोड किए गए दृश्य को कस्टम UIView उपवर्ग के उप-भाग के रूप में जोड़ने के बजाय, स्वयं को पूरी तरह से प्रतिस्थापित करने के अलावा कोई विकल्प नहीं है। यह मूल प्रश्न में उल्लिखित दूसरे दृष्टिकोण के अनुरूप है। इस दृष्टिकोण का उपयोग करते हुए स्विफ्ट में एक वर्ग की एक मोटी रूपरेखा इस प्रकार है:

@IBDesignable // <- to optionally enable live rendering in IB
class ExampleView: UIView {

    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        initializeSubviews()
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        initializeSubviews()
    }

    func initializeSubviews() {
        // below doesn't work as returned class name is normally in project module scope
        /*let viewName = NSStringFromClass(self.classForCoder)*/
        let viewName = "ExampleView"
        let view: UIView = NSBundle.mainBundle().loadNibNamed(viewName,
                               owner: self, options: nil)[0] as! UIView
        self.addSubview(view)
        view.frame = self.bounds
    }

}

इस दृष्टिकोण के नकारात्मक पक्ष पदानुक्रम में एक अतिरिक्त निरर्थक परत की शुरूआत है जो कि उद्देश्य-सी में लियोनाटन द्वारा उल्लिखित दृष्टिकोण का उपयोग करते समय मौजूद नहीं है। हालांकि, इसे एक आवश्यक बुराई के रूप में लिया जा सकता है और मौलिक तरीके के उत्पाद को Xcode में डिज़ाइन किया गया है (यह अभी भी मुझे पागल लगता है कि कस्टम यूआईवीयूवी क्लास को यूआई लेआउट के साथ जोड़ना एक तरह से मुश्किल है जो लगातार काम करता है दोनों स्टोरीबोर्ड और कोड से अधिक) - selfशुरुआती से पहले थोक में प्रतिस्थापित करना ऐसा कभी नहीं किया गया जैसे कि चीजों को करने का एक विशेष रूप से व्याख्या करने योग्य तरीका है, हालांकि अनिवार्य रूप से प्रति दृश्य दो वर्ग देखने में इतने शानदार नहीं लगते हैं।

बहरहाल, इस दृष्टिकोण का एक सुखद परिणाम यह है कि अब हमें असाइनमेंट करते समय सही व्यवहार सुनिश्चित करने के लिए इंटरफ़ेस बिल्डर में हमारी क्लास फ़ाइल में व्यू की कस्टम क्लास को सेट करने की आवश्यकता नहीं है self, और इसलिए init(coder aDecoder: NSCoder)जारी करने के लिए पुनरावर्ती कॉल जारी करते समय loadNibNamed()(सेट करके) xib फ़ाइल में कस्टम क्लास, init(coder aDecoder: NSCoder)हमारे कस्टम संस्करण के बजाय सादे वेनिला UIView की बजाय) कहा जाएगा।

भले ही हम सीधे xib में संग्रहीत दृश्य में वर्ग अनुकूलन नहीं कर सकते हैं, फिर भी हम अपने कस्टम वर्ग के लिए दृश्य के फ़ाइल स्वामी को सेट करने के बाद आउटलेट / क्रियाओं आदि का उपयोग करके अपने 'माता-पिता' UIView उपवर्ग में दृश्य को लिंक करने में सक्षम हैं:

कस्टम दृश्य की फ़ाइल स्वामी गुण सेट करना

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


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

इसके लिए धन्यवाद। मैंने सफलता के बिना स्विफ्ट में कई अलग-अलग तरीकों की कोशिश की जब तक कि मैंने नीब की कक्षा छोड़ने के बारे में आपकी सलाह पर ध्यान नहीं दिया UIView। मैं मानता हूँ कि यह पागल है कि Apple ने कभी यह आसान नहीं किया है, और अब यह लगभग असंभव है। एक कंटेनर हमेशा जवाब नहीं होता है।
इकोलोन

16

चरण 1। selfस्टोरीबोर्ड से प्रतिस्थापित करना

निम्नलिखित त्रुटि के साथ विधि selfमें प्रतिस्थापित initWithCoder:करना विफल हो जाएगा।

'NSGenericException', reason: 'This coder requires that replaced objects be returned from initWithCoder:'

इसके बजाय, आप डिकोड किए गए ऑब्जेक्ट को awakeAfterUsingCoder:(नहीं awakeFromNib) के साथ बदल सकते हैं । पसंद:

@implementation MyCustomView
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
    return [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
                                          owner:nil
                                        options:nil] objectAtIndex:0];
}
@end

चरण 2। पुनरावर्ती कॉल को रोकना

बेशक, यह भी पुनरावर्ती कॉल समस्या का कारण बनता है। (स्टोरीबोर्ड डिकोडिंग -> awakeAfterUsingCoder:-> loadNibNamed:-> awakeAfterUsingCoder:-> loadNibNamed:-> ...)
तो आप वर्तमान awakeAfterUsingCoder:की जाँच करने के लिए Storyboard डिकोडिंग प्रक्रिया या XIB डिकोडिंग प्रक्रिया में कहा जाता है। आपके पास ऐसा करने के कई तरीके हैं:

a) निजी का उपयोग करें @propertyजो केवल NIB में सेट किया गया है।

@interface MyCustomView : UIView
@property (assign, nonatomic) BOOL xib
@end

और "MyCustomView.xib 'में केवल" उपयोगकर्ता परिभाषित रनटाइम अटेंडेस "सेट करें।

पेशेवरों:

  • कोई नहीं

विपक्ष:

  • बस काम नहीं करता है: AFTERsetXib: कहा जाएगा awakeAfterUsingCoder:

बी) अगर selfकोई भी साक्षात्कार है, तो जाँच करें

आम तौर पर, आपके पास xib में साक्षात्कार होते हैं, लेकिन स्टोरीबोर्ड में नहीं।

- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
    if(self.subviews.count > 0) {
        // loading xib
        return self;
    }
    else {
        // loading storyboard
        return [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
                                              owner:nil
                                            options:nil] objectAtIndex:0];
    }
}

पेशेवरों:

  • इंटरफ़ेस बिल्डर में कोई चाल नहीं।

विपक्ष:

  • आपके स्टोरीबोर्ड में आपके साक्षात्कार नहीं हो सकते हैं।

ग) loadNibNamed:कॉल के दौरान एक स्थिर ध्वज सेट करें

static BOOL _loadingXib = NO;

- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
    if(_loadingXib) {
        // xib
        return self;
    }
    else {
        // storyboard
        _loadingXib = YES;
        typeof(self) view = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
                                                           owner:nil
                                                         options:nil] objectAtIndex:0];
        _loadingXib = NO;
        return view;
    }
}

पेशेवरों:

  • सरल
  • इंटरफ़ेस बिल्डर में कोई चाल नहीं।

विपक्ष:

  • सुरक्षित नहीं: स्थिर साझा ध्वज खतरनाक है

d) XIB में निजी उपवर्ग का उपयोग करें

उदाहरण के लिए, _NIB_MyCustomViewउपवर्ग के रूप में घोषित करें MyCustomView। और, केवल अपने XIB के _NIB_MyCustomViewबजाय का उपयोग करें MyCustomView

MyCustomView.h:

@interface MyCustomView : UIView
@end

MyCustomView.m:

#import "MyCustomView.h"

@implementation MyCustomView
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
    // In Storyboard decoding path.
    return [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
                                          owner:nil
                                        options:nil] objectAtIndex:0];
}
@end

@interface _NIB_MyCustomView : MyCustomView
@end

@implementation _NIB_MyCustomView
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
    // In XIB decoding path.
    // Block recursive call.
    return self;
}
@end

पेशेवरों:

  • कोई स्पष्ट ifमेंMyCustomView

विपक्ष:

  • Xib _NIB_इंटरफ़ेस बिल्डर में प्रीफ़िक्सिंग ट्रिक
  • अपेक्षाकृत अधिक कोड

ई) स्टोरीबोर्ड में प्लेसहोल्डर के रूप में उपवर्ग का उपयोग करें

करने के लिए इसी तरह की d)है, लेकिन स्टोरीबोर्ड, XIB में मूल कक्षा में उपयोग उपवर्ग।

यहाँ, हम MyCustomViewProtoएक उपवर्ग के रूप में घोषित करते हैं MyCustomView

@interface MyCustomViewProto : MyCustomView
@end
@implementation MyCustomViewProto
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
    // In storyboard decoding
    // Returns MyCustomView loaded from NIB.
    return [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self superclass])
                                          owner:nil
                                        options:nil] objectAtIndex:0];
}
@end

पेशेवरों:

  • बहुत सुरक्षित
  • स्वच्छ; में कोई अतिरिक्त कोड नहीं MyCustomView
  • कोई स्पष्ट ifजाँच के समान नहींd)

विपक्ष:

  • स्टोरीबोर्ड में उपवर्ग का उपयोग करने की आवश्यकता है।

मुझे लगता e)है कि यह सबसे सुरक्षित और स्वच्छ रणनीति है। इसलिए हम इसे यहां अपनाते हैं।

चरण 3। गुणों की प्रतिलिपि बनाएँ

बाद loadNibNamed:में 'awakeAfterUsingCoder:', आप कई गुणों से नकल करने के लिए है selfजो उदाहरण डीकोड च स्टोरीबोर्ड। frameऔर ऑटोलॉययट / ऑटोरेसेज़ गुण विशेष रूप से महत्वपूर्ण हैं।

- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
    typeof(self) view = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
                                                       owner:nil
                                                     options:nil] objectAtIndex:0];
    // copy layout properities.
    view.frame = self.frame;
    view.autoresizingMask = self.autoresizingMask;
    view.translatesAutoresizingMaskIntoConstraints = self.translatesAutoresizingMaskIntoConstraints;

    // copy autolayout constraints
    NSMutableArray *constraints = [NSMutableArray array];
    for(NSLayoutConstraint *constraint in self.constraints) {
        id firstItem = constraint.firstItem;
        id secondItem = constraint.secondItem;
        if(firstItem == self) firstItem = view;
        if(secondItem == self) secondItem = view;
        [constraints addObject:[NSLayoutConstraint constraintWithItem:firstItem
                                                            attribute:constraint.firstAttribute
                                                            relatedBy:constraint.relation
                                                               toItem:secondItem
                                                            attribute:constraint.secondAttribute
                                                           multiplier:constraint.multiplier
                                                             constant:constraint.constant]];
    }

    // move subviews
    for(UIView *subview in self.subviews) {
        [view addSubview:subview];
    }
    [view addConstraints:constraints];

    // Copy more properties you like to expose in Storyboard.

    return view;
}

अंतिम समाधान

जैसा कि आप देख सकते हैं, यह बॉयलरप्लेट कोड का एक सा है। हम उन्हें 'श्रेणी' के रूप में लागू कर सकते हैं। यहां, मैं आमतौर पर उपयोग किए जाने वाले UIView+loadFromNibकोड का विस्तार करता हूं ।

#import <UIKit/UIKit.h>

@interface UIView (loadFromNib)
@end

@implementation UIView (loadFromNib)

+ (id)loadFromNib {
    return [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass(self)
                                          owner:nil
                                        options:nil] objectAtIndex:0];
}

- (void)copyPropertiesFromPrototype:(UIView *)proto {
    self.frame = proto.frame;
    self.autoresizingMask = proto.autoresizingMask;
    self.translatesAutoresizingMaskIntoConstraints = proto.translatesAutoresizingMaskIntoConstraints;
    NSMutableArray *constraints = [NSMutableArray array];
    for(NSLayoutConstraint *constraint in proto.constraints) {
        id firstItem = constraint.firstItem;
        id secondItem = constraint.secondItem;
        if(firstItem == proto) firstItem = self;
        if(secondItem == proto) secondItem = self;
        [constraints addObject:[NSLayoutConstraint constraintWithItem:firstItem
                                                            attribute:constraint.firstAttribute
                                                            relatedBy:constraint.relation
                                                               toItem:secondItem
                                                            attribute:constraint.secondAttribute
                                                           multiplier:constraint.multiplier
                                                             constant:constraint.constant]];
    }
    for(UIView *subview in proto.subviews) {
        [self addSubview:subview];
    }
    [self addConstraints:constraints];
}

इसका उपयोग करके, आप इस MyCustomViewProtoतरह की घोषणा कर सकते हैं:

@interface MyCustomViewProto : MyCustomView
@end

@implementation MyCustomViewProto
- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
    MyCustomView *view = [MyCustomView loadFromNib];
    [view copyPropertiesFromPrototype:self];

    // copy additional properties as you like.

    return view;
}
@end

XIB:

XIB स्क्रीनशॉट

स्टोरीबोर्ड:

स्टोरीबोर्ड

परिणाम:

यहाँ छवि विवरण दर्ज करें


3
प्रारंभिक समस्या की तुलना में समाधान अधिक जटिल है। पुनरावर्ती लूप को रोकने के लिए आपको सामग्री दृश्य को MyCustomView वर्ग प्रकार के रूप में घोषित करने के बजाय फ़ाइल के स्वामी ऑब्जेक्ट को सेट करना होगा।
बोगदान ओनू

यह सिर्फ एक) सरल प्रारंभिक प्रक्रिया की प्रक्रिया है, लेकिन जटिल दृश्य पदानुक्रम और ख) जटिल प्रारंभिक प्रक्रिया लेकिन सरल दृश्य पदानुक्रम है। n'est-ce pas? ;)
रिंटारो

क्या इस परियोजना के लिए कोई डाउनलोड लिंक है?
कार्तिकेयन

13

मत भूलना

दो महत्वपूर्ण बिंदु:

  1. अपने कस्टम दृश्य के वर्ग नाम के लिए .xib के फ़ाइल स्वामी को सेट करें।
  2. .Xib के मूल दृश्य के लिए IB में कस्टम वर्ग नाम सेट न करें

पुन: प्रयोज्य दृश्य बनाना सीखते हुए मैं कई बार इस प्रश्नोत्तर पृष्ठ पर आया। उपरोक्त बिंदुओं को भूल जाने से मुझे यह पता लगाने में बहुत समय बर्बाद हो गया कि क्या अनंत पुनरावृत्ति हो रही थी। इन बिंदुओं का उल्लेख अन्य उत्तरों में यहां और अन्य जगहों पर किया गया है , लेकिन मैं उन्हें यहां पुनर्मूल्यांकन करना चाहता हूं।

चरणों के साथ मेरा पूरा स्विफ्ट उत्तर यहां है


2

एक समाधान है जो उपरोक्त समाधानों की तुलना में बहुत अधिक स्वच्छ है: https://www.youtube.com/watch?v=xP7YvdlhHfA

कोई रनटाइम गुण, कोई पुनरावर्ती कॉल समस्या। मैंने इसकी कोशिश की और यह स्टोरीबोर्ड से और XIB से IBOutlet गुणों (iOS8.1, XCM6) के साथ एक आकर्षण की तरह काम किया।

कोडिंग के लिए गुड लक!


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

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

मेरा मानना ​​है कि एक निरर्थक दृश्य पूरी तरह से और पूरी तरह से प्राकृतिक है, और कोई दूसरा समाधान नहीं हो सकता है। ध्यान दें कि आप "'कुछ' 'यहाँ' 'जा रहे हैं ..." कि "यहाँ" एक प्राकृतिक मौजूदा चीज़ है। " वहाँ बस "कुछ" होना चाहिए, एक "जगह आप चीजों को डालने जा रहे हैं" - एक "दृश्य" की बहुत परिभाषा। और प्रतीक्ष करो! Apple का "कंटेनर दृश्य" सामान वास्तव में, ठीक है कि .. वहाँ एक "फ्रेम", एक "धारक दृश्य" ("कंटेनर दृश्य") है, जिसमें आप किसी चीज़ को देखते हैं। वास्तव में, 'निरर्थक' दृश्य समाधान ठीक हैं, एक हाथ से निर्मित कंटेनर दृश्य! बस Apple का उपयोग करें।
फेटी
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.