15

Aim: to make a generic ViewController and TableViewController which would be able to return themselves from the existing storyboards and which would be subclassed by other view controllers and allow them to use this functionality.

class GenericTableViewController: UITableViewController
{
    //MARK: Storyboard
    class func storyboardName() -> String
    {
        return ""
    }

    class func storyboardIdentifier() -> String
    {
        return ""
    }

    class func existingStoryboardControllerTemplate() -> Self
    {
        return  UIStoryboard.storyboardWithName(storyboardName()).instantiateViewControllerWithIdentifier(storyboardIdentifier()) as! Self
    }
}

The problem is.. the compiler forces me to change the Self to this "GenericTableViewController" and if I change it... it complains that I no longer return "Self".

Is there something that can fix this?

Fawkes
  • 3,831
  • 3
  • 22
  • 37
  • In a type method Self refers to the type, but you are returning an instance and trying to cast it as a type. – Andrea Jun 13 '15 at 07:36
  • Have a look at http://stackoverflow.com/questions/25645090/protocol-func-returning-self# – ABakerSmith Jun 13 '15 at 08:08
  • I looked, but it's a little bit confusing for me.. -> I cannot understand how to adapt it that way. – Fawkes Jun 13 '15 at 08:09

2 Answers2

25

Doing the following should work:

class func existingStoryboardControllerTemplate() -> Self {
    return  existingStoryboardControllerTemplate(self)
}

private class func existingStoryboardControllerTemplate<T>(type: T.Type) -> T {
    return  UIStoryboard(name: storyboardName(), bundle: nil).instantiateViewControllerWithIdentifier(storyboardIdentifier()) as! T
}

Basically you create a generic version of your existingStoryboardControllerTemplate and add an extra method to help the compiler infer the type of T.

Tomas Camin
  • 9,996
  • 2
  • 43
  • 62
  • How about specializing the second method like this? existingStoryboardControllerTemplate(type: T.Type) – John Difool Jun 13 '15 at 16:34
  • 1
    @JohnDifool would definitely work. However, in this case, there won't be any particular advantage in doing so as the second method is basically a private method (answer edited) which will be only called from a `GenericTableViewController` class (or subclass) – Tomas Camin Jun 13 '15 at 16:45
5

Building on the answer from Tomas Camin, here's a UIViewController extension in Swift 3.

extension UIViewController {

  class func fromStoryboard(_ name: String, in bundle: Bundle? = nil, withIdentifier id: String? = nil) -> Self? {
    return fromStoryboard(UIStoryboard(name: name, bundle: bundle), withIdentifier: id)
  }

  class func fromStoryboard(_ storyboard: UIStoryboard, withIdentifier id: String? = nil) -> Self? {
    return fromStoryboard(storyboard, withIdentifier: id, as: self)
  }

  private class func fromStoryboard<T>(_ storyboard: UIStoryboard, withIdentifier id: String? = nil, as type: T.Type) -> T? {
    return  storyboard.instantiateViewController(withIdentifier: id ?? "\(type)") as? T
  }

}

If your storyboard view controller identifiers match their class names, just call the class function fromStoryboard(name:) with a name.

let viewController = MyCustomViewController.fromStoryboard("Main")

Otherwise, provide an identifier.

let viewController = MyCustomViewController.fromStoryboard("Main", withIdentifier: "ID")

If you already have an instance of a storyboard, use may use it.

let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)

let viewController = MyCustomViewController.fromStoryboard(storyboard, withIdentifier: "ID")
Brandon Zacharie
  • 2,320
  • 2
  • 24
  • 29