iPhone アプリでジェスチャーを使うというと、特殊なアプリを想像しがちですが、メインで使うのではなく、ゲームの裏コマンド入力や、隠し機能、デバッグモードのオープンなどに積極的に使ってみるのはどうでしょうか。
オリジナルのジェスチャーを作るには、UIGestureRecognizer のサブクラスを作成して実装します。UIGestureRecognizer の詳細については、Apple の Event Handling Guide for iOS ドキュメント、または
こちらの日本語PDFをご覧ください。
UIGestureRecognizer は状態マシンとして機能する抽象クラスです。
ユーザーがこのクラスに関連付けられたビューをタッチすると、そのイベントがこのクラスにリダイレクトされます。そのタッチイベントを読み取って、ジェスチャーの進行中・失敗・成功の状態を遷移させるのがこのクラスの役目となります。
UIGestureRecognizer のサブクラスは、以下の5つのメソッドをオーバーライドします。
- (void)reset;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
reset 以外は、UIResponder のタッチメイベントそのままなので、馴染み深いでしょう。
ジェスチャーには大きく分けて、単発的なジェスチャーと、連続的なジェスチャーがあります。今回は、ジェスチャーの中でも比較的簡単な単発的なジェスチャーを扱います。
単発的なジェスチャーでは、タッチイベントの中でジェスチャーが確定したと判断できたら、state プロパティを
UIGestureRecognizerStateRecognized に設定します。逆に失敗したと判断したら、
UIGestureRecognizerStateFailed を設定します。
非常に簡単です。
公式ドキュメントには、チェックマーク(✔)ジェスチャーを実装する例がありますので、ここではもうちょっと複雑に以下のような三角旗ジェスチャーを作ってみました。
ヘッダファイルは以下のような変数を定義しておきます。
@interface FlagGesture : UIGestureRecognizer
{
unsigned int actMode; // ジェスチャー管理用ワーク
CGPoint startPoint; // ジェスチャー開始座標
CGPoint topPoint; // 三角旗の天井座標
}
touchesBegan:メソッドは以下のようになります。
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
if ([touches count]!=1) {
// マルチタッチを感知したら失敗とする
self.state = UIGestureRecognizerStateFailed;
return;
}
// ジェスチャー開始座標格納
startPoint = [[touches anyObject] locationInView:self.view];
}
touchesMoved:メソッドは以下のようになります。
actMode が 0 の時は、ジェスチャーが開始座標から旗の天井に向かうところを判定します。ここは垂直に上に伸びるジェスチャーなので、15°以上傾いたら失敗させるようにしています。
actMode が 1 の時は、ジェスチャーが旗の右端突端に向かうところを判定します。
actMode が 2 の時は、右端突端からジェスチャー開始地点まで戻るところを判定します。
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesMoved:touches withEvent:event];
CGPoint nowPoint = [[touches anyObject] locationInView:self.view];
CGPoint prevPoint = [[touches anyObject] previousLocationInView:self.view];
switch (actMode) {
case 0: // 旗の天井に向かうアクション
if (nowPoint.y<prevPoint.y) {
float rot = atanf(fabsf(nowPoint.x-startPoint.x)/(startPoint.y-nowPoint.y));
if (rot>=15) {
// 15°以上傾いたら失敗
self.state = UIGestureRecognizerStateFailed;
return;
}
} else {
if (nowPoint.x>prevPoint.x) {
CGRect rect = self.view.bounds;
// 垂直線が画面の高さの3分の1あるかを判定材料とする
if ((startPoint.y-nowPoint.y)>=(rect.size.height*0.33f)) {
// 旗の右端突端に向かうジェスチャーになったと仮定する
actMode = 1;
topPoint = nowPoint; // 旗の天井座標格納
break;
}
}
self.state = UIGestureRecognizerStateFailed;
}
break;
case 1: // 旗の右端突起に向かうアクション
if ((nowPoint.x>=prevPoint.x)&&(nowPoint.y>=prevPoint.y)) {
// OK
} else if ((nowPoint.x<prevPoint.x)&&(nowPoint.y>=prevPoint.y)) {
float dy = startPoint.y-topPoint.y;
if (((topPoint.y+dy*0.35f)>nowPoint.y)||((startPoint.y-dy*0.35f)<nowPoint.y)) {
// 突端のY座標が旗の垂直線の中央にないと失敗とする
self.state = UIGestureRecognizerStateFailed;
return;
}
actMode = 2;
}
break;
case 2: // 開始座標に戻るアクション
if ((nowPoint.x<=prevPoint.x)&&(nowPoint.y>=prevPoint.y)) {
// OK
} else {
self.state = UIGestureRecognizerStateFailed;
}
break;
}
}
touchesEnded:メソッドは以下のようになります。
タッチが離れた時、actMode が 2 であり、且つ開始座標に近ければ、ジェスチャーが成功したとします。
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
if ((self.state == UIGestureRecognizerStatePossible)&&(actMode==2)) {
CGPoint nowPoint = [[touches anyObject] locationInView:self.view];
CGPoint delta = CGPointMake(nowPoint.x-startPoint.x,nowPoint.y-startPoint.y);
float k = delta.x*delta.x + delta.y*delta.y;
if (k<(50*50)) {
self.state = UIGestureRecognizerStateRecognized;
}
}
}
touchesCancelled:メソッドは以下のようになります。
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesCancelled:touches withEvent:event];
self.state = UIGestureRecognizerStateFailed;
}
reset メソッドは以下のようになります。
ジェスチャーが失敗・成功後に自動的に呼ばれるメソッドです。管理用ワークを初期化しておきます。
- (void)reset
{
[super reset];
actMode = 0;
}
カスタムジェスチャークラスができたら、実際にビューに登録してみます。
以下の例では、ビューコントローラーの viewDidLoad メソッドから登録しています。
viewDidUnload メソッドでは、登録したジェスチャーを取り除いています。
ジェスチャーの登録・削除は、各アプリで必要な箇所に組み込む必要があるでしょう。
- (void)viewDidLoad
{
[super viewDidLoad];
FlagGesture *gesture = [[[FlagGesture alloc] initWithTarget:self action:@selector(handleGestureFlag:)] autorelease];
[self.view addGestureRecognizer:gesture];
}
- (void)viewDidUnload
{
[super viewDidUnload];
NSEnumerator *e = [self.view.gestureRecognizers objectEnumerator];
while (UIGestureRecognizer *gesture = (UIGestureRecognizer*)[e nextObject]) {
[self.view removeGestureRecognizer:gesture];
}
}
- (void)handleGestureFlag:(UIGestureRecognizer*)sender
{
NSLog(@"FlagGesture OK! : state = %d",sender.state);
}
今回は、ちょっと長くなってしまいました。ぜひ、ご自分用の楽しいジェスチャーを作ってみて下さい。