स्टोरीबोर्ड में इंटरफ़ेस सेटअप के साथ स्विफ्ट में UIViewController के लिए कस्टम init


89

मैं UIViewController के उपवर्ग के लिए कस्टम इनिट लिखने के लिए समस्या हो रही है, मूल रूप से मैं सीधे संपत्ति सेट करने के बजाय viewController के लिए init विधि के माध्यम से निर्भरता पारित करना चाहता हूं viewControllerB.property = value

इसलिए मैंने अपने viewController के लिए एक कस्टम init बनाया और सुपर नामित init कॉल किया

init(meme: Meme?) {
        self.meme = meme
        super.init(nibName: nil, bundle: nil)
    }

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

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

समस्या यह है कि जब मैं अपने कस्टम init को कॉल करने की कोशिश MyViewController(meme: meme)करता हूं, तो यह मेरे viewController में बिल्कुल भी इनिट गुण नहीं है ...

मैं डिबग करने की कोशिश कर रहा था, मैंने अपने व्यू कॉन्ट्रोलर में पाया, init(coder aDecoder: NSCoder)पहले बुलाया, फिर मेरे कस्टम इनिट को बाद में बुलाया गया। हालाँकि ये दो init मेथड अलग अलग selfमेमोरी एड्रेस को लौटाते हैं ।

मुझे अपने व्यू-कॉन्ट्रोलर के लिए init के साथ कुछ गलत होने का संदेह है, और यह हमेशा selfउस के साथ वापस आ जाएगा init?(coder aDecoder: NSCoder), जिसका कोई कार्यान्वयन नहीं है।

क्या किसी को पता है कि आपके दृष्टिकोण के लिए कस्टम init कैसे सही ढंग से बनाने के लिए? नोट: मेरा दृश्यकंट्रोलर का इंटरफ़ेस स्टोरीबोर्ड में सेट है

यहाँ मेरा विचार कोड कोड है:

class MemeDetailVC : UIViewController {

    var meme : Meme!

    @IBOutlet weak var editedImage: UIImageView!

    // TODO: incorrect init
    init(meme: Meme?) {
        self.meme = meme
        super.init(nibName: nil, bundle: nil)
    }

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

    override func viewDidLoad() {
        /// setup nav title
        title = "Detail Meme"

        super.viewDidLoad()
    }

    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        editedImage = UIImageView(image: meme.editedImage)
    }

}

क्या आपको इसके लिए कोई समाधान मिला?
user481610

जवाबों:


50

जैसा कि ऊपर दिए गए उत्तरों में से एक में निर्दिष्ट किया गया था, आप दोनों और कस्टम इनिट विधि और स्टोरीबोर्ड का उपयोग नहीं कर सकते हैं।

लेकिन आप अभी भी ViewControllerएक स्टोरीबोर्ड से इंस्टेंट करने के लिए एक स्थिर विधि का उपयोग कर सकते हैं और उस पर अतिरिक्त सेटअप कर सकते हैं।

यह इस तरह दिखेगा:

class MemeDetailVC : UIViewController {
    
    var meme : Meme!
    
    static func makeMemeDetailVC(meme: Meme) -> MemeDetailVC {
        let newViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "IdentifierOfYouViewController") as! MemeDetailVC
        
        newViewController.meme = meme
        
        return newViewController
    }
}

अपने स्टोरीबोर्ड में दृश्य नियंत्रक पहचानकर्ता के रूप में IdentifierOfYouViewController निर्दिष्ट करने के लिए मत भूलना। आपको ऊपर दिए गए कोड में स्टोरीबोर्ड का नाम भी बदलना पड़ सकता है।


MemeDetailVC शुरू होने के बाद, संपत्ति "मेम" मौजूद है। फिर "मेम" के लिए मान असाइन करने के लिए निर्भरता इंजेक्शन आता है। इस समय, loadView () और viewDidLoad को कॉल नहीं किया गया है। पदानुक्रम को देखने के लिए MemeDetailVC ऐड / पुश के बाद उन दो तरीकों को बुलाया जाएगा।
जिम यू

सबसे अच्छा जवाब यहाँ!
पास्कल

फिर भी init विधि की आवश्यकता है, और संकलक कहेंगे मेम संग्रहित संपत्ति को शुरू नहीं किया गया है
सुरेंद्र कुमार

27

