[SwiftUI]Gestureを知る！

SwiftUI初心者がGestureを一から調べてみた過程を記録します！
これから学ぼうと思う方にとって有益な情報となれば嬉しいです:)

# Add gesture modifiers to a view
> Each gesture you add applies to a specific view in the view hierarchy. To recognize a gesture event on a particular view, create and configure the gesture, and then use the gesture(_:including:) modifier:
> 
> 追加した各ジェスチャは、ビュー階層内の特定のビューに適用されます。 特定のビューでジェスチャ イベントを認識するには、ジェスチャを作成および構成してから、gesture(_:including:) 修飾子を使用します。

```swift
struct ShapeTapView: View {
    var body: some View {
        let tap = TapGesture()
            .onEnded { _ in
                print("View tapped!")
            }
        
        return Circle()
            .fill(Color.blue)
            .frame(width: 100, height: 100, alignment: .center)
            .gesture(tap)
    }
}
```

https://developer.apple.com/documentation/swiftui/adding-interactivity-with-gestures#Add-gesture-modifiers-to-a-view

### gesture(_:including:)
第一引数がジェネリクス型で、`Gesture`プロトコルを継承する必要があります。
第二引数には、デフォルト値で`.all`が設定されています。
```swift
func gesture<T>(
    _ gesture: T,
    including mask: GestureMask = .all
) -> some View where T : Gesture
```

`GestureMask`は、特定のジェスチャーがビュー階層のどのレベルで反応するか、または反応しないかを細かく制御するために使用されます。例えば、子Viewにはジェスチャーを反応させないということが可能になります。

https://developer.apple.com/documentation/swiftui/gesturemask

### Gestureプロトコル
Gestureプロトコルを継承することで、`update`, `onChange`, `onEnded`などの関数が使えるようになります。

https://developer.apple.com/documentation/swiftui/gesture

`TapGesture`, `DragGesture`などが、Gestureプロトコルを継承しています。

### 優先順位
以下は、Imageをタップすると、"Tap on Image."が表示され、四角形をタップすると"Tap on VStack."が表示されます。このように、Viewに直接適用されたジェスチャーが優先されます。
```swift
struct GestureExample: View {
    @State private var message = "Message"
    let newGesture = TapGesture().onEnded {
        print("Tap on VStack.")
    }


    var body: some View {
        VStack(spacing:25) {
            Image(systemName: "heart.fill")
                .resizable()
                .frame(width: 75, height: 75)
                .padding()
                .foregroundColor(.red)
                .onTapGesture {
                    print("Tap on image.")
                }
            Rectangle()
                .fill(Color.blue)
        }
        .gesture(newGesture)
        .frame(width: 200, height: 200)
        .border(Color.purple)
    }
}
```

# Respond to gesture callbacks
以下はGesturedプロトコルを継承した型で使える関数です。
`TapGesture`や、`DragGesture`などが当てはまります。
### onEnded(_:)
> Adds an action to perform when the gesture ends.

``` swift
func onEnded(_ action: @escaping (Self.Value) -> Void) -> _EndedGesture<Self>
```

クロージャのバラメーターには、ジェスチャの最終値が含まれます。
`Value`の値は、構造体によって違います。
例えば、`TapGesture`の場合、`Value`には、`Void`が含まれていました。
``` swift
public struct TapGesture : Gesture {

    /// The required number of tap events.
    public var count: Int

    /// Creates a tap gesture with the number of required taps.
    ///
    /// - Parameter count: The required number of taps to complete the tap
    ///   gesture.
    public init(count: Int = 1)

    /// The type of gesture representing the body of `Self`.
    public typealias Body = Never

    /// The type representing the gesture's value.
    public typealias Value = Void
}
```

