UIScrollView の上で UIView を動かしたい

公開日: : 最終更新日:2011/06/16 iPad, iPhone ,

今作っている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の方にメッセージが送られることもわかった。

関連記事

no image

GTD用にOmniFocusを購入

半年くらいMacBookとiPhoneでOmniFocusを使っている。 なかなか良いので、紹介して

記事を読む

no image

iPadアプリ おんぷちゃん が雑誌に紹介されました

いろいろあって、iPhone/iPad アプリのおんぷちゃん がムジカノーヴァ2011年2月号で紹介

記事を読む

[WatchKit] Apple WatchアプリをRejectされた話

Apple Watch はいつの間にか電池がかなり減っていることが多く、いつどのように減っているか知

記事を読む

no image

確かに薄い iPhoneケース eggshell iPhone クリア

試しにiPhoneアプリのwordpressで更新してみる。 とにかく薄いiPhoneケースがほしく

記事を読む

no image

iPadアプリ第2弾 タッチ!にほんちずHD 公開

自分の娘用アプリ第2弾が今朝2011/06/17公開されました。 今回は日本地図を学習するアプ

記事を読む

ついにうちにもジェットブラックが来た。

予約から1ヶ月、ようやくうちにもiPhone 7 Plus ジェットブラックがきた。(2016/10

記事を読む

[Xcode] Interface Builder でControl+ドラッグでOutletが作成できない

たまにInterface Builder でUI作っているとはまるのでメモしておく。 Xco

記事を読む

no image

[iPad開発本] Beginning iPad Development

まだ数が少ないiPad開発本がApressから出たみたい。(Beginning of iPad De

記事を読む

no image

詳解 Objective-C 2.0 改訂版 2010年12月17日発売

まだ書店で遭遇できてないけれども荻原さんの詳解 Objective-C 2.0の改訂版が発売されてい

記事を読む

英単語学習アプリ EverLearn 1.4.0 を公開しました

ようやく、iOS7 に対応した EverLearn  1.4.0 を公開しました。 i

記事を読む

Message

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください

Apple Developer Program更新 2024

今年も更新した。 昨年はブログに記録し忘れたらしい。 今

ポモドーロテクニック用物理タイマーならTime Timer

会社ではなかなか自由に時間を使えないが、家で読書や作業をする

DELL 32インチディスプレイ U3223QE 購入

Dell U3223QE は解像度 3840x216

WWDC 2023 Vision Pro発表

2023/6/5 (日本時間 2023/06/06 2AM)のWWD

M1 MacBook Air を Venturaにアップデートする

M1 MacBook Air を macOS Montere

→もっと見る

  • 2011年5月
     1
    2345678
    9101112131415
    16171819202122
    23242526272829
    3031  
PAGE TOP ↑