核心摘要:原生的 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 的
@State或Transaction结合。
方案二:兼容低版本 (iOS 17 及以下)
在不支持新协议的版本中,我们需要通过 UIViewRepresentable 创建一个透明的 UIView,并将手势识别器附加其上。
实现步骤
- 创建一个透明的
UIView子类,配置UITapGestureRecognizer。 - 使用
UIViewRepresentable包装该视图。 - 通过
.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中实现代理方法处理冲突。