Xamarin.iOS のマルチタッチ フィンガー トラッキング

このドキュメントでは、複数の指によるタッチ イベントを追跡する方法について説明します

マルチタッチ アプリケーションでは、画面上で複数の指が同時に動かされた場合、個々の指を追跡することが必要な場合があります。 一般的なアプリケーションの 1 つとして、フィンガー ペイント プログラムがあります。 あなたは、ユーザーが 1 つの指で描画できるだけでなく、同時に複数の指で描画できるようにする必要があります。 プログラムで複数のタッチ イベントを処理する場合は、これらの指を区別する必要があります。

指が最初に画面に触れると、iOS によってその指の UITouch オブジェクトが作成されます。 このオブジェクトは画面上を移動する指と同じように保持され、そして指が画面から離れると、その時点でオブジェクトは破棄されます。 指の追跡を続けるために、プログラムではこの UITouch オブジェクトを直接保存しないようにする必要があります。 代わりに、型 IntPtrHandle プロパティを使用すれば、これらの UITouch オブジェクトを一意に識別できます。

ほとんどの場合、個々の指を追跡するプログラムには、タッチを追跡するための辞書が用意されています。 iOS プログラムの場合、特定の指を識別する Handle 値が辞書キーとなります。 辞書の値は、アプリケーションによって異なります。 FingerPaint プログラムでは、各指のストローク (タッチから解放まで) が、その指で描かれた線をレンダリングするのに必要である情報をすべて含むオブジェクトに関連付けられます。 プログラムでは、これを目的とした小さな FingerPaintPolyline クラスを定義します。

class FingerPaintPolyline
{
    public FingerPaintPolyline()
    {
        Path = new CGPath();
    }

    public CGColor Color { set; get; }

    public float StrokeWidth { set; get; }

    public CGPath Path { private set; get; }
}

各ポリラインには、色、ストロークの幅、および描画中に線の複数のポイントを蓄積してレンダリングする iOS グラフィックス CGPath オブジェクトが設定されます。

以下に示すコードの残りの部分はすべて、FingerPaintCanvasView という名前の、UIView の派生クラスに含まれています。 このクラスでは、1 つまたは複数の指によってオブジェクトがアクティブに描画されている間、FingerPaintPolyline 型のオブジェクトの辞書を維持します。

Dictionary<IntPtr, FingerPaintPolyline> inProgressPolylines = new Dictionary<IntPtr, FingerPaintPolyline>();

この辞書を使用すると、各指に関連付けられている FingerPaintPolyline 情報を UITouch オブジェクトの Handle プロパティに基づいて、ビューですばやく取得できます。

FingerPaintCanvasView クラスには、完了したポリラインに対する List オブジェクトも保持されます。

List<FingerPaintPolyline> completedPolylines = new List<FingerPaintPolyline>();

この List に含まれるオブジェクトは、それらが描画された順番となります。

FingerPaintCanvasView では、View によって定義された 5 つのメソッドをオーバーライドします。

各種の Touches オーバーライドにより、ポリラインを構成するポイントが蓄積されます。

[Draw] オーバーライドでは、完成したポリライン、そして進行中のポリラインを描画します。

public override void Draw(CGRect rect)
{
    base.Draw(rect);

    using (CGContext context = UIGraphics.GetCurrentContext())
    {
        // Stroke settings
        context.SetLineCap(CGLineCap.Round);
        context.SetLineJoin(CGLineJoin.Round);

        // Draw the completed polylines
        foreach (FingerPaintPolyline polyline in completedPolylines)
        {
            context.SetStrokeColor(polyline.Color);
            context.SetLineWidth(polyline.StrokeWidth);
            context.AddPath(polyline.Path);
            context.DrawPath(CGPathDrawingMode.Stroke);
        }

        // Draw the in-progress polylines
        foreach (FingerPaintPolyline polyline in inProgressPolylines.Values)
        {
            context.SetStrokeColor(polyline.Color);
            context.SetLineWidth(polyline.StrokeWidth);
            context.AddPath(polyline.Path);
            context.DrawPath(CGPathDrawingMode.Stroke);
        }
    }
}

