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

[英単語学習法] MyShortcuts を使って通知センターからいろいろな辞書を引いてみる

MyShortcuts+Viewer 価格: ¥100

記事を読む

no image

Corona SDK 調査3日目 (Lua 用エディター探しその1)

iPhoneとAndroid両方で動くアプリケーションを開発できるというCorona SDKの調査3

記事を読む

no image

iPad アプリの iOS Deployment Target に設定するバージョン値を検討する

自作アプリのiPadの対応バージョンを決める際に、一番古くから対応していることにした場合どのバージ

記事を読む

[iPhone 6 Plus] TUNEWEAR の iPhone 6 Plus 用ケースを買ってみた

iPhone 6 Plus 購入時に、とりあえず購入した Simplism のケースは、すぐ

記事を読む

おんぷちゃん 1.9.0 MIDI対応

おんぷちゃん 1.9.0 で MIDI キーボードに対応しました。(まだ App Store にてレ

記事を読む

no image

iPhone UIデザイン用シート

paella さんのページにiPhone UIデザイン用シートがあったので利用させてもらう。8月に発

記事を読む

英辞郎第7版からアルクのSVLを EverLearn に取り込む方法

英辞郎 第七版(辞書データVer.136/2013年1月8日版)アルク企画開発部 アルク 2013

記事を読む

no image

iPhone Core Audio プログラミング (Extended Audio File Services)

発売日からずっと気になる存在だったが、Audio Queue Services を使ったプログラムを

記事を読む

no image

スマートフォン手袋を買ってみた 2011

去年買ったスマートフォン用手袋は石油くさくてかなりつらい思いをしながら使ってましたが、今年もこりず

記事を読む

no image

SwitchEasy NUDE for iPhone5 を買ってみた。まだ様子見の方がよさそう

SwitchEasy NUDE for iPhone 5 UltraClearSwitchEasy

記事を読む

Message

メールアドレスが公開されることはありません。

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

iOS16でaurioTouch の inBufferFramesが1になる

https://developer.apple.com/librar

おんぷちゃん for iPad 2.0.0リリース

あけましておめでとうございます。今年もよろしくお願いします。

自宅をリフォームしてみた

数年前から奥さんに家のリフォームを希望されていた。 ガ

iOS 16 GM版アップデート

遅ればせながら iPhone 11 Pro Maxを iOS16 G

Apple Event Sep 2022 Far Out

毎年恒例の Apple Event が 2022/09/07 に行わ

→もっと見る

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