このエントリーをはてなブックマークに追加このエントリをつぶやく

UITouchをトラッキングする

ユーティリティ系アプリならUIScrollViewやUIGestureRecognizerを使って簡単にマルチタッチに対応することができます。しかし、単純なピンチや二本指タップではなく、複数の指の動きを個別に検出しようとするとちょっとした小技が必要になります。

タッチイベントを検出する

主にUIViewやUIViewControllerの以下のメソッドをオーバーライドすることになります。
[ccN_objc]
– (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
– (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
– (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
– (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
[/ccN_objc]
NSSetの中にUITouchオブジェクトが含まれているわけですが、UITouchクラスにはオブジェクトを識別するIDのようなプロパティが存在しません。
押されている位置にそれぞれカーソルを表示するようなアプリの場合、touchesMoved:withEvent:で検出された指の移動が、どのカーソルの移動にあたるのか判断する必要があります。

使えないNSDictionary

ならば、UITouchオブジェクトをキー、対応するカーソルのビューを値として、NSDictionaryで管理しようと考えました。
が、ここに罠が待ち構えています。
Javaプログラマとしてはわりと衝撃的な事実…というか勉強不足なだけですが、NSDictionaryはキーをコピーするようです。
UITouchクラスはNSCopyingプロトコルに対応していないため、NSDictionaryのキーとして使うことができません。

頼れる公式ドキュメント

結局、正解はAppleの公式ドキュメントにありました。

  • iOSイベント処理ガイド(Event Handling Guide for iOS)
    • 複雑なマルチタッチシーケンスの処理(Handling a Complex Multitouch Sequence)

このドキュメントによればCFDictionaryを使えとのことです。

[ccN_c]
CFMutableDictionaryRef CFDictionaryCreateMutable (
CFAllocatorRef allocator,
CFIndex capacity,
const CFDictionaryKeyCallBacks *keyCallBacks,
const CFDictionaryValueCallBacks *valueCallBacks
);
[/ccN_c]

第3、第4引数が関数ポインタを含む構造体で怯みましたが、今回のようにキーをコピーせずretainしてほしい用途の場合はあらかじめ定数が用意されているので使わせてもらいましょう。
実際の使用例は以下の通りです。

辞書の生成

[ccNe_objc]
// touches_はCFMutableDictionaryRef型
touches_ = CFDictionaryCreateMutable(NULL, MAX_TOUCHES, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
[/ccNe_objc]

要素の追加

[ccN_objc]
– (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
UIView *cursor = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, 40, 40)] autorelease];
cursor.backgroundColor = [UIColor redColor];
cursor.center = [touch locationInView:self.view];
[self.view addSubview:cursor];

CFDictionarySetValue(touches_, touch, cursor); // 追加
}
}
[/ccN_objc]

要素の取得

[ccN_objc]
– (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
const UIView *cursor = CFDictionaryGetValue(touches_, touch); // 取得
cursor.center = [touch locationInView:self.view];
}
}
[/ccN_objc]

要素の削除

[ccN_objc]
– (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
const UIView *cursor = CFDictionaryGetValue(touches_, touch); // 取得
[cursor removeFromSuperview];
CFDictionaryRemoveValue(touches_, touch); // 削除
}
}
[/ccN_objc]
もちろん、touchesCancelled:withEvent:も忘れずに。

辞書の解放

[ccN_objc]
– (void)dealloc {
if (touches_)
CFRelease(touches_); // 解放
[super dealloc];
}
[/ccN_objc]

まとめ

マルチタッチ対応を実装していて偶然気づきましたが、NSDictionaryがキーをコピーするというのは知らないとハマりそうです。

参考

Apple – iOSイベント処理ガイド
Apple – Event Handling Guide for iOS

カテゴリー: プログラミング   タグ:   この投稿のパーマリンク

コメントは受け付けていません。