`DragGesture`の場合は、`time`や、`location`など、様々な値が含まれていました。
``` swift
public struct DragGesture : Gesture {

    /// The attributes of a drag gesture.
    public struct Value : Equatable, Sendable {

        /// The time associated with the drag gesture's current event.
        public var time: Date

        /// The location of the drag gesture's current event.
        public var location: CGPoint

        /// The location of the drag gesture's first event.
        public var startLocation: CGPoint

        /// The total translation from the start of the drag gesture to the
        /// current event of the drag gesture.
        ///
        /// This is equivalent to `location.{x,y} - startLocation.{x,y}`.
        public var translation: CGSize { get }

        /// The current drag velocity.
        public var velocity: CGSize { get }

        /// A prediction, based on the current drag velocity, of where the final
        /// location will be if dragging stopped now.
        public var predictedEndLocation: CGPoint { get }

        /// A prediction, based on the current drag velocity, of what the final
        /// translation will be if dragging stopped now.
        public var predictedEndTranslation: CGSize { get }

        /// Returns a Boolean value indicating whether two values are equal.
        ///
        /// Equality is the inverse of inequality. For any values `a` and `b`,
        /// `a == b` implies that `a != b` is `false`.
        ///
        /// - Parameters:
        ///   - lhs: A value to compare.
        ///   - rhs: Another value to compare.
        public static func == (a: DragGesture.Value, b: DragGesture.Value) -> Bool
    }
}
```

https://developer.apple.com/documentation/swiftui/gesture/onended(_:)

### onChanged(_:)
> Adds an action to perform when the gesture’s value changes.

``` swift
func onChanged(_ action: @escaping (Self.Value) -> Void) -> _ChangedGesture<Self>
```

Valueの値が、Equatableを準拠することが必須になります。
パラメーターの値には、ジェスチャの新しい値が含まれます。

https://developer.apple.com/documentation/swiftui/gesture/onchanged(_:)

### updating(_:body:)
> Updates the provided gesture state property as the gesture’s value changes.

```swift
func updating<State>(
    _ state: GestureState<State>,
    body: @escaping (Self.Value, inout State, inout Transaction) -> Void
) -> GestureStateGesture<Self, State>
```

`body`は、ジェスチャの値が変化した時に、SwiftUIが呼び出すコールバックです。
`Self.Value`には変化後の値が入り、`State`には、変化前の値が入ります。
`inout`がついているので、引数でも変更可能です。
`Transaction`は、ジェスチャによって引き起こされるビューの状態変更のアニメーションや適用の仕方をカスタマイズすることができます。（実際にやってはないので、どうカスタマイズできるのかは不明です‥）

<details><summary>Transactionについて</summary>
以下はChatGPTで調べた内容です。

SwiftUIでの`updating`メソッドは、ジェスチャの状態が変化するたびに呼び出されるクロージャを提供します。このクロージャは、ジェスチャの新しい値（`Self.Value`）、状態を保持するための変数（`inout State`）、そして`Transaction`オブジェクト（`inout Transaction`）を受け取ります。`Transaction`オブジェクトには、ビューのアップデートを管理するための情報が含まれています。

`Transaction`は、SwiftUIのビューの状態の変更がどのようにアニメーション化されるか、またはどのように適用されるかを制御するために使用されます。具体的には、以下のような情報が含まれます：

- **アニメーション**：`Transaction`には、ビューの状態変更に適用されるアニメーションが含まれていることがあります。これを設定することで、状態の変化を滑らかにするアニメーションを指定できます。
- **無効化フラグ**：特定のビューの更新を無効化するためのフラグを含むことがあります。これにより、特定の状況下でビューの再描画を防ぐことができます。
- **ディスアビリティグループ**：ビューの更新が他の更新と同時に行われるべきかどうかを制御します。

