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

Objective-C 2.0のプロパティで再びはまる

Objective-C 2.0のプロパティに関してはまったことがあり、これはちょっと詳しく知っておく

記事を読む

no image

[iOS開発本] ARC や Storyboardなどを説明した本

ARC や Storyboard を紹介した良い本を教えてほしい、と会社のマニアな人に質問されたので

記事を読む

no image

iPhoneアプリケーションプログラミング-オンライン版

先日紹介した、新居雅行氏のiPhoneアプリケーションプログラミングだが、オンライン版が存在する。

記事を読む

GoodNotes5使いこなし

iPad Pro 12.9インチを購入した 理由の一つである、Apple Pencil

記事を読む

MacPeople は2014年10月号からスマホアプリ開発者向け雑誌に?

。 最近 MacPeople の記事がずいぶんアプリ開発者寄りだなと思っていたところ、2014

記事を読む

no image

[iPhone SDK] ツールバー(UIToolBar) に矢印ボタンを入れる

iPhone OS 3.0から、UIBarButtonItem たちを NSArray に入れて、U

記事を読む

no image

ついにヒレガス本第3版が出るらしい

以前から、いつかは出るかな、いやもういまさらでないかなと思っていたヒレガス本の第3版が11月についに

記事を読む

no image

iPad スタンド SANWA SUPPLY MR-IPADST1 感想

以前書いた、iPad スタンド SANWA SUPPLY MR-IPADST1 が届いて、しばらく使

記事を読む

no image

iPhone開発本 iPhone Cool Projects

iPhone Games Projectsに続いて、iPhone Cool Projects も買っ

記事を読む

リズムくんHD 1.2.1 で iOS11に対応しました

iPad用のリズムくんHD のiOS11対応が遅れてしまったため、2017年11月あたりからiOS1

記事を読む

Message

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

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

おんぷちゃんマイク入力

おんぷちゃん for iPad は五線譜上に表示される音符をスクリー

aurioTouch のcppファイルがビルドできない

Appleのオーディオ関連のサンプルコードに aurioTouch

iOS13からpresentViewControllerの挙動が変わっている

    ; を実行してViewCo

MacBook Pro Mid 2012 SSD換装

MacBook Pro Mid 2012 までは自由にストレージを交

Reject 履歴 おんぷちゃん 1.9.1

iOS14からおんぷ先生と接続できない の修正のため、久しぶ

→もっと見る

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