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:) 修飾子を使用します。
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)
}
}
gesture(_:including:)
第一引数がジェネリクス型で、Gesture
プロトコルを継承する必要があります。
第二引数には、デフォルト値で.all
が設定されています。
func gesture<T>(
_ gesture: T,
including mask: GestureMask = .all
) -> some View where T : Gesture
GestureMask
は、特定のジェスチャーがビュー階層のどのレベルで反応するか、または反応しないかを細かく制御するために使用されます。例えば、子Viewにはジェスチャーを反応させないということが可能になります。
Gestureプロトコル
Gestureプロトコルを継承することで、update
, onChange
, onEnded
などの関数が使えるようになります。
TapGesture
, DragGesture
などが、Gestureプロトコルを継承しています。
優先順位
以下は、Imageをタップすると、"Tap on Image."が表示され、四角形をタップすると"Tap on VStack."が表示されます。このように、Viewに直接適用されたジェスチャーが優先されます。
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.
func onEnded(_ action: @escaping (Self.Value) -> Void) -> _EndedGesture<Self>
クロージャのバラメーターには、ジェスチャの最終値が含まれます。
Value
の値は、構造体によって違います。
例えば、TapGesture
の場合、Value
には、Void
が含まれていました。
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
など、様々な値が含まれていました。
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
}
}
onChanged(_:)
Adds an action to perform when the gesture’s value changes.
func onChanged(_ action: @escaping (Self.Value) -> Void) -> _ChangedGesture<Self>
Valueの値が、Equatableを準拠することが必須になります。
パラメーターの値には、ジェスチャの新しい値が含まれます。
updating(_:body:)
Updates the provided gesture state property as the gesture’s value changes.
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
は、ジェスチャによって引き起こされるビューの状態変更のアニメーションや適用の仕方をカスタマイズすることができます。(実際にやってはないので、どうカスタマイズできるのかは不明です‥)
Transactionについて
以下はChatGPTで調べた内容です。SwiftUIでのupdating
メソッドは、ジェスチャの状態が変化するたびに呼び出されるクロージャを提供します。このクロージャは、ジェスチャの新しい値(Self.Value
)、状態を保持するための変数(inout State
)、そしてTransaction
オブジェクト(inout Transaction
)を受け取ります。Transaction
オブジェクトには、ビューのアップデートを管理するための情報が含まれています。
Transaction
は、SwiftUIのビューの状態の変更がどのようにアニメーション化されるか、またはどのように適用されるかを制御するために使用されます。具体的には、以下のような情報が含まれます:
-
アニメーション:
Transaction
には、ビューの状態変更に適用されるアニメーションが含まれていることがあります。これを設定することで、状態の変化を滑らかにするアニメーションを指定できます。 - 無効化フラグ:特定のビューの更新を無効化するためのフラグを含むことがあります。これにより、特定の状況下でビューの再描画を防ぐことができます。
- ディスアビリティグループ:ビューの更新が他の更新と同時に行われるべきかどうかを制御します。
Transaction
オブジェクトは、通常、開発者が直接操作することは少なく、SwiftUIのフレームワークによって内部的に使用されます。しかし、updating
メソッド内でTransaction
を使用することにより、ジェスチャによって引き起こされるビューの状態変更のアニメーションや適用の仕方をカスタマイズすることができます。例えば、ジェスチャに基づいてビューの状態を更新する際に、特定のアニメーションを適用したり、更新を即時に反映させるためにアニメーションを無効にしたりすることが可能です。
updating使用例
ジェスチャの変化に応じてビューを更新するには、GestureState プロパティをビューに追加し、updateing(_:body:) コールバックで更新します。 SwiftUI は、ジェスチャを認識するとすぐに、またジェスチャの値が変更されるたびに、更新コールバックを呼び出します。 たとえば、SwiftUI は、拡大ジェスチャが開始されるとすぐに更新コールバックを呼び出し、その後は倍率値が変更されるたびに再度呼び出します。 SwiftUI は、ユーザーがジェスチャを終了またはキャンセルしたときに更新コールバックを呼び出しません。 代わりに、ジェスチャ状態プロパティはその状態を自動的に初期値にリセットします。たとえば、ユーザーが長押ししている間に色が変化するビューを作成するには、ジェスチャ状態プロパティを追加し、更新コールバックでそれを更新します。
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)
}
}
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.
ユーザーがジェスチャを実行している間にプロパティを更新し、ジェスチャが終了するとプロパティを初期状態にリセットするプロパティ ラッパー タイプ。
@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 を使用するよりも大幅に高速です。
@State
に書き直したバージョンがありました。
ジェスチャ終了時に初期値へ戻るようになっています。
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" : "" )
)
}
}
}
真似したい参考資料
真似して作ってみたい‥🎨
メモとして残しておきます!
まとめ
応用的な動きができるよう理解を深めていきたいですー!
資料を作成してくださる先輩方、ありがとうございます🙇