Xamarin.iOS でのタッチ イベントとジェスチャ

iOS アプリケーションのタッチ イベントとタッチ API は、デバイスのすべての物理的な操作の中心になるため、理解しておくことが重要です。 すべてのタッチ操作には UITouch オブジェクトが含まれます。 この記事では、UITouch クラスとその API を使用してタッチをサポートする方法について説明します。 後で、ジェスチャをサポートする方法を学習するための知識について詳しく説明します。

タッチの有効化

UIControl からサブクラス化された UIKit のコントロールは、ユーザーの操作に非常に依存するため UIKit にジェスチャが組み込まれています。そのため、タッチを有効にする必要はありません。 これは既に有効になっています。

ただし、UIKit のビューの多くでは、既定ではタッチが有効になっていません。 コントロールに対してタッチを有効にする方法は 2 つあります。 最初の方法は、次のスクリーンショットに示すように、iOS Designer の Property Pad で [User Interaction Enabled]\(ユーザー操作が有効\) チェックボックスをオンにすることです。

Check the User Interaction Enabled checkbox in the Property Pad of the iOS Designer

また、コントローラーを使用して、UIView クラスで UserInteractionEnabled プロパティを true に設定することもできます。 これは、UI がコードで作成される場合に必要です。

次のコード行は例です。

imgTouchMe.UserInteractionEnabled = true;

タッチ イベント

ユーザーが画面に触れたり、指を動かしたり、指を離したりしたときに発生するタッチには 3 つのフェーズがあります。 これらのメソッドは、UIView の基底クラスである、UIResponder で定義されています。 iOS は、タッチを処理するために、UIViewUIViewController の関連付けられているメソッドをオーバーライドします。

  • TouchesBegan – これは、画面が最初にタッチされたときに呼び出されます。
  • TouchesMoved – これは、ユーザーが指を画面上でスライドさせてタッチの位置が変わると呼び出されます。
  • TouchesEnded または TouchesCancelledTouchesEnded は、ユーザーの指が画面から離されたときに呼び出されます。 TouchesCancelled は、iOS がタッチをキャンセルした場合に呼び出されます。たとえば、ユーザーがボタンから指をスライドして離して、ボタンを押したことを取り消した場合などです。

タッチ イベントは、UIView のスタックを再帰的に移動して、タッチ イベントがビュー オブジェクトの境界内にあるかどうかを確認します。 これは、多くの場合、ヒット テストと呼ばれます。 これらは、最初に最上位の UIView または UIViewController で呼び出され、次にビュー階層内でその下にある UIView および UIViewControllers で呼び出されます。

ユーザーが画面にタッチするたびに UITouch オブジェクトが作成されます。 この UITouch オブジェクトには、タッチがいつ発生したか、タッチが発生した場所、タッチがスワイプかどうかなどの、タッチに関するデータが含まれます。タッチ イベントには、タッチ プロパティ (1 つ以上のタッチが含まれる NSSet) が渡されます。 このプロパティを使用してタッチへの参照を取得し、アプリケーションの応答を決定できます。

タッチ イベントの 1 つをオーバーライドするクラスは、最初に基本実装を呼び出してから、イベントに関連付けられている UITouch オブジェクトを取得する必要があります。 最初のタッチへの参照を取得するには、次の例に示すように、AnyObject プロパティを呼び出し、UITouch としてキャストします。

public override void TouchesBegan (NSSet touches, UIEvent evt)
{
    base.TouchesBegan (touches, evt);
    UITouch touch = touches.AnyObject as UITouch;
    if (touch != null)
    {
        //code here to handle touch
    }
}

iOS は画面上の連続したクイック タッチを自動的に認識し、それらすべてを 1 つの UITouch オブジェクトに 1 回のタップとして収集します。 これにより、次のコードに示すように、TapCount プロパティをチェックするのと同じくらい簡単にダブル タップを確認できます。

public override void TouchesBegan (NSSet touches, UIEvent evt)
{
    base.TouchesBegan (touches, evt);
    UITouch touch = touches.AnyObject as UITouch;
    if (touch != null)
    {
        if (touch.TapCount == 2)
        {
            // do something with the double touch.
        }
    }
}

マルチタッチ

コントロールでは、マルチタッチは既定では有効になっていません。 次のスクリーンショットに示すように、iOS Designer でマルチタッチを有効にすることができます。

Multi-touch enabled in the iOS Designer

