Dependency Injection in View Controller

Dependency Injection in View Controller

The fact that the init?(coder:) initialiser is being used suggests that your view controller is initialised from storyboard. If that is the case, storyboard does not contain ModelManager, thus it cannot decode it.

You can work around this by wrapping storyboard initialisation into your own method, e.g.:

class MyViewController: UIViewController {
private var modelManager: ModelManager

static func create(modelManager: ModelManager) -> MyViewController {
let vc = /* instantiate vc from storyboard */
vc.modelManager = modelManager
return vc
}
}

If the above method does not suit your needs, I would suggest you take a look at the SwinjectStoryboard framework. It provides - besides basic DI functionality - ability to inject dependencies to the view controllers initialised from storyboard.

How to Implement ViewController custom init using dependency injection and factory patterns?

There are different types of dependency injection. You're currently trying to use constructor-based dependency injection, which unfortunately doesn't really work with storyboards, since they need to initialise with a decoder. iOS 13 does introduce some additional functionality which will make this approach possible, but for the moment, you could use setter-based dependency injection instead.

Something like the following:

class ViewController: UIViewController {
var factory: ViewControllerFactory!
}

protocol ViewControllerFactory {
func makeViewController() -> ViewController
}

class DependencyContainer {
let storyboard: UIStoryboard = UIStoryboard(name: "Storyboard", bundle: Bundle.main)
}

extension DependencyContainer: ViewControllerFactory {
func makeViewController() -> ViewController {
guard let vc = storyboard.instantiateViewController(withIdentifier: "ViewController") as? ViewController else {
fatalError("Unrecognised viewController")
}
vc.factory = self
return vc
}
}

How to dependency inject in storyboard managed UIViewControllers?

View controllers in storyboard are always initialised using init?(coder aDecoder: NSCoder), so there's no way to set any properties at initialisation.

I've found the following to be a good workaround…

Rather than using

let loginSync: LoginSync

Declare as

private (set) var loginSync: LoginSync!

Declare

func configure(loginSync: LoginSync) {
self.loginSync = loginSync
}

Then

let vc = storyboard.instantiateViewController(withIdentifier: "YourFlow")
vc.configure(loginSync: MockLoginSync())

You can also use this in segues…

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
switch segue.destination) {
case let vc as MyViewController:
vc.configure(loginSync: MockLoginSync())
default:
break
}
}

It's not perfect, but making the property private (set) ensures it can't be modified from another class, and the implicit unwrapping (!) means you'll get a crash if it's not set.

Use configure() methods in every UIView/UIViewController - once you get used to this pattern it becomes second nature.



Related Topics



Leave a reply



Submit