Type safe Storyboard Identifiers

Get rid off String type identifiers

I don’t like to have hardcoded Strings anywhere in my codebase. If you use Storyboards in your project, you probably wrote a code similar to the following:

let storyboard = UIStoryboard(name: "MyStoryboardName", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "MyViewController") as! MyViewController

I tried to eliminate a need of writing MyStoryboardName and MyViewController. If you make a typo, it might lead to a crash very easilly. At the time I was looking into an awesome implementation of Kickstarter application. I noticed that they have a really neat way to instantiate a View Controller. Let’s take a look…

enum Storyboard: String {
    case Main
    case Preferences
}

extension Storyboard {
    func instantiate<C: StoryboardIdentifiable>(_ viewController: C.Type, inBundle bundle: Bundle = .main) -> C {
        guard let vc = NSStoryboard(name: self.rawValue, bundle: bundle)
                  .instantiateController(withIdentifier: C.storyboardIdentifier) as? C
        else {
            fatalError("Couldn't instantiate \(C.storyboardIdentifier) from \(self.rawValue)")
        }

        return vc
    }
}

To make it work though we have to create a StoryboardIdentifiable protocol.

protocol StoryboardIdentifiable {
    var storyboardIdentifier: String { get }
    static var storyboardIdentifier: String { get }
}

It’s super easy to create a default implementation of this protocol. We just need to return a String based on self’s type.

extension StoryboardIdentifiable {
    var storyboardIdentifier: String {
        return String(reflecting: self).components(separatedBy: ".").dropFirst().joined(separator: ".")
    }

    static var storyboardIdentifier: String {
        return String(reflecting: self).components(separatedBy: ".").dropFirst().joined(separator: ".")
    }

    var description: String {
        return storyboardIdentifier
    }
}

With this implementation, if you call storyboardIdentifier on MyViewController, you will receive "MyViewController". Now, we can make desired class conforming to it. In my case it was NSViewController and NSWindowController.

extension NSViewController: StoryboardIdentifiable { }

extension NSWindowController: StoryboardIdentifiable { }

That’s it. We can give it a spin now. In order to do so, we need to set an identifier of specific View Controller in Storyboard the same as its name. Then we can instantiate it as follows:

let myViewController = Storyboard.Main.instantiate(MyViewController.self)

It’s neat, isn’t it? Here you can find the whole implementation I use in Napi.