オリジナルのジェスチャーを作るには、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); }
今回は、ちょっと長くなってしまいました。ぜひ、ご自分用の楽しいジェスチャーを作ってみて下さい。
0 件のコメント:
コメントを投稿