instantiateViewController(identifier:creator:)を試す

使ったことがないAPIはミニマムですぐ書く!

開発環境

> xcodebuild -version 
Xcode 13.3
Build version 13E113

モチベーション

instantiateViewController(identifier:creator:) を使ったことがないので使ってみたい

instantiateViewController(identifier:creator:) とは

Creates the specified view controller from the storyboard and initializes it using your custom initialization code. https://developer.apple.com/documentation/uikit/uistoryboard/3213989-instantiateviewcontroller

ViewControllerの初期化をカスタマイズできる。
どうカスタマイズできるのか。
本題に入る前に上記を使わない初期化方法を書く。

従来のViewControllerの初期化

ViewController -> SecondViewController に値を渡して遷移する場合を例にコードを書く。

ViewController.swift

import UIKit

final class ViewController: UIViewController {
    let number: Int = 1

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let vc = SecondBuilder(number: number).build()
        self.present(vc, animated: true)
    }
}

SecondViewController.swift

import UIKit

final class SecondViewController: UIViewController {
    var number: Int!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        print("number: \(number)")  // "number: Optional(1)"
    }
}

struct SecondBuilder {
    let number: Int
    
    func build() -> SecondViewController {
        let storyboard = UIStoryboard(name: "Second", bundle: nil)
        let vc = storyboard.instantiateViewController(withIdentifier: "Second") as! SecondViewController
        vc.number = number
        return vc
    }
}

所感

悪くないが、 SecondViewControllernumber を外から注入するため変数にしてしまっている。
変数をやめて定数にしたい。
そんな時に instantiateViewController(identifier:creator:) が有効だぞ!

instantiateViewController(identifier:creator:) を使って書き直す

ViewController.swift

import UIKit

final class ViewController: UIViewController {
    let number: Int = 1

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let storyboard = UIStoryboard(name: "Second", bundle: nil)
        let vc = storyboard.instantiateViewController(identifier: "Second") { coder in
            return SecondViewController(coder: coder, number: 1)
        }
        self.present(vc, animated: true)
    }
}

SecondViewController.swift

import UIKit

final class SecondViewController: UIViewController {
    let number: Int
    
    init?(coder: NSCoder, number: Int) {
        self.number = number
        super.init(coder: coder)
    }

    required init?(coder: NSCoder) {
        fatalError()
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        print("number: \(number)")  // "number: 1"
    }
}

所感

遷移先のイニシャライザを新規で生やす必要があるが、変数を定数にできるので心理的安全性は高まりそう。

import UIKit

final class SecondViewController: UIViewController {
    let viewModel: ViewModel
    
    struct ViewModel {
        let number: Int
        let hoge: String
        let fuga: String
        let piyo: String
    }
    
    init?(coder: NSCoder, viewModel: ViewModel) {
        self.viewModel = viewModel
        super.init(coder: coder)
    }

    required init?(coder: NSCoder) {
        fatalError()
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        print("number: \(viewModel.number)")
    }
}

注入したいプロパティが多い時は構造体切るなりするとイニシャライザの引数の個数も抑えられるし、iOS13以降なら使わない手はなさそう。

参考