import UIKit enum Storyboard: String { case main = "Main" case history = "History" case more = "More" func storyboard() -> UIStoryboard { return UIStoryboard(name: rawValue, bundle: nil) } } protocol StoryboardCreatable { associatedtype ViewModel static var storyboard: Storyboard { get } var viewModel: ViewModel { get set } } extension StoryboardCreatable { static var storyboardIdentifier: String { return String(describing: Self.self) } mutating func configureWithViewModel(viewModel: ViewModel) { self.viewModel = viewModel } } extension StoryboardCreatable where Self: UIViewController { static func createFromStoryboard(viewModel: ViewModel) -> Self { var viewController: Self = UIStoryboard.createViewController() viewController.configureWithViewModel(viewModel: viewModel) return viewController } } extension UIStoryboard { static func createViewController() -> T { let storyboard = UIStoryboard(name: T.storyboard.rawValue, bundle: nil) let createViewController = storyboard.instantiateViewController(withIdentifier: T.storyboardIdentifier) guard createViewController is T else { fatalError("Expected view controller with identifier \(T.storyboardIdentifier)") } return createViewController as! T } } // MARK: Implementation struct User { let name: String } struct History { } struct HistoryViewModel { let user: User let history: History } final class HistoryViewController: UIViewController, StoryboardCreatable { static var storyboard: Storyboard = .history var viewModel: HistoryViewModel! @IBOutlet weak var label: UILabel! override func viewDidLoad() { super.viewDidLoad() label.text = viewModel.user.name } } let viewModel = HistoryViewModel(user: User(name: "Mark"), history: History()) let viewController = HistoryViewController.createFromStoryboard(viewModel: viewModel)