UIWindowから別のUIWindowへタッチイベントを伝搬したい
環境
Xcode 6.2/iOS SDK 8.2/iOS Simulator iPhone 5s(iOS 7.1), 6(iOS 8.2)
テストコード
註: ストーリーボードを使わず、コードのみで画面を作成します。
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@property (strong, nonatomic) UIWindow *secondWindow;
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] initWithFrame: [[UIScreen mainScreen] bounds]];
self.window.restorationIdentifier = @"back window";
self.window.backgroundColor = [UIColor lightGrayColor];
[self.window addGestureRecognizer: [[UITapGestureRecognizer alloc] initWithTarget: self action: @selector(didTap:)]];
[self.window makeKeyAndVisible];
self.secondWindow = [[UIWindow alloc] initWithFrame: [[UIScreen mainScreen] bounds]];
self.secondWindow.restorationIdentifier = @"front window";
self.secondWindow.backgroundColor = [UIColor colorWithRed: 1.0f green: 0 blue: 0 alpha: 0.25f];
// (1)
[self.secondWindow addGestureRecognizer: [[UITapGestureRecognizer alloc] initWithTarget: self action: @selector(didTap:)]];
self.secondWindow.windowLevel = self.window.windowLevel + 1;
self.secondWindow.hidden = NO;
return YES;
}
#pragma mark - Responses
- (void)didTap:(UITapGestureRecognizer *)tap {
NSLog(@"Did tap %@.", tap.view.restorationIdentifier);
}
@end
2枚のウィンドウが同じサイズで生成され、重ねて表示されています。キーウィンドウは背面のほうです。どちらのウィンドウにもタップジェスチャーの認識が組み込まれています。
やりたいこと
このテストコードで画面をタップすると
Did tap front window.
と出力されます。
テストコードで、(1)の行をコメントアウトして、前面のウィンドウがイベントを処理しなくなったとき、背面のウィンドウがタッチイベントを認識するように し、
Did tap back window.
と出力されるようにしたいです。
既に分かっていること・調べたこと
- レスポンダーチェーンは最前面のビューから始まり、イベントを処理できるレスポンダが見つかるまでスーパービューを遡ってUIWindow、UIApplicationへと終着するため、ビュー階層の異なるUIWindowの位置を重ねても背面へタッチが伝搬することはない
- UIWindowがキーウィンドウであるかどうかで異なるのは、モーションイベントと遠隔操作イベントを受け取るか否かであり、タッチイベントはそれがキーウィンドウであるかに関係なく受け取る
参考にしているリファレンスやガイド
- UIResponder Class Reference およびクラスのヘッダファイル
- UIWindow Class Reference およびクラスのヘッダファイル
- Understanding Windows and Screens
- iOS Developer Center 日本語ドキュメント に掲載されている
- iOSイベント処理ガイド(日本語版PDF)
制約・要件
- 前面のウィンドウはデバイス画面全体を覆ってください。前面のウィンドウのサイズを変更して背面のウィンドウをタップできるようにする方法は採れません(ただし、妥協案としては考えています)
- 前面のウィンドウを背面のウィンドウのサブビューにすることはできません。同じ理由で、ウィンドウを使用せず、UIViewをルートビューの最前面に貼付ける、といった別方向からの解決法は採れません
- 実使用環境ではどちらのウィンドウにもrootViewControllerがセットされ、ルートビュー以下に多くのサブビューが追加されます。テストコードでは焦点を明確にするためにウィンドウのみを扱っています
現在じぶんが取り組んでいる方向性
レスポンダーチェーンの流れに沿ってどのビューもイベントを処理せずにUIWindowへイベントが到着したら、ウィンドウ自身も処理をせずUIApplicationへイベントを送るところを捕まえて別のUIWindowへ送信できないか、と考えています。
解決法のひとつとして探っているものの図解