Reputation: 636
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
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