ものすごく深い階層の View から 特定のViewController にたどり着きたい時があったとします。そのView から その ViewController にたどり着くには、中間に UIView や UIViewController がいくつも介在している可能性もあります。 NSNotification
を投げてやりとりをする人もいるかもしれませんが、今回は別の方法を紹介したいと思います。
iOS も OS X もイベントなどを処理する仕組みとして Responder Chain と言う仕組みがあります。これを辿れば、目的となる View Controller が見つかるはずです。
OS X では、NSViewController
が Responder Chain に入ってきたのは最近ですので、version の確認が必要です。WWDC 2104 Storyboards and Controllers on OS X
そこで、こんな Extension を紹介いたします。
extension UIResponder {
func findViewController<T: UIViewController>() -> T? {
var responder = self.nextResponder()
while responder != nil {
if let viewController = responder as? T {
return viewController
}
responder = responder!.nextResponder()
}
return nil
}
}
EDIT: UIResponder の extension に変更しました。2016.5.19
使い方は以下のようなイメージです。 Generics で UIViewController
の縛りが入っているので、as? MyViewController
とか as? UIViewController
とか型を決定させてあげる必要があります。この場合当然見つからない場合は nil
が帰ってきます。
let myViewController = view.findViewController() as? MyViewController
追記: うまく型が Generic に伝わらない場合がある事に気がつきました。まだ挙動に関して納得できていない部分が多いので、詳細は調査中としますが、以下のコードの方が安全のようです。2016.5.19
if let myViewController: MyViewController = view.findViewController() {
//...
}
同様に、superview を辿って、特定のクラスにたどり着きたい場合には、以下の extension を使います。
extension UIView {
func findView<T: UIView>() -> T? {
var view = self.superview
while view != nil {
if let view = view as? T {
return view
}
view = view!.superview
}
return nil
}
}
使い方の例は、以下の通りです。
let myView = view.findView() as? MyView
View が多重階層となり、View と ViewController にいくつもの 中間 View や 中間 ViewController を介在するようになると、多くの中間 delegate プロトコルを用意して、処理やイベントを中継に中継を重ねて伝達させる必要があったりします。そんな場合には、こんな方法もありますが、無用な密結合が生まれるのでご注意ください。
応用すれば、Responder Chain で特定の Delegate などの特定のプロトコルを持つオブジェクトを探す、なんてのも可能かと思いますので、機会があれば是非実験してみてください。では、Have a happy coding!
swift --version
Apple Swift version 2.2 (swiftlang-703.0.18.1 clang-703.0.29)