So you use storyboards and you find yourself wanting to instantiate view controllers from one. The API to do this isn’t the nicest. First you have to create your UIStoryboard object, passing a string to define what storyboard you want. Then you pass another string in, and force unwrap to the UIViewController you want – yuck!
So instead we can create an enum and extension on UIStoryboard like this:
Each enum case is a different storyboard bundled in our app. Now we can do the exact same thing like this:
A much cleaner and safer approach.
We can also extend this to segues. The segue API is also kinda ugly, so we can create another enum and extension to clean this up for us!
Each segue that you make with an identifier, go ahead and put it in this enum as a new case. Now to perform a segue in a view controller, all we need to do is this:
The last piece of the puzzle is that pesky prepare(for segue:) method. In there you check the identifier as a string, check the destination, unwrap as the destination – yuck! And if that view controller is wrapped in a navigation controller you have to check that too and then get it out of the navigation controller. That’s a lot of boiler plate. So we can simplify it using the extension above. Now, we can just do this:
If this destination is a UINavigationController, it will automatically reach in and give you the correct view controller you want.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
enum Storyboard: String { | |
case main | |
var value: String { | |
return self.rawValue.capitalized | |
} | |
} | |
extension UIStoryboard { | |
convenience init(_ name: Storyboard) { | |
self.init(name: name.value, bundle: nil) | |
} | |
func instantiateViewController<T: UIViewController>() -> T { | |
if let name = NSStringFromClass(T.self).components(separatedBy: ".").last, let vc = instantiateViewController(withIdentifier: name) as? T { | |
return vc | |
} | |
fatalError("Could not find " + String(describing: T.self)) | |
} | |
} | |
enum Segue: String { | |
case showLogin | |
} | |
extension UIViewController { | |
func perform(segue: Segue) { | |
performSegue(withIdentifier: segue.rawValue, sender: nil) | |
} | |
} | |
extension UIStoryboardSegue { | |
func destination<T: UIViewController>(matching segue: Segue, as vc: T.Type) -> T? { | |
if identifier != segue.rawValue { return nil } | |
if let nav = destination as? UINavigationController { | |
return nav.viewControllers.first as? T | |
} | |
return destination as? T | |
} | |
} |