जब आप एक स्टोरीबोर्ड से इनिशियलाइज़ करते हैं, तो आप एक कस्टम इनिशलाइज़र का उपयोग नहीं कर सकते हैं, init?(coder aDecoder: NSCoder)इस तरह से Apple ने एक कंट्रोलर को इनिशियलाइज़ करने के लिए स्टोरीबोर्ड को डिज़ाइन किया है। हालाँकि, डेटा भेजने के तरीके ए हैं UIViewController

आपके दृश्य नियंत्रक का नाम इसमें detailहै, इसलिए मुझे लगता है कि आप एक अलग नियंत्रक से प्राप्त करते हैं। इस मामले में आप prepareForSegueविस्तार से डेटा भेजने के लिए विधि का उपयोग कर सकते हैं (यह स्विफ्ट 3 है):

override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) {
    if segue.identifier == "identifier" {
        if let controller = segue.destinationViewController as? MemeDetailVC {
            controller.meme = "Meme"
        }
    }
}

मैंने परीक्षण प्रयोजनों Stringके Memeलिए केवल प्रकार की संपत्ति का उपयोग किया । इसके अलावा, सुनिश्चित करें कि आप सही सेग आइडेंटिफ़ायर ( "identifier"केवल एक प्लेसहोल्डर) थे।


1
नमस्ते, लेकिन हम वैकल्पिक के रूप में निर्दिष्ट करने से कैसे छुटकारा पा सकते हैं और हम इसे निर्दिष्ट नहीं करने की गलती भी नहीं करना चाहते हैं। हम केवल नियंत्रक के लिए सख्त निर्भरता को पहले स्थान पर रखना चाहते हैं।
अंबर के

1
@AmberK आप इंटरफ़ेस बिल्डर का उपयोग करने के बजाय अपने इंटरफ़ेस को देखना चाहते हैं।
कालेब क्लेवेटर

ठीक है, im भी संदर्भ के लिए किकस्टार्टर आईओएस प्रोजेक्ट देख रहा है, अभी तक यह बताने में सक्षम नहीं है कि वे अपने दृश्य मॉडल कैसे भेजते हैं।
अंबर के

23

जैसा कि @Caleb Kleveter ने बताया है, हम स्टोरीबोर्ड से आरंभ करते समय एक कस्टम इनिशियलाइज़र का उपयोग नहीं कर सकते हैं।

लेकिन, हम फैक्ट्री / क्लास पद्धति का उपयोग करके समस्या को हल कर सकते हैं जो स्टोरीबोर्ड से व्यू कंट्रोलर ऑब्जेक्ट को इंस्टेंट करता है और व्यू कंट्रोलर ऑब्जेक्ट को वापस करता है। मुझे लगता है कि यह एक बहुत अच्छा तरीका है।

ध्यान दें: यह प्रश्न का सटीक उत्तर नहीं है, बल्कि समस्या को हल करने के लिए समाधान है।

MemeDetailVC वर्ग में वर्ग विधि इस प्रकार बनाएं:

// Considering your view controller resides in Main.storyboard and it's identifier is set to "MemeDetailVC"
class func `init`(meme: Meme) -> MemeDetailVC? {
    let storyboard = UIStoryboard(name: "Main", bundle: nil)
    let vc = storyboard.instantiateViewController(withIdentifier: "MemeDetailVC") as? MemeDetailVC
    vc?.meme = meme
    return vc
}

उपयोग:

let memeDetailVC = MemeDetailVC.init(meme: Meme())

5
लेकिन फिर भी दोष यह है कि, संपत्ति को वैकल्पिक होना चाहिए।
अंबर के

class func init (meme: Meme) -> MemeDetailVCXcode 10.2 का उपयोग करते समय उलझन में है और त्रुटि देता हैIncorrect argument label in call (have 'meme:', expected 'coder:')
Samuël

21

एक तरीका जो मैंने किया है वह एक सुविधा आरंभीकरण के साथ है।

class MemeDetailVC : UIViewController {

    convenience init(meme: Meme) {
        self.init()
        self.meme = meme
    }
}

फिर आप अपने MemeDetailVC के साथ आरंभ करते हैं let memeDetailVC = MemeDetailVC(theMeme)

