Basic Gestures
Tap Gestures
The simplest gesture is the tap gesture. SwiftUI provides .onTapGesture as a convenient modifier for handling taps.
struct TapGestureDemo: View {
@State private var tapCount = 0
var body: some View {
VStack(spacing: 20) {
Text("Taps: \(tapCount)")
.font(.largeTitle)
// Single tap
Rectangle()
.fill(.blue)
.frame(width: 200, height: 100)
.onTapGesture {
tapCount += 1
}
// Double tap
Rectangle()
.fill(.green)
.frame(width: 200, height: 100)
.onTapGesture(count: 2) {
tapCount += 2
}
// Triple tap
Rectangle()
.fill(.red)
.frame(width: 200, height: 100)
.onTapGesture(count: 3) {
tapCount += 3
}
}
}
}
Long Press Gestures
Long press gestures detect when the user presses and holds. You can customize the minimum duration required.
struct LongPressDemo: View {
@State private var isPressed = false
@State private var longPressProgress: CGFloat = 0
var body: some View {
VStack(spacing: 20) {
Circle()
.fill(isPressed ? .red : .blue)
.frame(width: 100, height: 100)
.scaleEffect(isPressed ? 1.2 : 1.0)
.animation(.spring(), value: isPressed)
.onLongPressGesture(
minimumDuration: 0.5,
pressing: { pressing in
isPressed = pressing
},
perform: {
print("Long press completed!")
}
)
Text(isPressed ? "Pressing..." : "Press and hold")
.font(.headline)
}
}
}
Long Press with Progress
For more sophisticated long press interactions, you can track the progress of the press using LongPressGesture with state.
struct LongPressProgressView: View {
@GestureState private var isPressed = false
@State private var completedAction = false
var body: some View {
VStack {
Circle()
.fill(completedAction ? .green : .blue)
.frame(width: 100, height: 100)
.overlay(
Circle()
.stroke(.white, lineWidth: 4)
)
.gesture(
LongPressGesture(minimumDuration: 1.0)
.onEnded { _ in
completedAction = true
}
)
.onAppear {
completedAction = false
}
if completedAction {
Text("Action completed!")
.foregroundColor(.green)
}
}
}
}
Gesture Modifiers
Drag Gestures
Drag gestures track the movement of a finger across the screen. They are essential for building interactive UIs like swipeable cards, draggable elements, and custom scroll behaviors.
struct DraggableView: View {
@State private var offset = CGSize.zero
@State private var color: Color = .blue
var body: some View {
RoundedRectangle(cornerRadius: 20)
.fill(color)
.frame(width: 300, height: 200)
.offset(offset)
.gesture(
DragGesture()
.onChanged { value in
offset = value.translation
}
.onEnded { value in
withAnimation(.spring()) {
if offset.width > 100 {
color = .green
} else if offset.width < -100 {
color = .red
} else {
offset = .zero
}
}
}
)
}
}
Magnification Gestures
Magnification gestures detect pinch-to-zoom interactions. They provide a scale factor that you can apply to views.
struct ZoomableView: View {
@State private var currentScale: CGFloat = 1.0
@GestureState private var gestureScale: CGFloat = 1.0
var body: some View {
Image(systemName: "photo")
.resizable()
.scaledToFit()
.frame(width: 200, height: 200)
.scaleEffect(currentScale * gestureScale)
.gesture(
MagnificationGesture()
.updating($gestureScale) { value, state, _ in
state = value
}
.onEnded { value in
currentScale *= value
}
)
}
}
Rotation Gestures
Rotation gestures detect the twisting motion of two fingers. They provide an angle in radians that you can use to rotate views.
struct RotatableView: View {
@State private var angle: Angle = .zero
@GestureState private var gestureAngle: Angle = .zero
var body: some View {
RoundedRectangle(cornerRadius: 10)
.fill(.purple)
.frame(width: 150, height: 150)
.rotationEffect(angle + gestureAngle)
.gesture(
RotationGesture()
.updating($gestureAngle) { value, state, _ in
state = value
}
.onEnded { value in
angle += value
}
)
}
}
Complex Gesture Combinations
Combining Gestures
SwiftUI allows you to combine multiple gestures using the .simultaneously, .sequenced, and .exclusively modifiers. This enables complex interaction patterns.
struct CombinedGestureView: View {
@State private var offset = CGSize.zero
@State private var scale: CGFloat = 1.0
var body: some View {
let dragGesture = DragGesture()
.onChanged { value in
offset = value.translation
}
.onEnded { _ in
withAnimation {
offset = .zero
}
}
let magnifyGesture = MagnificationGesture()
.onChanged { value in
scale = value
}
.onEnded { _ in
withAnimation {
scale = 1.0
}
}
// Combine both gestures to work simultaneously
let combined = dragGesture.simultaneously(with: magnifyGesture)
RoundedRectangle(cornerRadius: 20)
.fill(.blue)
.frame(width: 200, height: 200)
.offset(offset)
.scaleEffect(scale)
.gesture(combined)
}
}
Sequential Gestures
Sequential gestures require one gesture to complete before the next one begins. This is useful for multi-step interactions.
struct SequentialGestureView: View {
@State private var tapCount = 0
@State private var offset = CGSize.zero
var body: some View {
let tap = TapGesture()
.onEnded {
tapCount += 1
}
let longPress = LongPressGesture(minimumDuration: 0.5)
.onEnded { _ in
// Long press action
}
VStack(spacing: 20) {
Text("Taps: \(tapCount)")
// Long press first, then tap
Rectangle()
.fill(.blue)
.frame(width: 200, height: 100)
.gesture(
longPress.sequenced(before: tap)
)
}
}
}
Exclusive Gestures
Exclusive gestures give priority to one gesture over another. The first gesture that succeeds wins.
struct ExclusiveGestureView: View {
@State private var action = "None"
var body: some View {
let longPress = LongPressGesture(minimumDuration: 1.0)
.onEnded { _ in
action = "Long Press"
}
let tap = TapGesture()
.onEnded {
action = "Tap"
}
VStack {
Text(action)
.font(.title)
// Tap takes priority; long press only triggers if tap doesn't
Circle()
.fill(.blue)
.frame(width: 100, height: 100)
.gesture(
tap.exclusively(before: longPress)
)
}
}
}
Using highPriorityGesture and simultaneousGesture
View-level gesture modifiers allow you to override or add gestures at the view hierarchy level:
.highPriorityGesture()- Overrides child gestures.simultaneousGesture()- Adds gesture alongside child gestures
struct ParentChildGestureView: View {
var body: some View {
ChildView()
.highPriorityGesture(
TapGesture().onEnded {
print("Parent captured the tap")
}
)
}
}
Haptic Feedback
Enhance gesture interactions with haptic feedback to provide tactile responses.
import UIKit
func provideHaptic() {
let generator = UIImpactFeedbackGenerator(style: .medium)
generator.impactOccurred()
}
// Usage in gesture
.onTapGesture {
provideHaptic()
// handle tap
}
Quiz
1. How do you handle a double tap gesture in SwiftUI?
2. What does MagnificationGesture provide in its onChange handler?
3. How do you combine two gestures to work simultaneously?
4. What view modifier gives a gesture priority over child view gestures?
Flashcards
Question
How do you handle multiple taps in SwiftUI?
Click to reveal answer
Answer
Use .onTapGesture(count: N) where N is the number of taps required.
Question
What does DragGesture provide?
Click to reveal answer
Answer
It tracks finger movement with .translation (current offset), .velocity (speed), and .location (position).
Question
How do you combine gestures for simultaneous recognition?
Click to reveal answer
Answer
Use gesture1.simultaneously(with: gesture2) to allow both gestures to be active at once.
Question
What is @GestureState used for?
Click to reveal answer
Answer
It tracks gesture state that automatically resets when the gesture ends, preventing stale values.
Revision Notes
Key Takeaways
- 1. Tap gestures support count parameter for double/triple taps
- 2. Drag gestures provide translation, velocity, and location data
- 3. Magnification gestures provide scale factor for pinch-to-zoom
- 4. Combine gestures with simultaneously, sequenced, and exclusively
- 5. highPriorityGesture overrides child view gestures
Interview Tips
- • Explain the difference between @State and @GestureState for gesture values
- • Describe how to build a swipeable card with drag gestures
- • Discuss when to use simultaneous vs sequential gesture combinations
Cheat Sheet
Gestures Quick Reference
- Tap: .onTapGesture(count: N) { }
- Long Press: .onLongPressGesture(minimumDuration:) { }
- Drag: DragGesture().onChanged{}.onEnded{}
- Magnification: MagnificationGesture()
- Rotation: RotationGesture()
- Combine: .simultaneously(with:), .sequenced(before:), .exclusively(before:)
- Priority: .highPriorityGesture(), .simultaneousGesture()
- State: @GestureState for auto-resetting values