我已经为我的所有UIViewController子类添加了一个方法,允许我从类和它里面的故事板中实例化它。
所有方法都遵循以下格式:
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
}
相反,我想制定一个协议Instantiatable
,它需要上面的方法和变量storyboardName: String
。
然后,我想扩展此Instantiatable
,因此它包含与上面类似的实现。我的目标是,我可以声明UIViewController
遵守此协议,我必须定义的所有内容都是storyboardName
。
我觉得我很接近这个实现:
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
}
}
但是,当我尝试向CameraViewController
添加一致性时,我收到错误:
非最终班级
instantiateFromStoryboard()
中的方法CameraViewController
必须返回Self
以符合协议Instantiatable
我错过了什么?
感谢。
答案 0 :(得分:1)
您可以使用泛型来实现您的目标。像这样:
protocol Instantiatable {
static func instantiateFromStoryboard<T: UIViewController>() -> T?
}
extension Instantiatable where Self: UIViewController {
static func instantiateFromStoryboard<T: UIViewController>() -> T? {
let storyboard = UIStoryboard(name: self.description(), bundle: nil)
let initial = storyboard.instantiateInitialViewController() as? T
guard let _ = initial as? Self else {
return nil
}
return initial
}
}
因此,如果VCA为instantiatable
,您可以说let vCA = VCA.InstantiateFromStoryboard()
我将函数更改为类函数,以便您可以在类上调用它,而不需要视图控制器的实例。此代码使用类名来检索storyboard文件,但这意味着您的storyboard需要被称为 projectname.classname.storyboard ,这有点难看。
另一种方法是要求视图控制器类实现一个返回故事板名称的函数:
protocol Instantiatable {
// var storyboardName: String { get }
static func instantiateFromStoryboard<T: UIViewController>() -> T?
static func storyboardName() -> String
}
extension Instantiatable where Self: UIViewController {
static func instantiateFromStoryboard<T: UIViewController>() -> T? {
let storyboard = UIStoryboard(name: self.storyboardName(), bundle: nil)
let initial = storyboard.instantiateInitialViewController() as? T
guard let _ = initial as? Self else {
return nil
}
return initial
}
}
然后在每个Instantiatable
中你需要实现:
static func storyboardName() -> String {
return "ViewController" // (or whatever the storyboard name is)
}
修改强>
根据您的回答和@AliSoftware的评论,第三个(可能是最好的)替代方法是制作UIViewController
子类final
。
这可以让你使用
protocol Instantiatable {
// var storyboardName: String { get }
static func instantiateFromStoryboard() -> Self?
static func storyboardName() -> String
}
extension Instantiatable where Self: UIViewController {
static func instantiateFromStoryboard() -> Self? {
let storyboard = UIStoryboard(name: self.storyboardName(), bundle: nil)
let initial = storyboard.instantiateInitialViewController() as? Self
return initial
}
}
只要您将视图控制器声明为:
final class ViewController: UIViewController, Instantiatable {
....
答案 1 :(得分:1)
final
这里的解决方案只是将final
添加到子类UIViewController
(在我的示例中,它是CameraViewController
)。
这允许您的呼叫网站正确推断UIViewController
的类型而不进行投射。在我的示例中,呼叫站点是:
guard let controller = CameraViewController.instantiate() else {
return
}
为什么添加最终关键字很重要?
在与@AliSoftware讨论后,他解释了final
的必要性。 (他还在Swift mixin存储库Reusable中添加了类似的协议。)
编译器会关注您的自定义VC是否为final
,以确保可以静态推断Self
要求Instantiatable
提及。
在一个例子中:
class ParentVC: UIViewController, Instantiatable {
// Because of Instantiatable, this class automatically has a method
// with this signature:
func instantiate() -> ParentVC // here, the "Self" coming from the Protocol, meaning "the conforming class", which is solved as "ParentVC"
}
class ChildVC: ParentVC {
// Error: It inherits from ParentVC, and has to conform
// to Instantiatable as a Parent
func instantiate() -> ParentVC
// but, it also has this from being a Instantiatable object itself
func instantiate() -> ChildVC
// thus, the compiler cannot solve what "Self" should resolve to here,
// either ParentVC or ChildVC.
//
// Also, it can generate problems in various contexts being "of
// both types" here, which is why it's not permitted
}
您的自定义VC可以直接从UIViewController
继承,但不需要再进行子类化,因此无论如何都应该将其标记为final
。
或者,您正在创建父抽象CommonVC
。您打算从中继承多个子项(class CustomVC1: CommonVC
,class CustomVC2: CommonVC
),但在这种情况下,CommonVC
是抽象的,可能不会直接实例化。因此,它不是标记为Instantiatable
的那个,您应该将CustomVC1
等标记为final
+ Instantiatable
。