इनिशियलाइज़र पर ऐप्पल का प्रलेखन बहुत अच्छा है, लेकिन मेरा व्यक्तिगत पसंदीदा रे वेंडरलिच है: प्रारंभिक ट्यूटोरियल सीरीज़ में प्रारंभिक विवरण जो आपको अपने विभिन्न init विकल्पों और चीजों को करने के "उचित" तरीके पर बहुत सारे स्पष्टीकरण / उदाहरण देना चाहिए।


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

यदि आपका इंटरफ़ेस स्टोरीबोर्ड में सेट किया गया है और आप पूरी तरह से प्रोग्रामर को कंट्रोलर बना रहे हैं, तो एक सुविधा आरंभीक संभवत: सबसे आसान तरीका है जिसे आप करने की कोशिश कर रहे हैं क्योंकि आपको आवश्यक init से निपटना नहीं है। NSCoder (जिसे मैं अभी भी वास्तव में नहीं समझता हूं)।

यदि आप स्टोरीबोर्ड के माध्यम से अपना व्यू कंट्रोलर प्राप्त कर रहे हैं, तो आपको @Caleb Kleveter के उत्तर का पालन ​​करना होगा और व्यू कंट्रोलर को अपने वांछित सबक्लास में डालना होगा, फिर प्रॉपर्टी को मैन्युअल रूप से सेट करें।


1
मुझे यह नहीं मिलता है, अगर मेरा इंटरफ़ेस स्टोरीबोर्ड में सेटअप है और yea im इसे प्रोग्रामेटिक रूप से बना रहा है, तो मुझे इंटरफ़ेस को सही लोड करने के लिए स्टोरीबोर्ड का उपयोग करके तत्काल विधि को कॉल करना होगा? यहाँ कैसे convenienceमदद मिलेगी
अम्बर के

कक्षा के दूसरे initफ़ंक्शन को कॉल करके स्विफ्ट में कक्षाएं आरंभ नहीं की जा सकती हैं (हालांकि संरचनाएं)। इसलिए कॉल करने के लिए self.init(), आपको init(meme: Meme)एक convenienceइनिलाइज़र के रूप में चिह्नित करना होगा । अन्यथा, आपको UIViewControllerप्रारंभ में अपने आप में सभी आवश्यक गुणों को मैन्युअल रूप से सेट करना होगा , और मुझे यकीन नहीं है कि वे सभी गुण क्या हैं।
पोनबॉय 4747

यह तब UIViewController को उपवर्गित नहीं कर रहा है, क्योंकि आप self.init () और सुपरइनिट () नहीं कह रहे हैं। आप अभी भी डिफ़ॉल्ट init का उपयोग करके MemeDetailVC को इनिशियलाइज़ कर सकते हैं MemeDetail()और उस स्थिति में कोड क्रैश होगा
अली

इसे अभी भी उपवर्ग माना जाता है क्योंकि self.init () को इसके कार्यान्वयन में super.init () को कॉल करना होगा या शायद self.init () को सीधे माता-पिता से विरासत में मिला होगा जिस स्थिति में वे कार्यात्मक रूप से समकक्ष हैं।
पोनोबायो 47

6

मूल रूप से कुछ उत्तर थे, जो गाय के वोट थे और हटा दिए गए थे, भले ही वे मूल रूप से सही थे। जवाब है, आप नहीं कर सकते।

स्टोरीबोर्ड परिभाषा से काम करते समय आपके दृश्य नियंत्रक उदाहरण सभी संग्रहीत होते हैं। अतः, init?(coder...इनका उपयोग करने के लिए यह आवश्यक है कि इसका उपयोग किया जाए। coder है जहां से सभी सेटिंग्स / दृश्य जानकारी आती है।

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

सभी मामलों में आप एसडीके आवश्यक इनिट फ़ंक्शन का उपयोग करते हैं और बाद में अतिरिक्त पैरामीटर पास करते हैं।


4

स्विफ्ट 5

आप इस तरह से कस्टम इनिशियलाइज़र लिख सकते हैं ->

class MyFooClass: UIViewController {

    var foo: Foo?

    init(with foo: Foo) {
        self.foo = foo
        super.init(nibName: nil, bundle: nil)
    }

    public required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.foo = nil
    }
}

3

UIViewControllerवर्ग NSCodingप्रोटोकॉल के अनुरूप है जिसे निम्न के रूप में परिभाषित किया गया है:

public protocol NSCoding {

   public func encode(with aCoder: NSCoder)

