How to pass variable data using Dependency Injection in Swift 5

I can't find ANYWHERE a simple, up-to-date tutorial of how to simply share (pass) variable data between multiple view controllers using Dependency Injection.

I followed this tutorial... The Right Way to Share State Between Swift View Controllers

...But it's out-of-date (Swift 3.0) so it doesn't work and I get this error: Unexpectedly found nil while implicitly unwrapping an Optional value

Can someone please just quickly take a look at that short tutorial and let me know how to fix the issue? The post author isn't replying to the questions in his comments. Kindly don't reply unless you've looked at the tutorial and can correct whatever is wrong in it.

I'm a new programmer, trying to learn how to do things the right way, instead of cheating, but I can't seem to really understand the more complex tutorials out there. The one linked above is the only one I understood... but of course it has the error lol.

Please show your answer in code. Thank you!

Here's an example of my code... it's very similar to his, except I'm trying to gather data in the "InfoViewController" via text fields, pass them to the "ModelController," and then access them in an NSObject .class called "AutoPDFBuilder." The initial (root) view controller is called ViewController and I inject into it via the AppDelegate, as in the tutorial.

App Delegate:

class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?


    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        
        if let homeViewController = window?.rootViewController as? ViewController {
            homeViewController.modelController = ModelController()
        }
        
        return true
    }
}

ViewController:

class ViewController: UIViewController {
    
    var modelController: ModelController!

@IBAction func newReportButton(_ sender: Any) {
        performSegue(withIdentifier: "homeSegue", sender: self)
    }
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if let infoViewController = segue.destination as? InfoViewController {
            infoViewController.variableController = modelController
        }
    }
}

InfoViewController (gather the information in textFields here):

struct pdfVariables {
    let theClient: String
    let theClaimNumber: String
    let theFileNumber: String
    let theLossDate: String
    let theInsured: String
    let theClaimant: String
    let thePicsAreBy: String
    let theDateTaken: String
}

class InfoViewController: UIViewController, UITextFieldDelegate {
    
    
    
    //TEXT FIELDS
    @IBOutlet weak var clientTextField: UITextField!
    @IBOutlet weak var claimNumberTextField: UITextField!
    @IBOutlet weak var fileNumberTextField: UITextField!
    @IBOutlet weak var lossDateTextField: UITextField!
    @IBOutlet weak var insuredTextField: UITextField!
    @IBOutlet weak var claimantTextField: UITextField!
    @IBOutlet weak var picsByTextField: UITextField!
    @IBOutlet weak var dateTakenTextField: UITextField!
    
    
    //DECLARATIONS
    var variableController: ModelController!

    @IBAction func infoDoneButton(_ sender: Any) {
        
        var client = String()
        var claimNumber = String()
        var fileNumber = String()
        var lossDate = String()
        var insured = String()
        var claimant = String()
        var picsBy = String()
        var dateTaken = String()
        
        if clientTextField.text != nil {
            client = clientTextField.text!
        } else {
            client = "Not applicable"
        }
        
        if claimNumberTextField != nil {
            claimNumber = claimNumberTextField.text!
        } else {
            claimNumber = "Not applicable"
        }
        
        if fileNumberTextField != nil {
            fileNumber = fileNumberTextField.text!
        } else {
            fileNumber = "Not applicable"
        }
        
        if lossDateTextField != nil {
            lossDate = lossDateTextField.text!
        } else {
            lossDate = "Not applicable"
        }
        
        if insuredTextField != nil {
            insured = insuredTextField.text!
        } else {
            insured = "Not applicable"
        }
        
        if claimantTextField != nil {
            claimant = claimantTextField.text!
        } else {
            claimant = "Not applicable"
        }
        
        if picsByTextField != nil {
            picsBy = picsByTextField.text!
        } else {
            picsBy = "Not applicable"
        }
        
        if dateTakenTextField != nil {
            dateTaken = dateTakenTextField.text!
        } else {
            dateTaken = "Not applicable"
        }
        
        let claimInfoData = pdfVariables(theClient: client, theClaimNumber: claimNumber, theFileNumber: fileNumber, theLossDate: lossDate, theInsured: insured, theClaimant: claimant, thePicsAreBy: picsBy, theDateTaken: dateTaken)
        
        func load() {
            variableController.thePDFInfo = claimInfoData
        }
        
        performSegue(withIdentifier: "infoSegue", sender: self)
        
    }
    
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        
        print("The info segue was executed.")
        let autoPDFBuilder = AutoPDFBuilder()
        autoPDFBuilder.variableController = variableController

    }
}

AutoPDFBuilder:

class AutoPDFBuilder: NSObject {

    var variableController: ModelController!
    var client = String()
    
    
    func createPDF() -> Data {
        
        client = variableController.thePDFInfo.theClient
        print("The client is \(client)")
   }
}

ModelController:

class ModelController {
    
    var thePDFInfo = pdfVariables(theClient: "N/A", theClaimNumber: "N/A", theFileNumber: "N/A", theLossDate: "N/A", theInsured: "N/A", theClaimant: "N/A", thePicsAreBy: "N/A", theDateTaken: "N/A")
    
}

