ZStackにおけるViewについて考察する(003,タップだけでなくドラッグにも対応)

この記事は下記の記事の続きです。画面上のタップだけではなく、画面上を指で滑らせるドラッグ動作にも反応して座標を取得する方法を紹介します。

ZStackにおけるViewについて考察する(002,タップ座標を十字カーソルで表示)

ビューをタップした座標を十字カーソルを表示してわかりやすくします。

Fig. 1 Rectangle 内で指をすべらせる
Fig. 2 Rectangle の右下やや外側にむかって指をすべらせる

タップジェスチャに記述しているコードと全く同じものを、DragGesture の .onChanged と .onEnded に書き込んでください。

75、76行目が、ドラッグした座標を取得しているところです、tap.location で取得できます。
109、110行目も、ドラッグした座標を取得しているところです、tap.location で取得できます。

タップした座標は、下記のコードでは39、40行目で取得していますが、こちらは tap だけで座標を取得しています。tap.locationtap で微妙な違いですがビルドが通らなくて困ってしまいそうなところなのでご注意ください。

72行目が画面上で指を滑らせたときのドラッグの反応性を決定しています。minimumDistance を 0 にしているのは、指をすべらせてすぐにドラッグに反応させたいからです。この値を 100 などの極端な値に変更して試してみてください。画面上で距離が 100.0 以上指を接触させたまま動かないとドラッグ動作に反応しません。

ツムツムのようなゲームはドラッグ動作がキモなので minimumDistance を 0 にしないといけないでしょう。

import SwiftUI

struct ContentView: View {

    let RECT_W: CGFloat = 320
    let RECT_H: CGFloat = 640

    let COLOR_RECT: Color = Color.blue
    let COLOR_TEXT: Color = Color.white

    let COLOR_INSIDE: Color = Color( red: 0x00, green: 0xff, blue: 0x00, opacity: 0xff )
    let COLOR_OUTSIDE: Color = Color( red: 0xff, green: 0x00, blue: 0x00, opacity: 0xff )

    let CURSOR_COLOR: Color = Color.yellow
    let CURSOR_LINE_WIDTH: CGFloat = 3.0

    @State var TapX: CGFloat = 0.0
    @State var TapY: CGFloat = 0.0
    @State var InsideRectTapX: CGFloat = 0.0
    @State var InsideRectTapY: CGFloat = 0.0

    @State var FlagInsideRect = true

    var body: some View {

        ZStack{

            // 最背面.
            Rectangle()
                .foregroundColor( FlagInsideRect ? COLOR_INSIDE : COLOR_OUTSIDE )

            // 中層面.
            Rectangle()
                .frame( width: RECT_W, height: RECT_H ) // サイズを指定する.
                .foregroundColor( COLOR_RECT ) // 色を指定する.
                .onTapGesture {
                    tap in

                    TapX = tap.x
                    TapY = tap.y

                    // いったんそのままタップ座標を代入する.
                    InsideRectTapX = TapX
                    InsideRectTapY = TapY

                    // いったんフラグを真にしておく.
                    FlagInsideRect = true

                    // Rectangleの内側か外側か判定して InsideRectTapXとY を調整する.
                    if ( InsideRectTapX < 0.0 ){
                        InsideRectTapX = 0.0;
                        FlagInsideRect = false
                    }

                    if ( InsideRectTapY < 0.0 ){
                        InsideRectTapY = 0.0
                        FlagInsideRect = false
                    }

                    if ( InsideRectTapX >= RECT_W ){
                        InsideRectTapX = RECT_W - 1
                        FlagInsideRect = false
                    }

                    if ( InsideRectTapY >= RECT_H ){
                        InsideRectTapY = RECT_H - 1
                        FlagInsideRect = false
                    }

                }
                .gesture(
                    DragGesture( minimumDistance: 0 )
                        .onChanged{ tap in

                            TapX = tap.location.x
                            TapY = tap.location.y

                            // いったんそのままタップ座標を代入する.
                            InsideRectTapX = TapX
                            InsideRectTapY = TapY

                            // いったんフラグを真にしておく.
                            FlagInsideRect = true

                            // Rectangleの内側か外側か判定して InsideRectTapXとY を調整する.
                            if ( InsideRectTapX < 0.0 ){
                                InsideRectTapX = 0.0;
                                FlagInsideRect = false
                            }

                            if ( InsideRectTapY < 0.0 ){
                                InsideRectTapY = 0.0
                                FlagInsideRect = false
                            }

                            if ( InsideRectTapX >= RECT_W ){
                                InsideRectTapX = RECT_W - 1
                                FlagInsideRect = false
                            }

                            if ( InsideRectTapY >= RECT_H ){
                                InsideRectTapY = RECT_H - 1
                                FlagInsideRect = false
                            }

                        }
                        .onEnded { tap in

                            TapX = tap.location.x
                            TapY = tap.location.y

                            // いったんそのままタップ座標を代入する.
                            InsideRectTapX = TapX
                            InsideRectTapY = TapY

                            // いったんフラグを真にしておく.
                            FlagInsideRect = true

                            // Rectangleの内側か外側か判定して InsideRectTapXとY を調整する.
                            if ( InsideRectTapX < 0.0 ){
                                InsideRectTapX = 0.0;
                                FlagInsideRect = false
                            }

                            if ( InsideRectTapY < 0.0 ){
                                InsideRectTapY = 0.0
                                FlagInsideRect = false
                            }

                            if ( InsideRectTapX >= RECT_W ){
                                InsideRectTapX = RECT_W - 1
                                FlagInsideRect = false
                            }

                            if ( InsideRectTapY >= RECT_H ){
                                InsideRectTapY = RECT_H - 1
                                FlagInsideRect = false
                            }

                        }
                    )

            // 十字カーソルを描画する.
            Path { path in

                let the_x = InsideRectTapX
                let the_y = InsideRectTapY

                // 始点から終点にむかって線分を引く(垂直線).
                path.move(to: CGPoint(x: the_x, y: 0 ))
                path.addLine(to: CGPoint(x: the_x, y: RECT_H ))

                // 始点から終点にむかって線分を引く(水平線).
                path.move(to: CGPoint(x: 0, y: the_y ))
                path.addLine(to: CGPoint(x: RECT_W, y: the_y ))

            }
            .stroke( CURSOR_COLOR, lineWidth: CURSOR_LINE_WIDTH )
            .frame( width: RECT_W, height: RECT_H )

            // 最前面.
            VStack{

                Text( String( format: "Rectangle WH %.1f * %.1f", RECT_W, RECT_H ))
                    .foregroundColor( COLOR_TEXT )
                Text( String( format: "TapXY( %.1f, %.1f )", TapX, TapY ))
                    .foregroundColor( COLOR_TEXT )
                Text( String( format: "InsideRectTapXY( %.1f, %.1f )", InsideRectTapX, InsideRectTapY ))
                    .foregroundColor( COLOR_TEXT )
                Text( String( format: "FlagInsideRect is %d", FlagInsideRect ))
                    .foregroundColor( COLOR_TEXT )

            }

        }

    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}