I've added a method to all my UIViewController subclasses that allows me to instantiate it from the class and the storyboard it's inside.
All the methods follow this format:
class func instantiateFromStoryboard() -> CameraViewController? {
let storyboard = UIStoryboard(name: "Camera", bundle: nil)
let initial = storyboard.instantiateInitialViewController()
guard let controller = initial as? CameraViewController else {
return nil
}
return controller
}
Instead, I would like to make a protocol, Instantiatable
, that requires the above method along with a variable, storyboardName: String
.
Then, I'd like to extend this Instantiatable
so it contains a similar implementation as above. My objective is that I can state that a UIViewController
adheres to this protocol, and all that I have to define is the storyboardName
.
I feel like I'm close with this implementation:
protocol Instantiatable {
var storyboardName: String { get }
func instantiateFromStoryboard() -> Self?
}
extension Instantiatable where Self: UIViewController {
func instantiateFromStoryboard() -> Self? {
let storyboard = UIStoryboard(name: storyboardName, bundle: nil)
let initial = storyboard.instantiateInitialViewController()
guard let controller = initial as? Self else {
return nil
}
return controller
}
}
However, when I try to add conformance to CameraViewController
, I get the error:
Method
instantiateFromStoryboard()
in non-final classCameraViewController
must returnSelf
to conform to protocolInstantiatable
What am I missing?
Thanks.
Add
final
The solution here was just to add
final
to the subclassedUIViewController
(in my example, it wasCameraViewController
).This allows your call site to correctly infer the type of the
UIViewController
without casting. In my example, the call site is:But, why?
Why does adding that final keyword matter?
After discussing with @AliSoftware, he explained the need for
final
. (He also added a similar protocol into the Swift mixin repository, Reusable.)The compiler cares if your custom VC is
final
to ensure that theSelf
requirementInstantiatable
mentions can be statically inferred or not.In an example:
Why adding final is ok
Either your custom VC is directly inheriting from
UIViewController
, but doesn't need to be subclassed any further, so you should mark itfinal
anyway.Or, you are creating a parent abstract
CommonVC
. You intend to make multiple children (class CustomVC1: CommonVC
,class CustomVC2: CommonVC
), inherit from it, but in that caseCommonVC
is abstract and likely not to be instantiated directly. Therefore, it's not the one to be marked asInstantiatable
, and you should mark theCustomVC1
etc. you intend to instantiate asfinal
+Instantiatable
instead.You can use generics to achieve what you are after. Something like this:
So, if VCA is
instantiatable
you can saylet vCA = VCA.InstantiateFromStoryboard()
I changed the function to be a class function, so that you can call it on the class, rather than needing an instance of the view controller. This code uses the class name to retrieve the storyboard file, but this means that your storyboard needs to be called projectname.classname.storyboard, which is a bit ugly.
Another approach is to require your view controller classes to implement a function that returns the name of the storyboard:
Then in each
Instantiatable
you would need to implement:EDIT
The third (and probably best) alternative is to make your
UIViewController
subclassesfinal
, as per your answer and @AliSoftware's comments.This lets you use
as long as you declare your view controllers to be: