Skip to content
intermediate Phase 7 · Advanced UI

Gestures & Interactions

Handle taps, drags, long presses, magnification, and complex gesture combinations.

45m
2 problems
Topic Progress 0%

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?

Question 1 options

2. What does MagnificationGesture provide in its onChange handler?

Question 2 options

3. How do you combine two gestures to work simultaneously?

Question 3 options

4. What view modifier gives a gesture priority over child view gestures?

Question 4 options

Flashcards

Question

How do you handle multiple taps in SwiftUI?

Answer

Use .onTapGesture(count: N) where N is the number of taps required.

Question

What does DragGesture provide?

Answer

It tracks finger movement with .translation (current offset), .velocity (speed), and .location (position).

Question

How do you combine gestures for simultaneous recognition?

Answer

Use gesture1.simultaneously(with: gesture2) to allow both gestures to be active at once.

Question

What is @GestureState used for?

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