Touches オーバーライドからは、メソッドに渡される touches 引数に格納された 1 つまたは複数の UITouch オブジェクトによって示される、複数の指のアクションが報告される可能性があります。 TouchesBegan オーバーライドでは、これらのオブジェクトをループ処理します。 各 UITouch オブジェクトごとに、メソッドでは新しい FingerPaintPolyline オブジェクトを作成して初期化します。これには、LocationInView メソッドから取得した指の初期位置を格納することも含まれます。 この FingerPaintPolyline オブジェクトを InProgressPolylines 辞書に追加するには、UITouch オブジェクトの Handle プロパティを辞書キーとして使用します。

public override void TouchesBegan(NSSet touches, UIEvent evt)
{
    base.TouchesBegan(touches, evt);

    foreach (UITouch touch in touches.Cast<UITouch>())
    {
        // Create a FingerPaintPolyline, set the initial point, and store it
        FingerPaintPolyline polyline = new FingerPaintPolyline
        {
            Color = StrokeColor,
            StrokeWidth = StrokeWidth,
        };

        polyline.Path.MoveToPoint(touch.LocationInView(this));
        inProgressPolylines.Add(touch.Handle, polyline);
    }
    SetNeedsDisplay();
}

SetNeedsDisplay を呼び出して Draw オーバーライドの呼び出しを生成し、画面を更新することで、メソッドは終了します。

1 つまたは複数の指を画面上で移動させると、View によって、その TouchesMoved オーバーライドの複数の呼び出しが行われます。 このオーバーライドでも同様に、touches 引数に格納されている UITouch オブジェクトをループ処理し、指の現在の位置をグラフィックス パスに追加します。

public override void TouchesMoved(NSSet touches, UIEvent evt)
{
    base.TouchesMoved(touches, evt);

    foreach (UITouch touch in touches.Cast<UITouch>())
    {
        // Add point to path
        inProgressPolylines[touch.Handle].Path.AddLineToPoint(touch.LocationInView(this));
    }
    SetNeedsDisplay();
}

touches コレクションには、TouchesBegan または TouchesMoved の最後の呼び出し以降に移動した指に対応する UITouch オブジェクトのみが含まれます。 現在画面に触れている "すべて" の指に対応する UITouch オブジェクトが必要な場合、その情報は、メソッドへの UIEvent 引数の AllTouches プロパティを介して入手できます。

TouchesEnded オーバーライドには、2 つのジョブがあります。 グラフィックス パスに最後のポイントを追加し、FingerPaintPolyline オブジェクトを inProgressPolylines 辞書から completedPolylines リストに転送する必要があります。

public override void TouchesEnded(NSSet touches, UIEvent evt)
{
    base.TouchesEnded(touches, evt);

    foreach (UITouch touch in touches.Cast<UITouch>())
    {
        // Get polyline from dictionary and remove it from dictionary
        FingerPaintPolyline polyline = inProgressPolylines[touch.Handle];
        inProgressPolylines.Remove(touch.Handle);

        // Add final point to path and save with completed polylines
        polyline.Path.AddLineToPoint(touch.LocationInView(this));
        completedPolylines.Add(polyline);
    }
    SetNeedsDisplay();
}

TouchesCancelled オーバーライドを処理するには、辞書内の FingerPaintPolyline オブジェクトを破棄するだけです。

public override void TouchesCancelled(NSSet touches, UIEvent evt)
{
    base.TouchesCancelled(touches, evt);

    foreach (UITouch touch in touches.Cast<UITouch>())
    {
        inProgressPolylines.Remove(touch.Handle);
    }
    SetNeedsDisplay();
}

つまり、この処理により、FingerPaint プログラムでは、個々の指を追跡し、その結果を画面上に描画できるようになります。

Tracking individual fingers and drawing the results on the screen

これで、画面上の個々の指を追跡し、それらを区別する方法がわかりました。