🔍 让 SwiftUI 视图支持多指点击

为您每周带来有关 Swift 和 SwiftUI 的精选资讯!

TL;DR: 在 SwiftUI 中实现多指点击手势。在 iOS 18 及以上版本使用 UIGestureRecognizerRepresentable 桥接 UIKit 手势,在低版本中通过 UIViewRepresentable 封装实现。

背景

SwiftUI 的 TapGesture 手势仅支持设置连击次数,不支持多指点击。本指南将介绍在不同 iOS 版本中实现多指点击的方法。

iOS 18+

从 iOS 18 开始,可以使用 UIGestureRecognizerRepresentable 将 UIKit 手势直接桥接到 SwiftUI,实现多指点击功能。

示例代码

Swift
struct TowFingerTapDemo: View {
  var body: some View {
    Rectangle()
      .foregroundStyle(.orange)
      .frame(width: 200, height: 200)
      .onTapGesture {
        print("One Touch")
      }
      // 应用 TwoFingerTapGesture
      .gesture(TwoFingerTapGesture {
        print("Two Touches")
      })
  }
}

struct TwoFingerTapGesture: UIGestureRecognizerRepresentable {
  let action: () -> Void

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

  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 // 支持多手势并行
    }
  }
}

特点

  • UIGestureRecognizerRepresentable 类似于 UIViewRepresentable,可桥接 UIKit 手势。
  • 可通过 Coordinator 自定义更详尽的手势处理逻辑。

在低版本中

在低于 iOS 18 的版本中,需通过 UIViewRepresentable 自定义视图,将 UIKit 手势封装为 SwiftUI 兼容组件。

实现步骤

  1. 创建支持双指点击的 UIView
  2. 通过 UIViewRepresentable 封装为 SwiftUI 视图
  3. 扩展 SwiftUI View,支持双指点击功能

示例代码

Swift
struct TowFingerTapDemo: View {
    var body: some View {
        Rectangle()
            .foregroundStyle(.orange)
            .frame(width: 200, height: 200)
            .onTwoFingerTap {
                print("Two Touches")
            }
            .onTapGesture {
                print("One Touch")
            }
    }
}

extension View {
    func onTwoFingerTap(perform action: @escaping () -> Void) -> some View {
        overlay(TwoFingerTapLayer(action: action))
    }
}

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

    func makeUIView(context: Context) -> some UIView {
        let view = TwoFingerTapUIView(action: action)
        view.backgroundColor = .clear
        return view
    }

    func updateUIView(_ uiView: Self.UIViewType, context: Self.Context) {}
}

class TwoFingerTapUIView: UIView {
    let action: () -> Void
    private var gesture: UITapGestureRecognizer!

    init(action: @escaping () -> Void) {
        self.action = action
        super.init(frame: .zero)
        setupGesture()
    }

    @available(*, unavailable)
    required init?(coder _: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    private func setupGesture() {
        gesture = UITapGestureRecognizer(target: self, action: #selector(handleGesture))
        gesture.numberOfTouchesRequired = 2 // 设置双指点击
        addGestureRecognizer(gesture)
    }

    @objc private func handleGesture() {
        action()
    }
}

注意事项

  • 手势顺序:确保 onTapGesture 等原生手势位于自定义 UIKit 手势之后,以避免冲突。
Swift
// 正确顺序
.onTwoFingerTap { print("Two Touches") }
.onTapGesture { print("One Touch") }

延伸阅读