私はこの拡張機能を作成しようとしています:
_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
}
}
_
しかし、コンパイルエラーが発生します:
エラー:タイプ「UIViewController」の戻り式を戻りタイプ「Self」に変換できません
出来ますか?また、init(storyboardName: String, storyboardId: String)
として作成したい
Swiftのクラス拡張関数で「self」を使用 と同様に、呼び出しコンテキストからselfのタイプを推測する汎用ヘルパーメソッドを定義できます。
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
}
}
それから
let vc = MyViewController.instantiateFromStoryboard("name", storyboardId: "id")
コンパイルされ、タイプはMyViewController
として推測されます。
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
}
}
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)
}
}
Self
は、ランタイムではなくコンパイル時に決定されます。コードでは、Self
はUIViewController
とまったく同じであり、「たまたまこれを呼び出しているサブクラス」ではありません。これはUIViewController
を返し、呼び出し元はas
を正しいサブクラスに入れる必要があります。私はそれがあなたが避けようとしていたことだと思います(それはそれを行うための「通常のCocoa」方法ですが、UIViewController
を返すだけがおそらく最良の解決策です)。
注:どんな場合でも関数にinitialize
という名前を付けてはいけません。これはNSObject
の既存のクラス関数であり、せいぜい混乱を引き起こし、最悪の場合はバグを引き起こします。
ただし、呼び出し側のas
を避けたい場合、通常、サブクラス化はSwiftに機能を追加するツールではありません。代わりに、通常はジェネリックとプロトコルが必要です。この場合、ジェネリックだけで十分です。
func instantiateViewController<VC: UIViewController>(storyboardName: String, storyboardId: String) -> VC {
let storyboad = UIStoryboard(name name: storyboardName, bundle: nil)
let controller = storyboad.instantiateViewControllerWithIdentifier(storyboardId) as! VC
return controller
}
これはクラスメソッドではありません。それは単なる機能です。ここにクラスは必要ありません。
let tvc: UITableViewController = instantiateViewController(name: name, storyboardId: storyboardId)
よりクリーンなソリューション(少なくとも視覚的にきれい):
class func initialize(storyboardName: String, storyboardId: String) -> Self {
// The absurdity that's Swift's type system. If something is possible to do with two functions, why not let it be just one?
func loadFromImpl<T>() -> T {
let storyboard = UIStoryboard(name: storyboardName, bundle: nil)
return storyboard.instantiateViewController(withIdentifier: storyboardId).view as! T
}
return loadFromImpl()
}
もう1つの方法は、プロトコルを使用することです。これにより、Self
を返すこともできます。
protocol StoryboardGeneratable {
}
extension UIViewController: StoryboardGeneratable {
}
extension StoryboardGeneratable where Self: UIViewController
{
static func initialize(storyboardName: String, storyboardId: String) -> Self
{
let storyboad = UIStoryboard(name: storyboardName, bundle: nil)
let controller = storyboad.instantiateViewController(withIdentifier: storyboardId) as! Self
return controller
}
}