Reputation: 1037
I'm using Swinject
for my DI solution, and extend it with the SwinjectStoryboard
extension.
I'm struggling with dynamic injecting the right viewModel
to a specific viewContoller
. The specific scenario is as follow:
MyViewController
has a property called var viewModel: ViewModeling
.
There are 2 different view models that are conforming to the ViewModeling
protocol, lets call them: firstViewModel
and secondViewModel
.
My storyboard contain just one controller and its the MyViewController
.
The Issue
Inject the right viewModel as a dependancy of MyViewController
dynamically (so only while running I'll know if to inject the first or the second)
I am able to do it on the service level (one protocol that 2 services are conforming to, and 2 different viewModel that each consume a different service can resolve the required one using a specific name)
I'm struggling with doing this on the viewController level, trying to inject the same view controller a specific viewModel (both conforms as mentioned to the same protocol).
Currently my hunch is that SwinjectStoryboard
doesn't let me instantiate a view controller using its storyboard id (like I would normally do), and in addition define few different names that will be resolved on runtime.
Am I missing something?
Upvotes: 2
Views: 3494
Reputation: 16114
My patterns, how to inject ViewModel
to ViewController
:
// ------------------------------------------------------------------------------------------
// Option 1, when you use Storyboards
// ------------------------------------------------------------------------------------------
import Foundation
class MyViewController {
var viewModel: MyViewModel!
@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var titleLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
bindViewModel()
}
func bindViewModel() {
}
// Then, you can inject ViewModel in prepare function
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
guard let myViewController = segue.destination as? MyViewController else { return }
myViewController.viewModel = MyViewModel(myDependency: myDependency)
}
}
// ------------------------------------------------------------------------------------------
// Option 2, when you use xibs
// ------------------------------------------------------------------------------------------
import Foundation
class MyViewController {
let viewModel: MyViewModel
@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var titleLabel: UILabel!
init(viewModel: MyViewModel) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
bindViewModel()
}
func bindViewModel() {
}
// Then, you can inject ViewModel in constructor:
func showMyViewController() {
let vm = MyViewModel(myDependency: myDependency)
let vc = MyViewController(viewModel: vm)
self.present(vc, animated: true, completion: nil)
}
}
Upvotes: 0
Reputation: 3873
You haven't missed anything - the behaviour you are looking for is indeed currently not possible to achieve with SwinjectStoryboard.
You can have multiple storyboardInitCompleted
with different names
, but they correspond to the names entered in the storyboard parameter swinjectRegistrationName
(see docs for more info) - in order to use this you would need to have multiple copies of the view controller in the storyboard.
From my point of view the ideal solution to this would be the registration arguments, i.e. you would have an enum ViewModelType {}
which would be used in the storyboardInitCompleted
to resolve the correct view model. Unfortunately, this feature is not yet finalised (see this PR).
In the current state I would probably try moving the selection of the view model from the injection logic to application logic - i.e. you could have some
protocol ViewModelProvider {
var viewModel: ViewModeling { get }
}
which would be injected into the view controller and provide correct view model based on some application state.
Another way to circumvent the issue would be to ditch the SwinjectStoryboard registration, and use basic Swinject to instantiate the view controller:
container.register(MyViewController.self, name: "name") {
let vc = SwinjectStoryboard.create(name: "MyStoryboard", bundle: nil).instantiateViewController(withIdentifier: "identifier")
vc.viewModel = $0.resolve(ViewModeling.self)
return viewModel
}
let vc = SwinjectStoryboard.defaultContainer.resolve(MyViewController.self, name: "name")
Upvotes: 2