🔍

SwiftUI 多指点击:实现双指与三指手势

(更新于 )

核心摘要:原生的 SwiftUI TapGesture 仅支持设置点击次数(Taps),无法设置触控手指数量(Touches)。要实现双指或三指点击,我们需要借助 UIKit 的力量。

在 iOS 18 之前,通过 UIViewRepresentable 包装 UIView 是唯一解;而在 iOS 18+ (包括最新的 iOS 26+) 中,苹果引入了更高效的 UIGestureRecognizerRepresentable 协议,直接桥接手势识别器。

方案一:iOS 18+ (推荐)

从 iOS 18 开始,可以使用 UIGestureRecognizerRepresentable 将 UIKit 的 UITapGestureRecognizer 直接暴露给 SwiftUI。这种方式比创建完整的 UIView 性能更好,且代码更 Swift 风格化。

示例代码

Swift
import SwiftUI

struct TwoFingerTapDemo: View {
    var body: some View {
        Rectangle()
            .foregroundStyle(.orange)
            .frame(width: 200, height: 200)
            .overlay(Text("Tap with 2 Fingers"))
            .onTapGesture {
                print("单指点击 (原生)")
            }
            // 应用自定义双指手势
            .gesture(TwoFingerTapGesture {
                print("双指点击触发!")
            })
    }
}

// 定义可桥接的手势
struct TwoFingerTapGesture: UIGestureRecognizerRepresentable {
    let action: () -> Void

    func makeUIGestureRecognizer(context: Context) -> UITapGestureRecognizer {
        let gesture = UITapGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.handleGesture))
        gesture.numberOfTouchesRequired = 2 // 关键:设置双指
        gesture.delegate = context.coordinator
        return gesture
    }

    func updateUIGestureRecognizer(_ recognizer: UITapGestureRecognizer, context: Context) {}

    func makeCoordinator(converter: CoordinateSpaceConverter) -> Coordinator {
        Coordinator(action: action)
    }

    class Coordinator: NSObject, UIGestureRecognizerDelegate {
        let action: () -> Void

        init(action: @escaping () -> Void) {
            self.action = action
        }

        @objc func handleGesture() {
            action()
        }

        // 允许与其他手势共存(例如原生的单指点击)
        func gestureRecognizer(_: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith _: UIGestureRecognizer) -> Bool {
            true 
        }
    }
}

优势

  • 轻量级:无需创建中间层 UIView
  • 状态同步:更容易与 SwiftUI 的 @StateTransaction 结合。

方案二:兼容低版本 (iOS 17 及以下)

在不支持新协议的版本中,我们需要通过 UIViewRepresentable 创建一个透明的 UIView,并将手势识别器附加其上。

实现步骤

  1. 创建一个透明的 UIView 子类,配置 UITapGestureRecognizer
  2. 使用 UIViewRepresentable 包装该视图。
  3. 通过 .overlay() 将其覆盖在目标 SwiftUI 视图之上。

示例代码

Swift
struct LegacyTwoFingerTapDemo: View {
    var body: some View {
        Rectangle()
            .foregroundStyle(.blue)
            .frame(width: 200, height: 200)
            .onTwoFingerTap {
                print("双指点击 (兼容模式)")
            }
    }
}

// 便捷修饰符
extension View {
    func onTwoFingerTap(perform action: @escaping () -> Void) -> some View {
        overlay(TwoFingerTapLayer(action: action))
    }
}

struct TwoFingerTapLayer: UIViewRepresentable {
    let action: () -> Void

    func makeUIView(context: Context) -> UIView {
        let view = UIView()
        view.backgroundColor = .clear // 确保透明
        
        let gesture = UITapGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.onTap))
        gesture.numberOfTouchesRequired = 2
        view.addGestureRecognizer(gesture)
        
        return view
    }

    func updateUIView(_ uiView: UIView, context: Context) {}
    
    func makeCoordinator() -> Coordinator {
        Coordinator(action: action)
    }
    
    class Coordinator: NSObject {
        let action: () -> Void
        init(action: @escaping () -> Void) { self.action = action }
        
        @objc func onTap() { action() }
    }
}

注意事项

  • 手势冲突:如果同时使用原生 .onTapGesture 和自定义多指手势,请注意 SwiftUI 的层级顺序。通常建议将 .onTapGesture(单指)放在 .onTwoFingerTap 之后,或者在 UIViewRepresentable 中实现代理方法处理冲突。

延伸阅读

相关提示

订阅 Fatbobman 周报

每周精选 Swift 与 SwiftUI 开发技巧,加入众多开发者的行列。

立即订阅