web-dev-qa-db-ja.com

SwiftのUIViewControllerのカスタムinitとストーリーボードのインターフェイス設定

UIViewControllerのサブクラスのカスタムinitを作成する際に問題があります。基本的に、viewControllerB.property = valueのようなプロパティを直接設定するのではなく、viewControllerのinitメソッドを介して依存関係を渡します。

そこで、viewControllerのカスタムinitを作成し、スーパー指定initを呼び出します

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

View Controllerのインターフェイスはストーリーボードにあり、カスタムクラスのインターフェイスもView Controllerになりました。また、Swiftは、このメソッド内で何も実行していない場合でも、このinitメソッドを呼び出す必要があります。そうしないと、コンパイラは文句を言います...

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

問題は、MyViewController(meme: meme)でカスタムinitを呼び出そうとすると、viewControllerのプロパティがまったく初期化されないことです...

デバッグしようとしていましたが、viewControllerでinit(coder aDecoder: NSCoder)が最初に呼び出され、その後カスタムinitが呼び出されました。ただし、これら2つのinitメソッドは、異なるselfメモリアドレスを返します。

ViewControllerのinitに何か問題があるのではないかと疑っていますが、init?(coder aDecoder: NSCoder)selfを返しますが、これには実装がありません。

誰でもviewControllerのカスタムinitを正しく作成する方法を知っていますか?注:viewControllerのインターフェイスはストーリーボードで設定されます

ここに私のviewControllerコードがあります:

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

}
64
Pigfly

上記の回答の1つで指定されているため、カスタムの初期化メソッドとストーリーボードの両方を使用することはできません。ただし、静的メソッドを使用して、ストーリーボードからViewControllerをインスタンス化し、追加のセットアップを実行できます。次のようになります。

class MemeDetailVC : UIViewController {

    var meme : Meme!

    static func makeMemeDetailVC(meme: Meme) -> MemeDetailVC {
        let newViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("IdentifierOfYouViewController") as! MemeDetailVC

        newViewController.meme = meme

        return newViewController
    }
}

ストーリーボードのView Controller識別子としてIdentifierOfYouViewControllerを指定することを忘れないでください。上記のコードでストーリーボードの名前を変更する必要がある場合もあります。

33

これを行った1つの方法は、便利な初期化子を使用することです。

class MemeDetailVC : UIViewController {

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

次に、let memeDetailVC = MemeDetailVC(theMeme)でMemeDetailVCを初期化します

初期化子に関するAppleのドキュメント は非常に良いですが、私の個人的なお気に入りは Ray Wenderlich:初期化の詳細 チュートリアルシリーズで、さまざまなinitオプションに関する説明/例がたくさんありますそして、物事を行うための「適切な」方法。


EDIT:カスタムView Controllerで便利なイニシャライザーを使用できますが、ストーリーボードから初期化するときにカスタムイニシャライザーを使用できないと誰もが言っています。またはストーリーボードセグエを通じて。

インターフェイスがストーリーボードに設定されていて、コントローラーを完全にプログラムで作成している場合、必要なinitを処理する必要がないため、コンビニイニシャライザーがおそらく最も簡単な方法です。 NSCoder(私はまだ本当に理解していません)。

ただし、ストーリーボード経由でView Controllerを取得する場合は、 @ Caleb Kleveter's answer に従って、View Controllerを目的のサブクラスにキャストしてから、プロパティを手動で設定する必要があります。

18
Ponyboy47

@Caleb Kleveterが指摘したように、ストーリーボードから初期化する際にカスタム初期化子を使用することはできません。

ただし、StoryboardからView Controllerオブジェクトをインスタンス化し、View Controllerオブジェクトを返すファクトリ/クラスメソッドを使用することで問題を解決できます。これはかなりクールな方法だと思います。

注:これは質問に対する正確な答えではなく、問題を解決するための回避策です。

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())
15
Nitesh Borad

元々はいくつかの回答があり、それらは基本的に正しいものでしたが、投票され削除されました。答えは、できません。

ストーリーボード定義から作業する場合、View Controllerインスタンスはすべてアーカイブされます。したがって、それらを初期化するには、init?(coder...を使用する必要があります。 coderは、すべての設定/ビュー情報の取得元です。

そのため、この場合、カスタムパラメータを使用して他のinit関数を呼び出すこともできません。セグエの準備時にプロパティとして設定するか、セグエを捨ててインスタンスをストーリーボードから直接ロードして構成する必要があります(基本的にはストーリーボードを使用したファクトリパターン)。

すべての場合において、SDKに必要なinit関数を使用し、その後追加パラメーターを渡します。

7
Wain

UIViewControllerクラスは、次のように定義されているNSCodingプロトコルに準拠しています。

public protocol NSCoding {

   public func encode(with aCoder: NSCoder)

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

したがって、UIViewControllerには、2つの指定された初期化子init?(coder aDecoder: NSCoder)init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?)があります。

Storyboradはinit?(coder aDecoder: NSCoder)を直接呼び出してUIViewControllerおよびUIViewを初期化し、パラメーターを渡す余地はありません。

面倒な回避策の1つは、一時キャッシュを使用することです。

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
}
1
wj2061