MhmdRizk
MhmdRizk

Reputation: 1721

Dependency injection in UIViewControllers iOS swift

First I checked this post and it wasn't useful

I want to apply dependency injection on navigation from a controller to another,

let's say I have controller A :

import UIKit

class A: UIViewController {

}

and a controller B :

import UIKit

class B: UIViewController {

       var name : String!

}

I'm navigating from A to B in this way :

let bViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "BVC")
as! B
bViewController.name = "HelloWorld"
self.navigationController?.pushViewController(bViewController, animated: true)

I want to convert my code in order to be using dependency injection through initializers.

can any one advice if this can be done, and if can be done how ??

thnx in advance.

Upvotes: 3

Views: 4163

Answers (4)

Rhenz
Rhenz

Reputation: 2293

class B: UIViewController {
    var name: String

    init?(coder: NSCoder, name: String) {
        self.name = name
        super.init(coder: coder)
    }

    required init?(coder: NSCoder) {
        fatalError("You shouldn't initialize this controller using init(coder:)")
    }
}

Then when you want to navigation from A to B:

func pushToAContoller {
    guard let vc = storyboard?.instantiateViewController(identifier: "AController", creator: { coder in
        return A(coder: coder, name: "SomeName")
    }) else {
        fatalError("Failed to load A from storyboard.")
    }

    navigationController?.pushViewController(vc, animated: true)
}

Upvotes: 0

Crt Gregoric
Crt Gregoric

Reputation: 402

Starting with iOS13 Apple added functionality that allows us to do dependency injection at initialization.

You can do this by using a new method on UIStoryboard called instantiateViewController(identifier:creator:). You have to define your own init with the desired data and coder in the view controller to which you want to pass data and then call this init with the above mentioned method in the parent/presenting view controller.

See linked documentation and example below.

class ParentViewController: UIViewController {

    @IBAction private func button() {
        let controller = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(identifier: "ChildViewController") { coder in
            return ChildViewController(coder: coder, userId: "123")
        }

        navigationController?.pushViewController(controller, animated: true)
    }

}

class ChildViewController: UIViewController {

    var userId: String

    init?(coder: NSCoder, userId: String) {
        self.userId = userId
        super.init(coder: coder)
    }

    required init?(coder: NSCoder) {
        fatalError("Missing user id.")
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        print("UserId: \(userId)")
    }

}

Upvotes: 2

Sishu
Sishu

Reputation: 1558

class UserCredentials {

private let userDefaults: NSUserDefaults

private let authorizationTokenKey = "AuthorizationTokenKey"

init(userDefaults: NSUserDefaults) {
    self.userDefaults = userDefaults
}

func getAuthorizationToken() -> String {
    let value = userDefaults.stringForKey(authorizationTokenKey)
    guard let retVal = value else { return "" }
    return retVal
}
}

Upvotes: 0

Peter Tretiakov
Peter Tretiakov

Reputation: 3410

It is impossible since you use Storyboards. When you instantiate ViewController from Storyboard through instantiateViewController method it uses required init?(coder aDecoder: NSCoder) initialiser.

If you want to use your custom initialiser, you need to get rid of Storyboards and create UIViewController only from code or from xib file. So you will be able to make this:

import UIKit

class B: UIViewController {
    var name: String!

    init(name: String) {
        self.name = name
        super.init(nibName: nil, bundle: nil) # or NIB name here if you'll use xib file
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}

Also you need to provide init(coder...) since every UI element can be instantiate from Storyboard. But you can leave it with default super call, since you won't use it.

Another option is to use static method in ViewController from the post in beginning of your question. But in fact it also assigns variables after ViewController's initialisation.

So no DI through initialisers for now. I would suggest to use separate struct for all the data which need to be injected in VC. This struct will have all the necessary fields so you won't miss any of them. And you typical flow will be:

  1. Instantiate VC from Storyboard
  2. Instantiate Data struct
  3. Assign data to VC's var data: Data!
  4. Use all the injected variables from it

Upvotes: 6

Related Questions