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 を参照すると、タッチしてからタイマーを開始し、タイマーが発火する前に指の移動を検知したらスクロールと判断するようになっている。

このため、タッチしてすぐに指を動かすとスクロール、ある程度指を置いてから動かすと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

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

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

記事を読む

no image

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

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

記事を読む

EverLearn Ver.1.6 を公開しました。今回はiPhone6 Plus 対応+英英辞書追加

残念ながらあまり売れていないiPhoneアプリ EverLearn ですが、自分的には毎日使っている

記事を読む

Apple Developer Program更新2019

今年も更新。税別 11800円だった。 2009年から継続しているようだ。もう11回

記事を読む

Siri Shortcuts に対応

正月休み中なので、ブログ記事が書きやすい。毎年この時期だけはよくブログを書いている気がする。

記事を読む

no image

App Storeでのアプリ最低価格が突然115円から85円に 2011/07/14

App Storeでのアプリ最低価格が突然日本時間2011/07/14(木)未明に115円から85円

記事を読む

おんぷちゃん for iPad 大譜表モード+MIDI対応を追加しました

おんぷちゃん for iPad: ぽこ・あ・ぽこ の方がおんぷちゃん for iPad を紹介してく

記事を読む

[Apple Watch] 2017年の冬休みの宿題はwatchOSアプリ開発

自分はPebble初代のころからスマートウォッチはPebble派だったが、2016年の年末は Peb

記事を読む

Alpha値だけのPNGファイルを作成する

Cocoaの日々: UIBarButtonItem にカスタム画像を表示する にあるようなalpha

記事を読む

とりあえず iPhone 6 Plus に対応する

iPhone 6 Plus は対応したアプリでないと画面が自動的に拡大されてらくらくホンぽい表示にな

記事を読む

Message

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

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

窪田テニス教室でスピンサーブ

15年以上テニススクールに通っているが、スクールにシングルス

LogLocations 1.4.0 写真表示対応

行動ログは取りたいが、何も操作したくない。という自分のようなずぼらな

Apple Watch用バッテリーロガーを公開しました

1年前に開発し、App StoreにSubmitしたものの Reje

Guideline 2.5.10 – Performance – Software Requirements で Reject

2019年3月27日以降、iPhone Xs Maxの画面サイズ6.

XcodeにiPhoneとwatchが表示されない

2019年はGWに10連休があるということで、今日は3日目。毎日少し

→もっと見る

  • 2011年5月
    « 4月   6月 »
     1
    2345678
    9101112131415
    16171819202122
    23242526272829
    3031  
PAGE TOP ↑