iosuikit

Why does initial viewcontroller crash with instantiateInitialViewController from the storyboard?


I’m trying to use dependency injection with instantiateInitialViewController for the initial view controller in a storyboard. My goal is to inject a ViewModel into my ViewController when it is instantiated. However, my app crashes with the error Fatal error: init(coder:) has not been implemented. Here’s my setup:

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    guard let windowScene = (scene as? UIWindowScene) else { return }
    
    window = UIWindow(windowScene: windowScene)
    let storyboard = UIStoryboard(name: "Main", bundle: nil)
    
    let viewController = storyboard.instantiateInitialViewController(creator: { coder in
        ViewController(coder: coder, viewModel: ViewModel())
    })
    
    window?.rootViewController = viewController
    window?.makeKeyAndVisible()
}

Here is the viewcontroller

final class ViewController: UIViewController {
    private let viewModel: ViewModel

    init?(coder: NSCoder, viewModel: ViewModel) {
        self.viewModel = viewModel
        super.init(coder: coder)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        viewModel.fetchUsers { users in
            print(users)
        }
    }
}

Solution

  • The problem is that even though you are fetching the storyboard yourself and instantiating the initial view controller yourself...

    let storyboard = UIStoryboard(name: "Main", bundle: nil)
    
    let viewController = storyboard.instantiateInitialViewController(creator: { coder in
        ViewController(coder: coder, viewModel: ViewModel())
    })
    

    ... nevertheless, you forgot to prevent the runtime from independently fetching the storyboard and instantiating the initial view controller. Therefore, your initial view controller is being instantiated twice — and the first time, your init?(coder:) override is called and you crash.

    To fix the problem, just go into the Info.plist and delete the line that sets the storyboard as Main:

    enter image description here

    See that line that I've highlighted? Select it and hit Delete.

    Now your app will launch successfully and you'll be doing the dependency injection you were aiming at.