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
.
CodePudding user response:
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
.
CodePudding user response:
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.