次のコード行に示すように MultipleTouchEnabled プロパティを設定することで、プログラムでマルチタッチを設定することもできます。

imgTouchMe.MultipleTouchEnabled = true;

画面にタッチした指の数を確認するには、UITouch プロパティで Count プロパティを使用します。

public override void TouchesBegan (NSSet touches, UIEvent evt)
{
    base.TouchesBegan (touches, evt);
    lblNumberOfFingers.Text = "Number of fingers: " + touches.Count.ToString();
}

タッチ位置の判断

メソッド UITouch.LocationInView は、特定のビュー内のタッチの座標を保持する CGPoint オブジェクトを返します。 さらに、メソッド Frame.Contains を呼び出すことによって、その場所がコントロール内にあるかどうかをテストできます。 次のコード スニペットは、この例を示しています。

if (this.imgTouchMe.Frame.Contains (touch.LocationInView (this.View)))
{
    // the touch event happened inside the UIView imgTouchMe.
}

ここまでで、iOS でのタッチ イベントについて理解したので、次はジェスチャ認識エンジンについて学習しましょう。

ジェスチャ認識エンジン

ジェスチャ認識エンジンを使用すると、アプリケーションでのタッチをサポートするためのプログラミング作業を大幅に簡素化し、削減できます。 iOS ジェスチャ認識エンジンは、一連のタッチ イベントを単一のタッチ イベントに集約します。

Xamarin.iOS は、次の組み込みのジェスチャ認識エンジンの基底クラスとしてクラス UIGestureRecognizer を提供します。

  • UITapGestureRecognizer – これは 1 回以上のタップ用です。
  • UIPinchGestureRecognizer – ピンチインおよびピンチアウト。
  • UIPanGestureRecognizer – パンまたはドラッグ。
  • UISwipeGestureRecognizer – 任意の方向へのスワイプ。
  • UIRotationGestureRecognizer – 時計回りまたは反時計回りでの、2 本の指の回転。
  • UILongPressGestureRecognizer – プレス アンド ホールド。長押しまたはロングクリックと呼ばれることもあります。

ジェスチャ認識エンジンを使用する基本的なパターンは次のとおりです。

  1. ジェスチャ認識エンジンをインスタンス化する – まず、UIGestureRecognizer サブクラスをインスタンス化します。 インスタンス化されたオブジェクトはビューによって関連付けられるので、ビューが破棄されるとガベージ コレクションが実行されます。 このビューをクラス レベル変数として作成する必要はありません。
  2. ジェスチャ設定を構成する – 次の手順では、ジェスチャ認識エンジンを構成します。 Xamarin のドキュメントで UIGestureRecognizer とそのサブクラスについて参照して、UIGestureRecognizer インスタンスの動作を制御するために設定できるプロパティの一覧を確認してください。
  3. ターゲットを構成する – Objective-C から引き継いで、Xamarin.iOS はジェスチャ認識エンジンでジェスチャとの一致が発生したときにイベントを発生させません。 UIGestureRecognizer には、ジェスチャ認識エンジンが一致したときに実行するコードを含む匿名デリゲートまたは Objective-C セレクターを受け入れることができる AddTarget というメソッドがあります。
  4. ジェスチャ認識エンジンを有効にする – タッチ イベントと同様に、ジェスチャはタッチ操作が有効になっている場合にのみ認識されます。
  5. ジェスチャ認識エンジンをビューに追加する – 最後の手順は、View.AddGestureRecognizer を呼び出して、それにジェスチャ認識エンジン オブジェクトを渡すことによって、ビューにジェスチャを追加することです。

コードで実装する方法の詳細については、ジェスチャ認識エンジンのサンプルを参照してください。

ジェスチャのターゲットが呼び出されると、発生したジェスチャへの参照が渡されます。 これにより、ジェスチャ ターゲットは、発生したジェスチャに関する情報を取得できます。 使用可能な情報の範囲は、使用されたジェスチャ認識エンジンの種類によって異なります。 各 UIGestureRecognizer サブクラスで使用できるデータについては、Xamarin のドキュメントを参照してください。

ジェスチャ認識エンジンがビューに追加されると、ビュー (およびその下のビュー) はタッチ イベントを受信しないことに注意してください。 ジェスチャと同時にタッチ イベントを許可するには、次のコードに示すように、CancelsTouchesInView プロパティを false に設定する必要があります。

_tapGesture.Recognizer.CancelsTouchesInView = false;