   public init?(coder aDecoder: NSCoder) // NS_DESIGNATED_INITIALIZER
}    

तो UIViewControllerदो निर्दिष्ट प्रारंभ करनेवाला init?(coder aDecoder: NSCoder)और है init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?)

स्टोरीबोर init?(coder aDecoder: NSCoder)सीधे init UIViewControllerऔर कहते हैं UIView, आपके पास मापदंडों को पारित करने के लिए कोई जगह नहीं है।

अस्थायी कैश का उपयोग करने के लिए एक बोझिल काम है:

class TempCache{
   static let sharedInstance = TempCache()

   var meme: Meme?
}

TempCache.sharedInstance.meme = meme // call this before init your ViewController    

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder);
    self.meme = TempCache.sharedInstance.meme
}

2

IOS 13 के रूप में आप व्यू कंट्रोलर को इनिशियलाइज़ कर सकते हैं जो कि स्टोरीबोर्ड में रहता है: उदाहरण instantiateViewController(identifier:creator:)के UIStoryboardलिए विधि ।

ट्यूटोरियल: https://sarunw.com/posts/better-d dependency-injection-for-storyboards-in-ios //


1

सही प्रवाह है, निर्दिष्ट आरंभीकरण को कॉल करें जो इस मामले में निबनाम के साथ एक init है,

init(tap: UITapGestureRecognizer)
{
    // Initialise the variables here


    // Call the designated init of ViewController
    super.init(nibName: nil, bundle: nil)

    // Call your Viewcontroller custom methods here

}

0

डिस्क्लेमर: मैं इसके लिए वकालत नहीं करता हूं और इसकी लचीलापन की पूरी तरह से जांच नहीं की गई है, लेकिन यह एक संभावित समाधान है जिसे मैंने आसपास खेलते समय खोजा था।

तकनीकी रूप से, कस्टम इनिशियलाइज़ेशन को दो बार व्यू कंट्रोलर को इनिशियलाइज़ करके स्टोरीबोर्ड-कॉन्फ़िगर किए गए इंटरफ़ेस को संरक्षित करते हुए प्राप्त किया जा सकता है: पहली बार आपके कस्टम के माध्यम से init, और दूसरी बार loadView()जहाँ आप स्टोरीबोर्ड से व्यू लेते हैं।

final class CustomViewController: UIViewController {
  @IBOutlet private weak var label: UILabel!
  @IBOutlet private weak var textField: UITextField!

  private let foo: Foo!

  init(someParameter: Foo) {
    self.foo = someParameter
    super.init(nibName: nil, bundle: nil)
  }

  override func loadView() {
    //Only proceed if we are not the storyboard instance
    guard self.nibName == nil else { return super.loadView() }

    //Initialize from storyboard
    let storyboard = UIStoryboard(name: "Main", bundle: nil)
    let storyboardInstance = storyboard.instantiateViewController(withIdentifier: "CustomVC") as! CustomViewController

    //Remove view from storyboard instance before assigning to us
    let storyboardView = storyboardInstance.view
    storyboardInstance.view.removeFromSuperview()
    storyboardInstance.view = nil
    self.view = storyboardView

    //Receive outlet references from storyboard instance
    self.label = storyboardInstance.label
    self.textField = storyboardInstance.textField
  }

  required init?(coder: NSCoder) {
    //Must set all properties intended for custom init to nil here (or make them `var`s)
    self.foo = nil
    //Storyboard initialization requires the super implementation
    super.init(coder: coder)
  }
}

अब आपके ऐप में कहीं और आप अपने कस्टम इनिशलाइज़र को कॉल कर सकते हैं CustomViewController(someParameter: foo)और फिर भी स्टोरीबोर्ड से व्यू कॉन्फ़िगरेशन प्राप्त कर सकते हैं।

मैं कई कारणों से इसे एक महान समाधान नहीं मानता:

  • ऑब्जेक्ट इनिशियलाइज़ेशन को डुप्लिकेट किया गया है, जिसमें किसी भी प्री-इनिट गुण शामिल हैं
  • कस्टम में पारित पैरामीटर initको वैकल्पिक गुणों के रूप में संग्रहीत किया जाना चाहिए
  • बॉयलरप्लेट जोड़ता है जिसे आउटलेट / संपत्तियों के रूप में बनाए रखा जाना चाहिए

