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の方にメッセージが送られることもわかった。
関連記事
-
-
iPhone&Androidアプリ内課金プログラミング完全ガイド
こんなマニアックな本が11月に出ていたらしい。 立ち読みしてよさそうだったら買ってこよう。 売れ
-
-
iPhoneアプリ おんぷちゃん開発日記 和音表示
自分の娘のために作った五線譜学習アプリ「おんぷちゃん」の開発を細々と続けている。 iPadのグランド
-
-
iPhone/Androidアプリで週末起業(山崎潤一郎著) を読んでみた。
前著を読んで、ちょっと楽観的に書きすぎていると思ったけれどもとりあえず最新版が出たので読んでみた。
-
-
有料iPhoneアプリを売る手続き
AppStoreで無料アプリを配布している分には必要ない手続きだが、有料アプリを売ろうと思うといろい
-
-
[IOS] iOS オープンソースライブラリ徹底活用 菊田剛著 秀和システム
ふらっと家の近くの書店に寄ったところたまたま発見した本。 最近発売されたようだ。 21章で69
-
-
Everioで撮影した動画を共有する
毎年クリスマス近くになると、娘たちのバレエミニ発表会があるので、動画を撮って参加者に共有している。と
-
-
iOS6 の Guided Access (Single App Mode) が便利そう
昨日のWWDC 2012 キーノートではiOS6の紹介がありいろいろと新機能の説明があったけ
-
-
[iPhone SDK] UINavigationBar にボタンを追加する
自作アプリでUIWebViewを使ってWebを表示しているが、戻る・進むボタンをUINaviga
-
-
Apple Developer Program 更新 2017
今年も Developer Program を更新。今年は11800円+税だった。そして証明書も更新