Article

Adding Interactivity with Gestures

Use gesture modifiers to add interactivity to your app.

Overview

Gesture modifiers handle all of the logic needed to process user-input events such as touches, and recognize when those events match a known gesture pattern, such as a long press or rotation. When recognizing a pattern, SwiftUI runs a callback you use to update the state of a view or perform an action.

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:

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)
    }
}

Respond to Gesture Callbacks

Depending on the callbacks you add to a gesture modifier, SwiftUI reports back to your code whenever the state of the gesture changes. Gesture modifiers offer three ways to receive updates: updating(_:body:), onChanged(_:), and onEnded(_:).

Update Transient UI State

To update a view as a gesture changes, add a GestureState property to your view and update it in the updating(_:body:) callback. SwiftUI invokes the updating callback as soon as it recognizes the gesture and whenever the value of the gesture changes. For example, SwiftUI invokes the updating callback as soon as a magnification gesture begins and then again whenever the magnification value changes. SwiftUI doesn’t invoke the updating callback when the user ends or cancels a gesture. Instead, the gesture state property automatically resets its state back to its initial value.

For example, to make a view that changes color while the user performs a long press, add a gesture state property and update it in the updating callback.

struct CounterView: View {
    @GestureState 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)
    }
}

Update Permanent State During a Gesture

To track changes to a gesture that shouldn’t reset once the gesture ends, use the onChanged(_:) callback. For example, to count the number of times your app recognizes a long press, add an onChanged(_:) callback and increment a counter.

struct CounterView: View {
    @GestureState var isDetectingLongPress = false
    @State var totalNumberOfTaps = 0
    
    var body: some View {
        let press = LongPressGesture(minimumDuration: 1)
            .updating($isDetectingLongPress) { currentState, gestureState, transaction in
                gestureState = currentState
            }.onChanged { _ in
                self.totalNumberOfTaps += 1
            }
        
        return VStack {
            Text("\(totalNumberOfTaps)")
                .font(.largeTitle)
            
            Circle()
                .fill(isDetectingLongPress ? Color.yellow : Color.green)
                .frame(width: 100, height: 100, alignment: .center)
                .gesture(press)
        }
    }
}

Update Permanent State When a Gesture Ends

To recognize when a gesture successfully completes and to retrieve the gesture’s final value, use the onEnded(_:) function to update your app’s state in the callback. SwiftUI only invokes the onEnded(_:) callback when the gesture succeeds. For example, during a LongPressGesture if the user stops touching the view before minimumDuration seconds have elapsed or moves their finger more than maximumDistance points SwiftUI does not invoke the onEnded(_:) callback.

For example, to stop counting long press attempts after the user completes a long press, add an onEnded(_:) callback and conditionally apply the gesture modifier.

struct CounterView: View {
    @GestureState var isDetectingLongPress = false
    @State var totalNumberOfTaps = 0
    @State var doneCounting = false
    
    var body: some View {
        let press = LongPressGesture(minimumDuration: 1)
            .updating($isDetectingLongPress) { currentState, gestureState, transaction in
                gestureState = currentState
            }.onChanged { _ in
                self.totalNumberOfTaps += 1
            }
            .onEnded { _ in
                self.doneCounting = true
            }
        
        return VStack {
            Text("\(totalNumberOfTaps)")
                .font(.largeTitle)
            
            Circle()
                .fill(doneCounting ? Color.red : isDetectingLongPress ? Color.yellow : Color.green)
                .frame(width: 100, height: 100, alignment: .center)
                .gesture(doneCounting ? nil : press)
        }
    }
}

See Also

Basic Gestures

struct TapGesture

A gesture that recognizes one or more taps.

struct LongPressGesture

A gesture that succeeds when the user performs a long press.

struct DragGesture

A dragging motion that invokes an action as the drag-event sequence changes.

struct MagnificationGesture

A gesture that recognizes a magnification motion and tracks the amount of magnification.

struct RotationGesture

A gesture that recognizes a rotation motion and tracks the angle of the rotation.