शायद आप इन ट्रेडऑफ़ को स्वीकार कर सकते हैं, लेकिन अपने जोखिम पर उपयोग करें


0

हालाँकि अब हम स्टोरीबोर्ड का उपयोग करने वाले डिफ़ॉल्ट नियंत्रकों के instantiateInitialViewController(creator:)लिए रिलेशनशिप और शो सहित सेगमेंट के लिए कस्टम इनिट कर सकते हैं ।

यह क्षमता Xcode 11 में जोड़ी गई थी और निम्नलिखित Xcode 11 रिलीज़ नोट्स का एक अंश है :

नई @IBSegueActionविशेषता के साथ एनोटेट की गई व्यू कंट्रोलर विधि का उपयोग कोड में किसी सेगमेंट के डेस्टिनेशन व्यू कंट्रोलर को बनाने के लिए किया जा सकता है, जिसमें किसी भी आवश्यक मान के साथ कस्टम इनिशलाइज़र का उपयोग किया जा सकता है। यह स्टोरीबोर्ड में गैर-वैकल्पिक आरंभीकरण आवश्यकताओं के साथ दृश्य नियंत्रकों का उपयोग करना संभव बनाता है। @IBSegueActionअपने स्रोत दृश्य नियंत्रक पर एक सेगमेंट से एक विधि के लिए एक कनेक्शन बनाएँ । नए OS संस्करण जो कि सेग्यूशन क्रियाओं का समर्थन करते हैं, उस पद्धति को कॉल किया जाएगा और यह जो रिटर्न देता destinationViewControllerहै वह सेगमेंट ऑब्जेक्ट के लिए पारित हो जाएगा prepareForSegue:sender:@IBSegueActionएक एकल स्रोत दृश्य नियंत्रक पर कई तरीकों को परिभाषित किया जा सकता है, जो सेगमेंट आइडेंटिफायर स्ट्रिंग्स की जांच करने की आवश्यकता को कम कर सकता है prepareForSegue:sender:। (47091566)

एक IBSegueActionविधि तीन मापदंडों तक ले जाती है: एक कोडर, प्रेषक और सेगू का पहचानकर्ता। पहले पैरामीटर की आवश्यकता होती है, और यदि वांछित हो तो अन्य पैरामीटर को आपके विधि के हस्ताक्षर से छोड़ा जा सकता है। NSCoderसुनिश्चित करने के लिए यह स्टोरीबोर्ड में विन्यस्त मूल्यों के साथ अनुकूलित है गंतव्य दृश्य नियंत्रक के प्रारंभकर्ता करने के माध्यम से पारित किया जाना चाहिए,। विधि एक दृश्य नियंत्रक देता है जो स्टोरीबोर्ड में परिभाषित गंतव्य नियंत्रक प्रकार से मेल खाता है, या nilएक गंतव्य नियंत्रक को मानक init(coder:)विधि के साथ आरंभ करने का कारण बनता है । यदि आप जानते हैं कि आपको लौटने की आवश्यकता नहीं है nil, तो वापसी प्रकार गैर-वैकल्पिक हो सकता है।

स्विफ्ट में, @IBSegueActionविशेषता जोड़ें :

@IBSegueAction
func makeDogController(coder: NSCoder, sender: Any?, segueIdentifier: String?) -> ViewController? {
    PetController(
        coder: coder,
        petName:  self.selectedPetName, type: .dog
    )
}

ऑब्जेक्टिव-सी में, IBSegueActionरिटर्न टाइप के सामने जोड़ें :

- (IBSegueAction ViewController *)makeDogController:(NSCoder *)coder
               sender:(id)sender
      segueIdentifier:(NSString *)segueIdentifier
{
   return [PetController initWithCoder:coder
                               petName:self.selectedPetName
                                  type:@"dog"];
}

-1

// व्यू कंट्रोलर मेनस्टोरीबोर्ड में है और इसमें आइडेंटिफायर सेट है

कक्षा बी

class func customInit(carType:String) -> BViewController 

{

let storyboard = UIStoryboard(name: "Main", bundle: nil)

let objClassB = storyboard.instantiateViewController(withIdentifier: "BViewController") as? BViewController

    print(carType)
    return objClassB!
}

कक्षा

let objB = customInit(carType:"Any String")

 navigationController?.pushViewController(objB,animated: true)
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.