I am working on an assignment for iOS project, and one of business logic rules is to let view controller receive id
as an input parameter, and I don't get what it means.
The id
is a JSON object field like below:
{
"id": 1213213,
"anotherKey:12322123,
"andAnotherKey: {
"keyInKey":123121
}
,
...
}
And my guess is open a view controller with custom init like below:
init(id: String) {
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
}
Is my understanding likely what the assignment intends? If so, how do I instantiate this view controller from SceneDelegate
? I've adopted MVVM pattern and the scene delegate doesn't look like a great place to leave view model. Any advice would be grateful!
Is my understanding likely what the assignment intends?
No. If you are using MVVM, I would rather set the string value to viewModel, and inject the fully configured viewModel as a dependency to ViewController (called principle of inversion of control (IoC)).
If you aren't using any third party dependency injector, you can always use init
This is called Dependency injection via Constructor
There are multiple ways to introduce Dependency injection, discussion of which is not in the scope of this answer.
Answer below assumes you are not using any third party Dependency Injector and using Constructor
based injection.
how do I instantiate this view controller from SceneDelegate?
step 1: Have a viewModel class
class ViewModelA {
let someParam: String
init(with param: String) {
self.someParam = param
//rest of code
}
//rest of code whatever makes sense here
//modification of string any buisness logic
//or hold other data models
}
Step 2: Inject ViewModel as constructor Dependency to ViewController
class ViewControllerA: UIViewController {
let viewModel: ViewModelA
init(with viewModel: ViewModelA) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//rest of code to set up, update UI and view delegates
}
Step3: Set your view controller as root view controller of window in willConnectTo session
method of SceneDelegate
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
guard let windowScene = (scene as? UIWindowScene) else { return }
let window = UIWindow(windowScene: windowScene)
let viewModelA = ViewModelA(with: "abcd")
let viewControllerA = ViewControllerA(with: viewModelA)
window.rootViewController = viewControllerA
self.window = window
window.makeKeyAndVisible()
}
I've adopted MVVM pattern and the scene delegate doesn't look like a great place to leave view model.
You have to set rootView controller somewhere, dont you think? Earlier we used to set rootView controller in AppDelegate ( conceptually speaking creating a ViewModel and ViewController instance in AppDelegate is also somewhat flaky). Even if you use storyboard and set some view controller as initial viewController, even iOS does the same thing under the hood. Its because you wanna follow MVVM and not MVC you have to intercept and instantiate ViewController with viewModel as its dependency manually.
I personally prefer to work with MVVM with router, that way creating and injecting dependency to ViewController can be separated out (better concern separation) and can be reused (better code reusability)