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の方にメッセージが送られることもわかった。
関連記事
-
-
Everioで撮影した動画を共有する
毎年クリスマス近くになると、娘たちのバレエミニ発表会があるので、動画を撮って参加者に共有している。と
-
-
iOS15にアップデート
2021/09/21にiOS15が公開され、そろそろ初期不具合もわかってきた頃だと思いアッ
-
-
Core Graphics と Quartz
Core Graphics と Quartz の関係がよく分からなかったので調べていたところ、ほぼ同
-
-
タッチ!にほんちずHD Ver.1.1.0 Submit
タッチ!にほんちずHDのバージョンアップ版(Ver.1.1.0)を App Store に提出しまし
-
-
EverLearn 1.9.0 に音声認識機能を追加しました
EverLearn 1.9.0 にて音声認識機能を追加しました。ホーム画面から、マイクボタンを押して
-
-
Apple Event Apple Watch 3/10 2AM JST
Apple Watch に関するイベントが3/10(火)2:00AM JSTに行われるら
-
-
[iPhone開発本] オライリー iPhoneアプリケーション開発ガイド 感想その1
面白そうだったので発売日に買ってみた。1995円と安いのもすばらしい。 しかしタイトルは一ひねりした
-
-
Reject 履歴 おんぷちゃん 1.9.0
久しぶりに おんぷちゃん をアップデートしようとしたところ、"Guideline 2.3.7 - P
-
-
[iOS9] viewWillAppear で部品の位置が確定しなくなった
どうやら iOS9 から viewWillAppear では部品の位置が確定しなくなったらしく、vi
-
-
Apple Developer Program 更新 2017
今年も Developer Program を更新。今年は11800円+税だった。そして証明書も更新