UIResponder

UIResponderというクラスがある。「レスポンダ」という名前に最初違和感があったのだが、ドキュメントを読むことで納得した。

ビューオブジェクトはレスポンダオブジェクト(UIResponderクラスのインスタンス)であり、タッチイベントを受け取ることができます。タッチイベントが発生すると、ウインドウは対応するイベントオブジェクトをタッチイベントが発生したビューにディスパッチします。ビューでイベントを処理しない場合、ビューはこのイベントを無視するか、別のオブジェクトで処理されるようにレスポンダチェーンに渡すことができます。
イベントへの応答 - iOS Viewプログラミングガイド

なるほど、iOSのタップなどの操作イベントを受け取ることが出来るUIクラスのルートがUIResponderらしい。
クラス階層もこのようにUIView、UIViewControllerの双方のスーパークラスとなっている。

では実際にはどのようなレスポンダ階層になっているのか調べてみることにしよう。サンプルとして今まで何度も登場したHomepwnerの詳細ビューを使うことにした。

このビューのコントローラ(ItemDetailViewController)に以下のようなメソッドを書いて

-(void)dumpUIResponderChain:(UIResponder*) responder buffer:(NSMutableString*) b
{
   NSString* str = @"";
   while(true) {
       str = [NSString stringWithFormat: @"%@", [responder class]];
       [b appendString: str];
       responder = [responder nextResponder];

       if(!responder)
       {
           break;
       }
       [b appendString:@"-> "];
   }
}

任意のView(この場合はUITextFieldクラスの"txtName")をセットしてやると、

- (IBAction)enumerateViewAction:(id)sender
{
   NSMutableString* ms = [[NSMutableString alloc] init];
   [self dumpUIResponderChain: [self txtName] buffer: ms];
   NSLog(@"\n%@", ms);
   [ms release];
}

以下のような結果が取れた。

UITextField-> UIView-> ItemDetailViewController->UIViewControllerWrapperView->UINavigationTransitionView->UILayoutContainerView->UINavigationController->UIWindow->UIApplication->HomepwnerAppDelegate

面白いのはUITextFieldを基点としたレスポンダチェーンにはUIViewControllerやUIApplicationの派生クラスも含まれていることだ。
このようにコントローラまでレスポンダに含まれるお陰で、UIイベントをどこでも処理することができるのだろう。