SkillAgentSearch skills...

UIKitGesturesForSwiftUI

Advanced gesture recognizers for SwiftUI, leveraging UIGestureRecognizerRepresentable for full UIKit feature parity and complex interaction support.

Install / Use

/learn @jacobvanorder/UIKitGesturesForSwiftUI
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

UIKitGesturesForSwiftUI

Advanced multi-touch gesture recognizers for SwiftUI, bringing the full power of UIKit's UIGestureRecognizer to SwiftUI with complete feature parity and Swift 6 concurrency support.

Written by @jacobvo.

A.I. Disclaimer:

Initial concept of one gesture hand-coded but then Claude assisted with replication of additional gestures.

Why This Library?

SwiftUI's built-in gesture system is powerful but has limitations:

  • DragGesture only supports single-finger dragging
  • No native support for multi-finger gestures (2+ fingers)
  • Limited access to UIKit-specific features like velocity, number of touches, and precise gesture states
  • No control over gesture recognizer delegate methods for complex gesture interactions

UIKitGesturesForSwiftUI bridges this gap by exposing UIKit's full gesture recognizer capabilities directly in SwiftUI.

Features

Multi-finger gesture support - Pan, tap, swipe, pinch, rotate with 2+ fingers ✅ Full UIKit feature parity - Access velocity, translation, rotation, scale, and more ✅ Gesture delegate control - Customize simultaneous recognition, failure requirements, and touch handling ✅ Swift 6 concurrency safe - All closures are @MainActor isolated ✅ Declarative builder API - Chain .onBegan, .onChanged, .onEnded naturally ✅ Comprehensive documentation - Inline docs for every gesture and method ✅ Somewhat tested - 23 unit tests covering core functionality

Requirements

  • iOS 18.0+
  • Swift 6.0+
  • Xcode 16.0+

Installation

Swift Package Manager

Add this package to your Package.swift:

dependencies: [
    .package(url: "https://github.com/yourusername/UIKitGesturesForSwiftUI.git", from: "1.0.0")
]

Or in Xcode:

  1. File → Add Package Dependencies
  2. Enter the repository URL
  3. Select version and add to your target

Quick Start

import SwiftUI
import UIKitGesturesForSwiftUI

struct ContentView: View {
    @State private var offset = CGSize.zero

    var body: some View {
        Rectangle()
            .fill(.blue)
            .frame(width: 200, height: 200)
            .offset(offset)
            .gesture(
                MultiFingerPanGesture(
                    minimumNumberOfTouches: 2,
                    maximumNumberOfTouches: 2
                )
                .onChanged { recognizer in
                    let translation = recognizer.translation(in: recognizer.view)
                    offset = CGSize(width: translation.x, height: translation.y)
                }
            )
    }
}

Available Gestures

MultiFingerPanGesture

Continuous gesture for tracking multi-finger panning with velocity and translation.

MultiFingerPanGesture(
    minimumNumberOfTouches: 2,
    maximumNumberOfTouches: 2
)
.onBegan { recognizer in
    print("Pan started")
}
.onChanged { recognizer in
    let translation = recognizer.translation(in: recognizer.view)
    let velocity = recognizer.velocity(in: recognizer.view)
    print("Translation: \(translation), Velocity: \(velocity)")
}
.onEnded { recognizer in
    let finalVelocity = recognizer.velocity(in: recognizer.view)
    print("Pan ended with velocity: \(finalVelocity)")
}

Use Cases:

  • Two-finger scrolling
  • Multi-touch drag operations
  • Gesture-based navigation

MultiFingerTapGesture

Discrete gesture for detecting multi-finger taps with configurable tap count.

MultiFingerTapGesture(
    numberOfTouchesRequired: 3,
    numberOfTapsRequired: 2  // Double-tap with 3 fingers
)
.onEnded { recognizer in
    print("Triple-finger double-tap detected!")
}

Use Cases:

  • Accessibility shortcuts
  • Hidden debug menus
  • Advanced user interactions

MultiFingerPinchGesture

Continuous gesture for tracking pinch-to-zoom with scale and velocity.

@State private var scale: CGFloat = 1.0

MultiFingerPinchGesture()
    .onChanged { recognizer in
        scale *= recognizer.scale
        recognizer.scale = 1.0  // Reset for next update
    }
    .onEnded { recognizer in
        let finalVelocity = recognizer.velocity
        print("Pinch ended with velocity: \(finalVelocity)")
    }

Use Cases:

  • Zoom controls
  • Image scaling
  • Map interactions

MultiFingerRotationGesture

Continuous gesture for tracking rotation with angle and velocity.

@State private var rotation: Angle = .zero

MultiFingerRotationGesture()
    .onChanged { recognizer in
        rotation += Angle(radians: recognizer.rotation)
        recognizer.rotation = 0  // Reset for next update
    }
    .onEnded { recognizer in
        let finalVelocity = recognizer.velocity
        print("Rotation velocity: \(finalVelocity) radians/sec")
    }

Use Cases:

  • Image rotation
  • 3D object manipulation
  • Creative tools

MultiFingerSwipeGesture

Discrete gesture for detecting directional swipes with multiple fingers.

