简体   繁体   中英

How to initialize ViewController from storyboard using it's Coder

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 .

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 .

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM