UIScrollView の上で UIView を動かしたい
今作っているiPadアプリで、画面をピンチインアウトで拡大縮小して、さらにその上でドラッグで部品を動かせるようにしたい。
まずは画面をピンチインアウトで拡大縮小する方法を調べたところ、UIScrollView の上に部品を置いてあげればそれでよいとのこと。
iPhoneアプリ開発、その(118) UIScrollViewはどうやって使うのか?|テン*シー*シー
viewForZoomingScrollView メソッドも実装する必要がある。
ここまでは簡単だったのだが、拡大縮小できるようにして、さらに一部の UIView をドラッグできるようにしようとすると、touchesBegan たちが飛んでこない。
ここに同様のことが書かれていた。
iPhoneアプリ開発、その(121) なぜにtouchesBeganが来ない?|テン*シー*シー
nextResponder にタッチ情報を渡せばよいかと思ったが、そもそも UIScrollView 上に置いている部品に touchesBegan たちが呼ばれないようだ。
どうやら、UIScrollView がタッチ情報を取得して拡大縮小をしているので、それにより touchesBegan などは呼び出されないように見える。
かわりに、UIScrollView の scrollViewWillBeginDragging などは呼び出されるけれども、これだとtouchesMovedがないので使えない。
ということで、何とかUIScrollView上に置いた部品で touchesBegan 達をとれる方法を調べたところ、Stack Overflow で同様の質問がいろいろと見つかった。
UIScrollView に関してはかなりいろいろ混乱が起きていてノイズが多いのだが、UIWindow ををサブクラス化して sendEvent でメッセージをインターセプトする方法を使ったところ、やりたいことが実現できた。
iphone – Observing pinch multi-touch gestures in a UITableView – Stack Overflow
これは、UIWebView に独自にタッチジェスチャーを追加したりするときに使う方法のようで、これを使えばUIWindowに来るイベントを何でもインターセプトできるようだ。
ということで touchesBegan 達を UIView に送ることができた。
Stack Overflow で説明されていた、EventInterceptWindowDelegate の実装
#import <Foundation/Foundation.h> @protocol EventInterceptWindowDelegate - (BOOL)interceptEvent:(UIEvent *)event; // return YES if event handled @end @interface EventInterceptWindow : UIWindow { // It would appear that using the variable name 'delegate' in any UI Kit // subclass is a really bad idea because it can occlude the same name in a // superclass and silently break things like autorotation. id <EventInterceptWindowDelegate> eventInterceptDelegate; } @property(nonatomic, assign) id <EventInterceptWindowDelegate> eventInterceptDelegate; @end
UIWindow の sendEvent をオーバーライドして、自分で設定した delegate に情報を送る
#import "EventInterceptWindow.h" @implementation EventInterceptWindow @synthesize eventInterceptDelegate; - (void)sendEvent:(UIEvent *)event { if ([eventInterceptDelegate interceptEvent:event] == NO) { NSLog(@"sendEvent", event); [super sendEvent:event]; } } @end
AppDelegate で delegate をセットする
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after app launch. // Set the view controller as the window's root view controller and display. self.window.rootViewController = self.viewController; [self.window makeKeyAndVisible]; self.window.eventInterceptDelegate = self.viewController; return YES; }
呼び出された側で、動かしたいUIViewにタッチ情報を送る
- (BOOL)interceptEvent:(UIEvent *)event { NSLog(@"interceptEvent"); NSSet *touches = [event allTouches]; UITouch *oneTouch = [touches anyObject]; if([testView isObjTouched:[oneTouch locationInView:testView]]) { switch(oneTouch.phase) { case UITouchPhaseBegan: [testView touchesBegan:touches withEvent:event]; break; case UITouchPhaseMoved: [testView touchesMoved:touches withEvent:event]; break; case UITouchPhaseEnded: [testView touchesEnded:touches withEvent:event]; break; default: break; } return YES; } return NO; }
追記 2011/06/06
もう一度試作していたところ、上記の UIWindow ををサブクラス化して sendEvent でメッセージをインターセプトする方法 を使わなくても、scrollViewのcontentviewの方にメッセージがとぶようになった。
そもそも、UIScrollView は、Appleの説明 や UIScrollView touch handling – Stack Overflow を参照すると、タッチしてからタイマーを開始し、タイマーが発火する前に指の移動を検知したらスクロールと判断するようになっている。
Because a scroll view has no scroll bars, it must know whether a touch signals an intent to scroll versus an intent to track a subview in the content. To make this determination, it temporarily intercepts a touch-down event by starting a timer and, before the timer fires, seeing if the touching finger makes any movement.
このため、タッチしてすぐに指を動かすとスクロール、ある程度指を置いてから動かすとcontentViewの方にメッセージが飛ぶようになっている。
ただ、この挙動はUIScrollViewのcanCancelContentTouches で変更することができる。
canCancelContentTouches を YES に設定すると、contentView にはメッセージが飛ぶことはないようだ。以前この記事を書いたときは、canCancelContentTouches が YES になっていたのでつねにUIScrollViewの方が反応していたのかも知れない。
canCancelContentTouches を NO の設定しておけば、ちゃんと contentViewにメッセージが飛んでいる。
UIWindow ををサブクラス化して sendEvent でメッセージをインターセプトする方法 は不要だったようだ。
ただ、今回は、
1) あるUIImageViewをタッチしてドラッグしているときにはスクロールさせず、そのUIImageViewを移動させる
2) そのUIImageViewでない、何もないところをタッチしてドラッグしているときにはスクロールさせる
ようにしたい。
この時の問題は、タッチしてちょっとしてcontentViewの方にメッセージが送られてしまうと、2)が実現されない。
このため、2)の時にはcanCancelContentTouches = YES として、contentView にメッセージが送られないようにした。
これにより、上記の挙動が実現できたようだ。
その後、delaysContentTouches を NO (デフォルトはYES) にしておけば、タッチしてすぐドラッグしてもcontentViewの方にメッセージが送られることもわかった。
関連記事
-
-
iOS 4.2.1, iOS SDK 4.2, Xcode 3.2.5 にアップデート
iPhone 3GSを、昨晩でたと思われる、iOS 4.2.1にアップデートしてみる。 iOS 4.
-
-
[iOS] INNER JOIN に失敗する
非常に面妖な状況でにわかには信じがたいのだが、iPhone 6 Plus だと問題なく動作する
-
-
薄い iPad2用ケース eggshell for iPad2 TUN-PD-000079 購入 2011/06/27
不具合報告があったので、当初予定がなかったiPad2を購入。 それはまた別に書くとして、まずは一緒に
-
-
iOS9から[NSLocale preferredLanguages] の出力が変更された
以前ツイートした内容を自分で忘れていて検索する羽目になったのでブログにも書いておく。iOS9から、
-
-
TestFlight を使ったベータテストについて
アプリのベータテストにご協力いただきありがとうございます。ベータテストには、AppleのTestFl
-
-
Apple、iPhoneアプリは今後3.0互換でなければ受付けないと通告
Apple、iPhoneアプリは今後3.0互換でなければ受付けないと通告 うちにはこのメールは届いて
-
-
新アプリ「ドラムちゃん」を公開しました
2015年から作りはじめた iPad専用 ドラム譜学習アプリ ドラムちゃん を本日 2017/01/
-
-
[WatchKit] How to update complications
Apple Watch の文字盤上に、自分のアプリの情報を載せることができる仕組みが、Complic
-
-
[IOS] iOS オープンソースライブラリ徹底活用 菊田剛著 秀和システム
ふらっと家の近くの書店に寄ったところたまたま発見した本。 最近発売されたようだ。 21章で69
-
-
はじめてのiPhoneプログラミング 正誤表
発売されてからすぐ はじめてのiPhoneプログラミング を購入し、必要に応じて少しずつ読み進めてい