UIGestureRecognizer に、ジェスチャ認識エンジンの状態に関する重要な情報を提供する State プロパティがあります。 このプロパティの値が変更されるたびに、iOS はサブスクライブ メソッドを呼び出して更新を行います。 カスタム ジェスチャ認識エンジンが State プロパティを更新しない場合、サブスクライバーは呼び出されず、ジェスチャ認識エンジンは役に立ちません。

ジェスチャは、次の 2 種類のいずれかとして要約できます。

  1. 不連続 – これらのジェスチャは、最初に認識されたときにのみ発生します。
  2. 継続的 – これらのジェスチャは、認識されている限り引き続き発生します。

ジェスチャ認識エンジンは、次のいずれかの状態で存在します。

  • Possible – これは、すべてのジェスチャ認識エンジンの初期状態です。 これは State プロパティの既定値です。
  • Began – 連続ジェスチャが最初に認識されると、状態は Began に設定されます。 これにより、サブスクライブでは、ジェスチャ認識が開始されるタイミングと変更されたタイミングを区別できます。
  • Changed – 連続ジェスチャが開始されたが、完了していない場合、ジェスチャの予想されるパラメーター内にある限り、タッチが移動または変更されるたびに状態は Changed に設定されます。
  • Cancelled – 認識エンジンが Began から Changed に切り替わった後、ジェスチャのパターンに合わなくなったようにタッチが変更された場合に、この状態が設定されます。
  • Recognized – ジェスチャ認識エンジンが一連のタッチに一致すると状態が設定され、ジェスチャが完了したことがサブスクライバーに通知されます。
  • Ended – これは、Recognized 状態の別名です。
  • Failed – ジェスチャ認識エンジンがリッスンしているタッチと一致しなくなった場合、状態は Failed に変わります。

Xamarin.iOS は、これらの値を UIGestureRecognizerState 列挙体に表します。

複数のジェスチャの操作

既定では、iOS では、既定のジェスチャを同時に実行することはできません。 代わりに、各ジェスチャ認識エンジンは、非確定的な順序でタッチ イベントを受け取ります。 次のコード スニペットは、ジェスチャ認識エンジンを同時に実行する方法を示しています。

gesture.ShouldRecognizeSimultaneously += (UIGestureRecognizer r) => { return true; };

iOS でジェスチャを無効にすることもできます。 ジェスチャ認識エンジンがアプリケーションの状態と現在のタッチ イベントを調べ、ジェスチャを認識する方法と認識すべきかどうかを決定できるようにする 2 つのデリゲート プロパティがあります。 次の 2 つのイベントがあります。

  1. ShouldReceiveTouch – このデリゲートは、ジェスチャ認識エンジンにタッチ イベントが渡される直前に呼び出され、タッチを調べて、ジェスチャ認識エンジンによって処理されるタッチを決定する機会を提供します。
  2. ShouldBegin – これは、認識エンジンが状態を可能から他の状態に変更しようとしたときに呼び出されます。 false を返すと、ジェスチャ認識エンジンの状態が強制的に Failed に変更されます。

次のコード スニペットに示すように、これらのメソッドを厳密に型指定された UIGestureRecognizerDelegate、弱いデリゲート、またはイベント ハンドラー構文を使用したバインドでオーバーライドできます。

gesture.ShouldReceiveTouch += (UIGestureRecognizer r, UITouch t) => { return true; };

最後に、ジェスチャ認識エンジンをキューに登録して、別のジェスチャ認識エンジンが失敗した場合にのみ成功するようにできます。 たとえば、ダブル タップ ジェスチャ認識エンジンが失敗した場合にのみ、シングル タップ ジェスチャ認識エンジンが成功するようにします。 次のコード スニペットは、この例を示しています。

singleTapGesture.RequireGestureRecognizerToFail(doubleTapGesture);

カスタム ジェスチャの作成

iOS には既定のジェスチャ認識エンジンがいくつか用意されていますが、場合によってはカスタム ジェスチャ認識エンジンを作成することが必要な場合があります。 カスタム ジェスチャ認識エンジンを作成するには、次の手順を実行します。

  1. UIGestureRecognizer をサブクラス化します。
  2. 適切なタッチ イベント メソッドをオーバーライドします。
  3. 基底クラスの State プロパティを使用して認識状態をバブリングします。

この実際の例については、iOS でのタッチの使用に関するチュートリアルを参照してください。