Search code examples
swiftswift2swift-extensions

Return instancetype in Swift


I'm trying to make this extension:

extension UIViewController
{
    class func initialize(storyboardName: String, storyboardId: String) -> Self
    {
        let storyboad = UIStoryboard(name: storyboardName, bundle: nil)
        let controller = storyboad.instantiateViewControllerWithIdentifier(storyboardId) as! Self

        return controller
    }
}

But I get compile error:

error: cannot convert return expression of type 'UIViewController' to return type 'Self'

Is it possible? Also I want to make it as init(storyboardName: String, storyboardId: String)


Solution

  • Similar as in Using 'self' in class extension functions in Swift, you can define a generic helper method which infers the type of self from the calling context:

    extension UIViewController
    {
        class func instantiateFromStoryboard(storyboardName: String, storyboardId: String) -> Self
        {
            return instantiateFromStoryboardHelper(storyboardName, storyboardId: storyboardId)
        }
    
        private class func instantiateFromStoryboardHelper<T>(storyboardName: String, storyboardId: String) -> T
        {
            let storyboard = UIStoryboard(name: storyboardName, bundle: nil)
            let controller = storyboard.instantiateViewControllerWithIdentifier(storyboardId) as! T
            return controller
        }
    }
    

    Then

    let vc = MyViewController.instantiateFromStoryboard("name", storyboardId: "id")
    

    compiles, and the type is inferred as MyViewController.


    Update for Swift 3:

    extension UIViewController
    {
        class func instantiateFromStoryboard(storyboardName: String, storyboardId: String) -> Self
        {
            return instantiateFromStoryboardHelper(storyboardName: storyboardName, storyboardId: storyboardId)
        }
    
        private class func instantiateFromStoryboardHelper<T>(storyboardName: String, storyboardId: String) -> T
        {
            let storyboard = UIStoryboard(name: storyboardName, bundle: nil)
            let controller = storyboard.instantiateViewController(withIdentifier: storyboardId) as! T
            return controller
        }
    }
    

    Another possible solution, using unsafeDowncast:

    extension UIViewController
    {
        class func instantiateFromStoryboard(storyboardName: String, storyboardId: String) -> Self
        {
            let storyboard = UIStoryboard(name: storyboardName, bundle: nil)
            let controller = storyboard.instantiateViewController(withIdentifier: storyboardId)
            return unsafeDowncast(controller, to: self)
        }
    }