skip to Main Content

I realize that this might be difficult to achieve, but I would like to be able to initialize ViewControllers from storyboard using init(coder: NSCoder) function directly. I mean not using storyboard.instantiateViewController(identifier: coder: ) – I know I could use this, but this is what I would like to skip. Instead I would prefer to have a init in ViewController like this:
init(viewModel: ViewModel) and use this init to initialize this ViewController from the storyboard. Inside this init I imagine having some mechanism that would open my storyboard, and extracted it’s coder somehow, so that I could write:

static let storyboard = UIStoryboard(named: "Game")

private let viewModel: ViewModel

init(viewModel: ViewModel) {
    self.viewModel = viewModel
    let identifier = String(describing: Self.self)
    let coder: NSCoder = Self.storyboard.somehowGetTheCoderForViewController(withId: identifier)
    super.init(coder: coder)
}

Problem is how to read storyboard in such a way that I was able to get the coder of particular ViewController from it.
Again – I know this can be solved by using something like this

storyboard.instantiateViewController(identifier: String(describing: Self.self)) { coder in
    Self.init(coder: coder, viewModel: viewModel)
}

But Im looking for a way to not use instantiateViewController, and just be able to get the coder, so that later I could just initiate VC like this:

let viewController = ViewContorller(viewModel: viewModel)

So the question is how to unpack storyboard, and retrieve coder object for some ViewController.

2

Answers


  1. You are not supposed to do this.

    As the documentation of init(nibName:bundle:) says:

    This is the designated initializer for this class. When using a storyboard to define your view controller and its associated views, you never initialize your view controller class directly. Instead, view controllers are instantiated by the storyboard either automatically when a segue is triggered or programmatically when your app calls the instantiateViewController(withIdentifier:) method of a storyboard object.

    When initialising a VC using an initialiser, that initialiser is what you should use, not init(coder:). If you use storyboards, then you should use instantiateViewController, or use segues to get new VCs.

    So to achieve this syntax:

    let viewController = ViewContorller(viewModel: viewModel)
    

    You can put your VCs in xib files, and do:

    init(viewModel: ViewModel) {
        self.viewModel = viewModel
        let identifier = String(describing: Self.self)
        let coder: NSCoder = Self.storyboard.somehowGetTheCoderForViewController(withId: identifier)
        super.init(nibName: String(describing: Self.self), bundle: nil)
    }
    

    If you must use storyboards, then you can’t use an initialiser syntax for initialising VCs. You can instead make a factory method, such as:

    let viewController = ViewContorller.from(viewModel: viewModel)
    

    And implement it using UIStoryboard.instantiateViewController.

    Login or Signup to reply.
  2. This is why the initializer instantiateViewController(identifier:creator:) exists.

    https://developer.apple.com/documentation/uikit/uistoryboard/3213989-instantiateviewcontroller

    You receive the coder and use it to call a custom initializer that itself calls the coder initializer but also does other custom initialization.

    To illustrate, suppose we have a ViewController class with a message String property to be presented to the user in a UILabel when the view appears. So we’ve given ViewController an init(coder:message:) initializer:

    class ViewController: UIViewController {
        @IBOutlet var lab : UILabel!
        var message: String = ""
        convenience init(coder:NSCoder, message:String) {
            self.init(coder:coder)!
            self.message = message
        }
        override func viewDidLoad() {
            super.viewDidLoad()
            self.lab.text = self.message
        }
    }
    

    Left to its own devices, however, the runtime will never call our init(coder:message:) initializer; it knows nothing of it! The only way the runtime knows to instantiate a view controller from a storyboard is by calling init(coder:). But we can call init(coder:message:) when we instantiate the view controller from the storyboard.

    Suppose this is the storyboard’s initial view controller, and we’re calling it in the scene delegate at launch time:

    func scene(_ scene: UIScene, 
        willConnectTo session: UISceneSession, 
        options connectionOptions: UIScene.ConnectionOptions) {
            guard let scene = (scene as? UIWindowScene) else { return }
            let message = "Howdy, world!" // or whatever
            self.window = UIWindow(windowScene: scene)
            let sb = UIStoryboard(name: "Main", bundle: nil)
            self.window?.rootViewController = sb.instantiateInitialViewController {
                return ViewController(coder: $0, message: message)
            }
            self.window?.makeKeyAndVisible()
    }
    

    We call instantiateViewController(identifier:creator:) and the creator: function calls init(coder:message:) — which, in turn, calls init(coder:). Thus our use of the creator: function is legal, and the view appears correctly.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search