`Transaction`オブジェクトは、通常、開発者が直接操作することは少なく、SwiftUIのフレームワークによって内部的に使用されます。しかし、`updating`メソッド内で`Transaction`を使用することにより、ジェスチャによって引き起こされるビューの状態変更のアニメーションや適用の仕方をカスタマイズすることができます。例えば、ジェスチャに基づいてビューの状態を更新する際に、特定のアニメーションを適用したり、更新を即時に反映させるためにアニメーションを無効にしたりすることが可能です。
</details>

<details><summary>updating使用例</summary>
ジェスチャの変化に応じてビューを更新するには、GestureState プロパティをビューに追加し、updateing(_:body:) コールバックで更新します。 SwiftUI は、ジェスチャを認識するとすぐに、またジェスチャの値が変更されるたびに、更新コールバックを呼び出します。 たとえば、SwiftUI は、拡大ジェスチャが開始されるとすぐに更新コールバックを呼び出し、その後は倍率値が変更されるたびに再度呼び出します。 SwiftUI は、ユーザーがジェスチャを終了またはキャンセルしたときに更新コールバックを呼び出しません。 代わりに、ジェスチャ状態プロパティはその状態を自動的に初期値にリセットします。

たとえば、ユーザーが長押ししている間に色が変化するビューを作成するには、ジェスチャ状態プロパティを追加し、更新コールバックでそれを更新します。

```swift
struct CounterView: View {
    @GestureState private var isDetectingLongPress = false
    
    var body: some View {
        let press = LongPressGesture(minimumDuration: 1)
            .updating($isDetectingLongPress) { currentState, gestureState, transaction in
                gestureState = currentState
            }
        
        return Circle()
            .fill(isDetectingLongPress ? Color.yellow : Color.green)
            .frame(width: 100, height: 100, alignment: .center)
            .gesture(press)
    }
}
```

https://developer.apple.com/documentation/swiftui/adding-interactivity-with-gestures#Update-transient-UI-state

</details>

### GestureState
> A property wrapper type that updates a property while the user performs a gesture and resets the property back to its initial state when the gesture ends.
> 
> ユーザーがジェスチャを実行している間にプロパティを更新し、ジェスチャが終了するとプロパティを初期状態にリセットするプロパティ ラッパー タイプ。

``` swift
@propertyWrapper @frozen
struct GestureState<Value>
```

> SwiftUI gives us a specific property wrapper for tracking the state of gestures, helpfully called @GestureState. Although you can accomplish the same using a simple @State property wrapper, @GestureState comes with the added ability that it automatically sets your property back to its initial value when the gesture ends, and it’s usually significantly faster than using a simple @State as well.
> 
> SwiftUI は、@GestureState と呼ばれる、ジェスチャーの状態を追跡するための特定のプロパティ ラッパーを提供します。 単純な @State プロパティ ラッパーを使用しても同じことを実現できますが、@GestureState には、ジェスチャの終了時にプロパティを自動的に初期値に戻す追加機能が付属しており、通常は単純な @State を使用するよりも大幅に高速です。

https://www.hackingwithswift.com/quick-start/swiftui/what-is-the-gesturestate-property-wrapper

`@State`に書き直したバージョンがありました。
ジェスチャ終了時に初期値へ戻るようになっています。

```swift
struct  ContentView : View { 
    @State  var isPressed =  false 
    var body: some  View { 
        VStack { 
            Rectangle () 
                .fill(.orange).frame(width: 200 , height: 200 ) 
                .gesture( DragGesture (minimumDistance: 0 ).onChanged { _  in
                     isPressed =  true
                 }.onEnded{ _  in
                     isPressed =  false
                 }) 
                .overlay( 
                    Text (isPressed ?  "Pressing" : "" ) 
                ) 
        } 
    } 
}
```

# 真似したい参考資料
真似して作ってみたい‥🎨
メモとして残しておきます！

https://yakkylab.com/playgrounds-draggesture/

https://fatbobman.com/en/posts/swiftuigesture/

# まとめ
応用的な動きができるよう理解を深めていきたいですー！
資料を作成してくださる先輩方、ありがとうございます🙇