MultiFingerSwipeGesture(
    numberOfTouchesRequired: 3,
    direction: .down
)
.onEnded { recognizer in
    print("Three-finger swipe down detected!")
}

Directions: .up, .down, .left, .right

Use Cases:

  • Navigation gestures
  • App switching
  • Custom gesture controls

MultiFingerLongPressGesture

Continuous gesture for long-press detection with configurable duration and movement tolerance.

MultiFingerLongPressGesture(
    numberOfTouchesRequired: 2,
    minimumPressDuration: 1.0,
    allowableMovement: 10
)
.onBegan { recognizer in
    print("Long press began")
}
.onEnded { recognizer in
    print("Long press ended")
}

Use Cases:

  • Context menus
  • Selection mode
  • Secondary actions

MultiFingerTransformGesture

This is just an example of a custom UIGestureRecognizer subclass that is then extended into SwiftUI. Continuous gesture combining pan, pinch, and rotation into a single transform.

@State private var transform = CGAffineTransform.identity

MultiFingerTransformGesture(
    minimumNumberOfTouches: 2,
    maximumNumberOfTouches: 2
)
.onChanged { recognizer in
    transform = recognizer.transform
    print("Translation: \(recognizer.translation)")
    print("Rotation: \(recognizer.rotation)")
    print("Scale: \(recognizer.scale)")
}

Use Cases:

  • Photo editing
  • Object manipulation
  • Canvas interactions

Advanced Usage

Customizing Gesture Delegate Behavior

All gestures support full UIGestureRecognizerDelegate customization via optional closures.

Simultaneous Gesture Recognition

Allow multiple gestures to recognize at the same time:

MultiFingerPanGesture(minimumNumberOfTouches: 2, maximumNumberOfTouches: 2)
    .shouldRecognizeSimultaneouslyWith { otherGesture in
        // Allow simultaneous recognition with pinch gestures
        return otherGesture is UIPinchGestureRecognizer
    }

Default: true (allows simultaneous recognition by default)

Controlling Gesture Begin

Conditionally allow or prevent gestures from starting:

@State private var isGestureEnabled = true

MultiFingerPanGesture(minimumNumberOfTouches: 2, maximumNumberOfTouches: 2)
    .shouldBegin { recognizer in
        return isGestureEnabled
    }

Touch and Event Filtering

Control which touches or events the gesture responds to:

MultiFingerPanGesture(minimumNumberOfTouches: 2, maximumNumberOfTouches: 2)
    .shouldReceiveTouch { recognizer, touch in
        // Only respond to touches inside specific views
        guard let view = touch.view else { return false }
        return view.tag == 100
    }

Gesture Failure Dependencies

Require one gesture to fail before another begins:

let tapGesture = MultiFingerTapGesture(numberOfTouchesRequired: 1, numberOfTapsRequired: 2)
let panGesture = MultiFingerPanGesture(minimumNumberOfTouches: 1, maximumNumberOfTouches: 1)
    .shouldRequireFailureOf { otherGesture in
        // Pan only starts if double-tap fails
        return otherGesture is UITapGestureRecognizer
    }

Complete Delegate Customization Example

MultiFingerPanGesture(
    minimumNumberOfTouches: 2,
    maximumNumberOfTouches: 2,
    shouldBegin: { recognizer in
        // Only allow if conditions are met
        return someCondition
    },
    shouldRecognizeSimultaneouslyWith: { otherGesture in
        // Allow with pinch, block others
        return otherGesture is UIPinchGestureRecognizer
    },
    shouldReceiveTouch: { recognizer, touch in
        // Filter touches by location
        let location = touch.location(in: touch.view)
        return location.x > 100
    }
)
.onChanged { recognizer in
    // Handle gesture
}

Gesture State Lifecycle

All continuous gestures follow this state machine:

.possible → .began → .changed (repeated) → .ended
               ↘ .cancelled
               ↘ .failed

Discrete gestures (tap, swipe) transition directly to .onEnded

Callbacks provided:

  • .onBegan - Gesture started (continuous only)
  • .onChanged - Gesture updated (continuous only)
  • .onEnded - Gesture completed (continuous and discrete)

Note: .cancelled and .failed states are not exposed as they indicate the gesture did not complete successfully.

Concurrency and Thread Safety

All gesture closures are marked @MainActor because:

✅ UIKit gesture recognizers always call delegates on the main thread ✅ SwiftUI view updates must happen on the main thread ✅ You can safely capture @MainActor isolated state (view models, etc.)

@MainActor
@Observable
class ViewModel {
    var count = 0
}

let viewModel = ViewModel()

MultiFingerPanGesture(minimumNumberOfTouches: 2, maximumNumberOfTouches: 2)
    .onChanged { recognizer in
        viewModel.count += 1  // ✅ Safe - both are @MainActor
    }

Examples

Two-Finger Scrolling Canvas

struct ScrollableCanvas: View {
    @State private var offset = CGSize.zero
    @Stat
View on GitHub
GitHub Stars12
CategoryCustomer
Updated1mo ago
Forks0

Languages

Swift

Security Score

90/100

Audited on Feb 28, 2026

No findings