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の方にメッセージが送られることもわかった。
関連記事
-
-
iOS14からおんぷ先生と接続できない
下記の問い合わせがあり、調べたところ、iOS14からおんぷちゃんとおんぷ先生がBluetoothで
-
-
NSInternalInconsistencyException
久しぶりにiOS6以前のアプリをビルドしたら NSInternalInconsistencyExce
-
-
MacPeople 2010.02
定期購読している MacPeople 2010年2月号が今日届いた。 今回はメールアプリとUbunt
-
-
新しい iPad の感想
昨日2012/03/16(金)は新しいiPadの発売日。 早速買って会社に持ってきた人がいたので、手
-
-
iPad アプリの iOS Deployment Target に設定するバージョン値を検討する
自作アプリのiPadの対応バージョンを決める際に、一番古くから対応していることにした場合どのバージ
-
-
オライリーのiPhoneゲーム開発本 iPhone Game Development (洋書)
このiPhone Game Developmentは話題になっていることをみたことがないし、日本のA
-
-
Xcode 設定しておくと便利なカスタマイズいろいろ
以前から Xcode はもう一歩使いにくいと思っていたが、iPhone アプリで稼げるのか でカスタ
-
-
[Xcode] iOS Developer Program 証明書の更新 2018
恒例の、年に一度の証明書の更新。今年はこのページを参照した。年に一度しか行わない作業だが、さすがに迷
-
-
Lexical or Preprocessor issue ‘xxx.h’ file not found
NSMutableArray に要素をランダムに並び替える機能を入れようと思い、 objective
-
-
iPhoneアプリ おんぷちゃん開発日記 ボタン表示の複数言語対応
そろそろiPhone OS 4 beta 3の開発環境に移行しようと思っているのだが、環境を移行して