StoryboardInstantiable Protocol Extension

Ever since Apple allowed for splitting up Storyboards, I've been using it for organizing my code into smaller, more manageable modules rather than having one big, universal storyboard. However, when instantiating view controllers in code you have to include a string with the name of the storyboard the view controllers UI resides in. The instantiation function should be familiar to most, and looks like this:

UIStoryboard(name: "StoryboardName", bundle: Bundle.main)  
  .instantiateViewController(withIdentifier: "MyViewController")

When juggling many storyboards spread across your application, this can mean a lot of hard-coded strings appear throughout which can cause nasty runtime errors. So, by using Protocol Extensions I found a preferable way of doing this. First, let me present the protocol.

protocol StoryboardInstantiable {  
  static var storyboardName: String { get }
  static var identifier: String { get }
}

This protocol requires the implementer to define two static strings; the name of its storyboard as well as the storyboard ID of the controller within.

By supplying an extension to this protocol, we can then provide a static function for generating new instances a given class without providing any strings on instantiation.

extension StoryboardInstantiable where Self: UIViewController {  
  static func instantiateViewController() -> Self {
    return UIStoryboard(name: storyboardName, bundle: Bundle.main)
      .instantiateViewController(withIdentifier: identifier) as! Self
  }
}

Having this protocol extension in place, UIViewController's can opt in for this behavior very easily:

extension LoginController: StoryboardInstantiable {  
  static var storyboardName: String { return "Auth" }
  static var identifier: String { return "LoginController" }
}

extension ContentReportViewController: StoryboardInstantiable {  
  static var storyboardName: String { return "ObjectionableContent" }
  static var identifier: String { return "ContentReportViewController" }
}

And, instantiation becomes much more readable, and less prone to runtime errors:

let loginController = LoginController.instantiateViewController()  
let contentReportController = ContentReportViewController.instantiateViewController()  

Note! The snippets provided assume you keep all your storyboards in the main bundle. If you keep storyboards spread across multiple bundles you could simply add an optional bundle identifier to the StoryboardInstantiable protocol

Frederik Nakstad

Read more posts by this author.

Tokyo, Japan