Ryan
Ryan

Reputation: 636

Why do I need to force the type using this Swift generic function?

I had some repetitive UIViewController boiler-plate scattered around that I wanted to encapsulate, so I defined this generic UIViewController extension method:

extension UIViewController {
    func instantiateChildViewController<T: UIViewController>(
        storyboardName: String? = nil,
        identifier: String? = nil
    ) -> T {
        let storyboard: UIStoryboard!
        if let name = storyboardName {
            storyboard = UIStoryboard(name: name, bundle: nil)
        }
        else {
            storyboard = UIStoryboard(name: "\(T.self)", bundle: nil)
        }

        let vc: T!
        if let identifier = identifier {
            vc = storyboard.instantiateViewController(withIdentifier: identifier) as! T
        }
        else {
            vc = storyboard.instantiateInitialViewController()! as! T
        }
        self.addChildViewController(vc)
        self.view.addSubview(vc.view)
        return vc
    }
}

However, when I use this extension like so:

class ChildViewController: UIViewController { /*...*/ }

class ParentViewController: UIViewController {
    private var childVC: ChildViewController!

    //...

    func setupSomeStuff() {
        self.childVC = self.instantiateChildViewController() //<-- Compiler error

        let vc: ChildViewController = self.instantiateChildViewController() //<-- Compiles!
        self.childVC = vc
    }
}

I get the compiler error Cannot assign value of UIViewController to type ChildViewController! on the line with the comment above. However, if I use an intermediate variable that I explicitly give a type to it works.

Is this a Swift bug? (Xcode 8.1) My interpretation of how generics work is that in this case T should equal the more specific ChildViewController, not the less constrained UIViewController. I get the same issue if I defined childVC as private var childVC: ChildViewController?, the only work-around I've found is the local variable, which obviously makes the extension less compelling, or to do an explicit cast like:

self.childVC = self.instantiateChildViewController() as ChildViewController

Upvotes: 0

Views: 31

Answers (1)

GetSwifty
GetSwifty

Reputation: 7756

I've seen this too. I think there's some weird behavior around Optionals the compiler isn't dealing with as expected.

If you change the return value of the function to an optional value it should work without a problem.

func instantiateChildViewController<T: UIViewController>(//whateverParams) -> T!

or

func instantiateChildViewController<T: UIViewController>(//whateverParams) -> T?

Also, your childVC should be a var rather than a let if you're going to set it anyplace other than an initializer

Upvotes: 1

Related Questions