UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

How to use dependency injection with storyboards

Swift version: 5.6

Paul Hudson    @twostraws   

Dependency injection is a fancy name for a simple thing: when we create an object in our app, we want to provide it with all the data it needs to work. Before iOS 13 this wasn’t possible when using storyboards, which meant we ended up with properties that were optional or implicitly unwrapped, even though we knew we’d be setting them immediately.

So, we used to write code like this:

// A view controller that we want to present with some data
class EditUserViewController: UIViewController {
    var selectedUser: User?
    // ...
}

// Our root view controller that wants to create, configure, and present an EditUserViewController
class MainViewController {
    // ...

    func show(user: User) {
        // attempt to load our view controller from the storyboard
        guard let vc = storyboard?.instantiateViewController(withIdentifier: "EditUser") as? EditUserViewController else {
            fatalError("Failed to load EditUserViewController from storyboard.")
        }

        // configure its only property
        vc.selectedUser = user

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

Having optionals in here was unavoidable because we had to let the storyboard handle initializing the view controller, but it adds all sorts of complexity – we can set that value to nil by accident, we can forget to set it at all, and we need to unwrap it as needed.

From iOS 13.0 and later there’s a better solution: a new method on UIStoryboard called instantiateViewController(identifier:creator:), which lets us determine how to create and configure our view controllers.

So, we could rewrite EditUserViewController to this:

class EditUserViewController: UIViewController {
    var selectedUser: User

    init?(coder: NSCoder, selectedUser: User) {
        self.selectedUser = selectedUser
        super.init(coder: coder)
    }

    required init?(coder: NSCoder) {
        fatalError("You must create this view controller with a user.")
    }

    // ...
}

That makes selectedUser non-optional, but also added two custom initializers: one with an NSCoder and a User, and one just with an NSCoder. The second one now uses fatalError() to make it clear that creating an EditUserViewController without a user isn’t allowed.

With that custom initializer in place we can now update MainViewController so that it initializes our EditUserViewController correctly:

func show(user: User) {
    guard let vc = storyboard?.instantiateViewController(identifier: "EditUser", creator: { coder in
        return EditUserViewController(coder: coder, selectedUser: user)
    }) else {
        fatalError("Failed to load EditUserViewController from storyboard.")
    }

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

What’s really changing here is that we’re now handed the NSCoder instance that can create our view controller, and we can use that however we want – including alongside other properties we want to inject. However, it means more places where we can remove optionality from properties, which is always welcome.

TAKE YOUR SKILLS TO THE NEXT LEVEL If you like Hacking with Swift, you'll love Hacking with Swift+ – it's my premium service where you can learn advanced Swift and SwiftUI, functional programming, algorithms, and more. Plus it comes with stacks of benefits, including monthly live streams, downloadable projects, a 20% discount on all books, and free gifts!

Find out more

Sponsor Hacking with Swift and reach the world's largest Swift community!

Available from iOS 13.0

Similar solutions…

About the Swift Knowledge Base

This is part of the Swift Knowledge Base, a free, searchable collection of solutions for common iOS questions.

BUY OUR BOOKS
Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

Average rating: 4.8/5

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.