The error is coming in the InfoViewController where I try to assign "claimInfoData" to "variableController.thePDFInfo". This is the area, as stated before: Unexpectedly found nil while implicitly unwrapping an Optional value.

Dependency injection hasn't really dated. If you're using UIKit, it's still a valid strategy, and is done more-or-less the same way. The site predate SceneDelegate, but you shouldn't need to worry about that yet

Your code looks fine, and it did run on my test. Chances are you're not connecting the storyboard correctly. Like forgetting to set ViewController and InfoViewController to proper views.

You mean here?

func load() {
  variableController.thePDFInfo = claimInfoData
}

It's not being called anywhere.

PS

  • This is largely a question about UIKit. I'd suggest that you ask instead over Apple Developer Forums or https://stackoverflow.com
  • I'd advice that pdfVariables are upper camel case (PDFVariables) to follow the API Guideline
  • Given that you're not using unwind segue on done operation, I'd also suggest that you check out how to use segue before dependency injection (or just use SwiftUI :stuck_out_tongue: ).
  • I'd suggest you start with a smaller project when trying new things. You don't need 8 text fields to figure out how to use segue for dependency injection (you don't even need a text field really).

Also, you need to make sure that these two lines are executed:

homeViewController.modelController = ModelController()

in AppDelegate.application, and

infoViewController.variableController = modelController

in ViewController.prepare.

As I said, you probably misconfigure the storyboard and miss one of them.

Okay. I followed the tutorial to the T and have the storyboard setup in the same way everyone else does it. The Button’s are a UIButton that calls the segue between the controllers via code. I never go backward in the stack, that’s why there’s no unwind segue.

Did both lines I mentioned execute? That's where the bug should be.

Also, when I tried downloading the project and running it, it wouldn’t even run due to it being in Swift 3.0.

Okay I used a couple print() tests to check if the two areas are executing. The ViewController one is successfully executing, but it's not executing in the AppDelegate. This is my AppDelegate code, any idea why it's not executing and how I can fix it?

class AppDelegate: UIResponder, UIApplicationDelegate {

var window: UIWindow?


func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // Override point for customization after application launch.
    
    if let homeViewController = window?.rootViewController as? ViewController {
        homeViewController.modelController = ModelController()
        print("App delegate excecuted successfully.")
    }
    
    return true
}

}

Also I'm new to this website and can't seem to find the hotkey for inserting code in a post... what is it?

There are 3 things you need to fix.

  1. In the Xcode, goto project->Targets->State->Build Setting. Search for Swift version, set to 5.0. This will fix the versioning problem. Swift 3 is forward compatible with Swift 5, so there isn't much problem

Screen Shot 2563-07-10 at 11.21.08
Screen Shot 2563-07-10 at 11.21.14


  1. Goto project -> Targets -> State -> Signing & Capabilities, you need to fix the signature there with your own to run outside of simulator. If you download a lot of projects online you might already be familiar with this.

  2. In AppDelegate.swift line 15, UIApplicationLaunchOptionsKey is renamed to UIApplication.LaunchOptionsKey.

  3. The save button in the example also seem to be broken, you might want to relink it in the storyboard.

The info about dependency doesn't really change, just some minor migration.

1 Like

That means that this line

if let homeViewController = window?.rootViewController as? ViewController

is not evaluated to true. Either window is nil, or window?.rootViewController is not ViewController. I suspect the latter. It could happen that your entry view in the storyboard isn't specified to ViewController.

It's possible that AppDelegate.application is not called at all, but I doubt it (and you could check).

Use tripple tick mark

```
Like this
```

And it will be rendered

Like this

Personally, I'd also suggest that you checkout SwiftUI, since the whole schtick about it is to pass around data conveniently, and to define views declaratively. It is much easier to manage compared to UIKit. Admittedly, it's still not as powerful as UIKit, but it's plenty enough for most part.

Okay, I'll check that out for sure, thanks for the suggestion!

Also, it turns out that ViewController isn't the root controller :sweat_smile: rookie mistake. the Navigation Controller is. So, I created a custom class for the NavigationController called NavController. In App delegate, I changed the rootViewController to be NavController. In NavController, I created the variable... but how do I pass it?? I tried using a segue, but it didn't execute it. I've never done any coding for a Navigation Controller, so I'm not quite sure how to get it to execute anything since it's only there for that split second before ViewController shows up.

Thanks

class NavController: UINavigationController {
    
    var modelController: ModelController!
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if let viewController = segue.destination as? ViewController {
            viewController.modelController = modelController
            print("NavViewController segue excecuted successfully.")
        }
    }
}

class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?


    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        print("The app delegate launched.")
        
        if let homeViewController = window?.rootViewController as? NavController {
            homeViewController.modelController = ModelController()
            print("App delegate homeViewController excecuted successfully.")
        }
        
        return true
    }
}

I don't interact with NavCtrl too much either, so I don't really know. I'd suggest that you ask instead over Apple Developer Forums or https://stackoverflow.com since it'd attract the right group of people. The question may even be asked already before.

1 Like