<!--
{
  "documentType" : "article",
  "framework" : "SwiftUI",
  "identifier" : "/documentation/SwiftUI/Adding-Interactivity-with-Gestures",
  "metadataVersion" : "0.1.0",
  "role" : "article",
  "title" : "Adding interactivity with gestures"
}
-->

# 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:)`](/documentation/SwiftUI/View/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:)`](/documentation/SwiftUI/Gesture/updating(_:body:)), [`onChanged(_:)`](/documentation/SwiftUI/Gesture/onChanged(_:)),
and [`onEnded(_:)`](/documentation/SwiftUI/Gesture/onEnded(_:)).

#### Update transient UI state

To update a view as a gesture changes, add a [`GestureState`](/documentation/SwiftUI/GestureState) property to
your view and update it in the [`updating(_:body:)`](/documentation/SwiftUI/Gesture/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 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)
    }
}
```

#### Update permanent state during a gesture

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

```
struct CounterView: View {
    @GestureState private var isDetectingLongPress = false
    @State private 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(_:)`](/documentation/SwiftUI/Gesture/onEnded(_:)) function to update your app’s
state in the callback. SwiftUI only invokes the [`onEnded(_:)`](/documentation/SwiftUI/Gesture/onEnded(_:)) callback
when the gesture succeeds. For example, during a [`LongPressGesture`](/documentation/SwiftUI/LongPressGesture) if
the user stops touching the view before [`minimumDuration`](/documentation/SwiftUI/LongPressGesture/minimumDuration)
seconds have elapsed or moves their finger more than [`maximumDistance`](/documentation/SwiftUI/LongPressGesture/maximumDistance)
points SwiftUI does not invoke the [`onEnded(_:)`](/documentation/SwiftUI/Gesture/onEnded(_:)) callback.

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

```
struct CounterView: View {
    @GestureState private var isDetectingLongPress = false
    @State private var totalNumberOfTaps = 0
    @State private 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)
        }
    }
}
```

---

Copyright &copy; 2026 Apple Inc. All rights reserved. | [Terms of Use](https://www.apple.com/legal/internet-services/terms/site.html) | [Privacy Policy](https://www.apple.com/privacy/privacy-policy)