7

When writing code manually, we could inject dependencies to rootViewController by constructor injection in AppDelegate:

UIViewController *vc = [[SomeViewController alloc] initWithDependency: dependency];
self.window.rootViewController = vc;

However, I can not find a way to inject dependencies when using Storyboard. It seems inappropriate to inject them in awakeFromNib or viewDidLoad etc.

Is that possible to inject them ?

mrahmiao
  • 1,291
  • 1
  • 10
  • 22
  • `initWithDependency`? i never heard such a method – Anil Varghese Sep 11 '14 at 11:52
  • @Anil It's just an example. Maybe `initWithFoo:Bar:` whatever. – mrahmiao Sep 11 '14 at 12:03
  • Setting those values in awakeFromNib or viewDidLoad seems ok. if you feel it is inappropriate in cases like parent controller only knows the dependencies, you can set the values in the `prepeareForSegue method` – Anil Varghese Sep 11 '14 at 12:16
  • @Anil I want to inject dependencies into rootViewController and it does not have a parent controller so that `prepareForSegue` can not be triggered. – mrahmiao Sep 11 '14 at 12:33
  • Grab the rootViewController in the appDelegate like `self.window.rootViewController` then set those properties :) – Anil Varghese Sep 11 '14 at 12:45

2 Answers2

2

In your AppDelegate test the following code:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

    let dependency = MyDependency()
    if let firstVC = window?.rootViewController as? MyViewControllerClass {
        firstVC.dependency = dependency
    }

    return true
}
MarMass
  • 5,755
  • 1
  • 18
  • 15
0

For doing dependency injection properly, you need 2 things :

1 - inject the data in your root view controller.

This can be done in the App delegate as shown in MarMass answer, or if you need something that also suits document applications, you can do it as I suggested in the accepted answer there : Can't use storyboard custom instantiated window controller

2 - you need to pass data from the root view controller to its descendants.

You will find many people saying "use prepareForSegue" for that. I really dislike using prepeareForSegue because it creates a hard dependency from the root view controller to its descendants, when I think each view controller should be kept independent.

You can't reuse your controllers in another Storyboard flow without modifying the view controller code (in that case prepareForSegue) again and again.

Plus, if you have many relationship, your prepareForSegue methods become a huge "case" code. Always an indicator that we can do better.

I suggest another method :

Create an extension to your view controller with an @IBSegueAction, and hook that @IBSegueAction to the segue view Interface Builder. This works both for transition segues and embedding segue.

extension FatherViewController {
    @IBSegueAction func injectToTextViewController(_ coder: NSCoder) -> NSViewController? {

        //  Create the destination view controller
        let destinationViewController = TextViewController(coder: coder)!

        //  Compute here the data to inject to the destination controller. As this is an extension of the father view controller, you have access to all its data
        destinationViewModel.data = self.data

        return destinationViewController
   }
}

Hook the @IBSegueAction in Interface Builder

(don't forget the ':' at the end of the selector name)

This way, you can keep your view controller code completely clean of thecontroller-to-controller relationships.

I like to keep all my segues actions in a single file that I put in the same directory as my storyboard. If I change the storyboard, I change the segue actions, not the controllers...

Keep @IBSegueAction with your Storyboard, not your controller

AirXygène
  • 2,409
  • 15
  • 34