UIViewController extension to create an instance from a storyboard

I am trying to write a small extension in Swift to handle an instance of a UIViewController from a storyboard.

My idea is this: UIStoryboard UIStoryboard instantiateViewControllerWithIdentifier method needs an identifier to instantiate this storyboard view controller, why not assign each view controller in my storyboard an identifier equal to its exact class name (that is, UserDetailViewController will have the identifier "UserDetailViewController") and create a method a class in a UIViewController that:

  • accept an instance of UIStoryboard as a unique parameter
  • get the current class name as a string
  • calling instantiateViewControllerWithIdentifier on the instantiateViewControllerWithIdentifier storyboard with the class name as parameter
  • get the newly created instance of the UIViewController and return it

So instead (which repeats the class name as a string is not very nice)

 let vc = self.storyboard?.instantiateViewControllerWithIdentifier("UserDetailViewController") as UserDetailViewController 

it would be:

 let vc = UserDetailViewController.instantiateFromStoryboard(self.storyboard!) 

I used to do this in Objective-C with the following category:

 + (instancetype)instantiateFromStoryboard:(UIStoryboard *)storyboard { return [storyboard instantiateViewControllerWithIdentifier:NSStringFromClass([self class])]; } 

But I am completely stuck in the Swift version. I hope there is some way to do this. I tried the following:

 extension UIViewController { class func instantiateFromStoryboard(storyboard: UIStoryboard) -> Self { return storyboard.instantiateViewControllerWithIdentifier(NSStringFromClass(Self)) } } 

Returning Self instead of AnyObject allows AnyObject with an output type. Otherwise, I would have to throw every return of this method, which is annoying, but maybe you have a better solution?

This gives me an error: Use of unresolved identifier 'Self' Part of the NSStringFromClass seems to be a problem.

What do you think?

  • Is there a way to return Self from class functions?

  • How would you get this to work without having to return a return value each time? (i.e. saving -> Self as return value)

Thank you

+8
ios objective-c iphone uiviewcontroller swift
source share
11 answers

How to write UIStoryboard extension instead of UIViewController ?

 extension UIStoryboard { func instantiateVC<T: UIViewController>() -> T? { // get a class name and demangle for classes in Swift if let name = NSStringFromClass(T.self)?.componentsSeparatedByString(".").last { return instantiateViewControllerWithIdentifier(name) as? T } return nil } } 

Even taking this approach, the cost of use is also low.

 let vc: UserDetailViewController? = aStoryboard.instantiateVC() 
+23
source share

Thanks to MartinR and his answer, I know the answer:

UPDATE: rewritten by protocol.

instanced

 protocol StringConvertible { var rawValue: String {get} } protocol Instantiable: class { static var storyboardName: StringConvertible {get} } extension Instantiable { static func instantiateFromStoryboard() -> Self { return instantiateFromStoryboardHelper() } private static func instantiateFromStoryboardHelper<T>() -> T { let identifier = String(describing: self) let storyboard = UIStoryboard(name: storyboardName.rawValue, bundle: nil) return storyboard.instantiateViewController(withIdentifier: identifier) as! T } } //MARK: - extension String: StringConvertible { // allow string as storyboard name var rawValue: String { return self } } 

Storyboardname

 enum StoryboardName: String, StringConvertible { case main = "Main" //... } 

Using:

 class MyViewController: UIViewController, Instantiable { static var storyboardName: StringConvertible { return StoryboardName.main //Or you can use string value "Main" } } let viewController = MyController.instantiateFromStoryboard() 
+5
source share

We strive to quickly advance our c-project. We divided the project into modules. Modules have their own storyboards. We have expanded your (even ours) solution to the problem to another level, avoiding the explicit names of the storyboard.

 // Add you modules here. Make sure rawValues refer to a stroyboard file name. enum StoryModule : String { case SomeModule case AnotherModule = "AnotherModulesStoryBoardName" // and so on... } extension UIStoryboard { class func instantiateController<T>(forModule module : StoryModule) -> T { let storyboard = UIStoryboard.init(name: module.rawValue, bundle: nil); let name = String(T).componentsSeparatedByString(".").last return storyboard.instantiateViewControllerWithIdentifier(name!) as! T } } // Some controller whose UI is in a stroyboard named "SomeModule.storyboard", // and whose storyboardID is the class name itself, ie "MyViewController" class MyViewController : UIViewController { // Controller Code } // Usage class AClass { // Here we must alwasy provide explicit type let viewController : MyViewController = UIStoryboard.instantiateController(forModule: StoryModule.SomeModule) } 
+3
source share

Two things:

  • Class constructors in Objective-C are convenience initializers in Swift. Use convenience init , not class func .
  • NSStringFromClass(Self) with NSStringFromClass(self.type) .
+2
source share

Here's how I did it in a recent Swift 4.0 project:

 protocol InstantiableFromStoryboard {} extension InstantiableFromStoryboard { static func fromStoryboard(name: String = "Main", bundle: Bundle? = nil) -> Self { let identifier = String(describing: self) guard let viewController = UIStoryboard(name: name, bundle: bundle).instantiateViewController(withIdentifier: identifier) as? Self else { fatalError("Cannot instantiate view controller of type " + identifier) } return viewController } } extension UIViewController: InstantiableFromStoryboard {} 

Using

 let viewController = ViewController.fromStoryboard() 
+2
source share

Or you can do it

  func instantiateViewControllerWithIdentifier<T>(_ identifier: T.Type) -> T { let identifier = String(describing: identifier) return instantiateViewController(withIdentifier: identifier) as! T } 
+1
source share

Use the protocol in the UIViewController to reach your thoughts.

let vc = YourViewController.instantiate(from: .StoryboardName)

You can see the use of my link: D

https://github.com/JavanC/StoryboardDesignable

+1
source share

You can add this extension: -

 extension UIStoryboard{ func instantiateViewController<T:UIViewController>(type: T.Type) -> T? { var fullName: String = NSStringFromClass(T.self) if let range = fullName.range(of:".", options:.backwards, range:nil, locale: nil){ fullName = fullName.substring(from: range.upperBound) } return self.instantiateViewController(withIdentifier:fullName) as? T } } 

And it can create an instance of a controller like this: -

 self.storyboard?.instantiateViewController(type: VC.self)! 
0
source share

you can create an instance of UIViewController as follows:

Create an enum with all your storyboard name.

 enum AppStoryboard: String { case main = "Main" case profile = "Profile" } 

Then, this is an extension for the UIViewController instance

 extension UIViewController { class func instantiate<T: UIViewController>(appStoryboard: AppStoryboard) -> T { let storyboard = UIStoryboard(name: appStoryboard.rawValue, bundle: nil) let identifier = String(describing: self) return storyboard.instantiateViewController(withIdentifier: identifier) as! T } } 

Using:

 let profileVC: ProfileVC = ProfileVC.instantiate(appStoryboard: .profile) self.navigationController?.pushViewController(profileVC,animated:true) 
0
source share

Here is a modern Swift example based on @findall solution:

 extension UIStoryboard { func instantiate<T>() -> T { return instantiateViewController(withIdentifier: String(describing: T.self)) as! T } static let main = UIStoryboard(name: "Main", bundle: nil) } 

Using:

 let userDetailViewController = UIStoryboard.main.instantiate() as UserDetailViewController 

I think this is normal when you try to create an instance of the view controller from the storyboard, as this problem should be detected soon.

0
source share

In addition to the @ChikabuZ version, here is mine, which takes into account which set the storyboard is in (for example, if your story files are in a different set than your application). I also added a little func if you want to use xib instead of storyboad.

 extension UIViewController { static func instantiate<TController: UIViewController>(_ storyboardName: String) -> TController { return instantiateFromStoryboardHelper(storyboardName) } static func instantiate<TController: UIViewController>(_ storyboardName: String, identifier: String) -> TController { return instantiateFromStoryboardHelper(storyboardName, identifier: identifier) } fileprivate static func instantiateFromStoryboardHelper<T: UIViewController>(_ name: String, identifier: String? = nil) -> T { let storyboard = UIStoryboard(name: name, bundle: Bundle(for: self)) return storyboard.instantiateViewController(withIdentifier: identifier ?? String(describing: self)) as! T } static func instantiate<TController: UIViewController>(xibName: String? = nil) -> TController { return TController(nibName: xibName ?? String(describing: self), bundle: Bundle(for: self)) } } 
0
source share

All Articles