You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI simultaneousGesture与UIViewRepresentable共存问题咨询

问题

给定以下代码:

struct ContentView: View {
    var body: some View {
      MyButton()
        .simultaneousGesture(
          TapGesture(count: 1).onEnded { print("\(#function)") },
          including: .all
        )
    }
}

struct MyButton: UIViewRepresentable {
  func makeUIView(context: Context) -> UIButton {
    var config = UIButton.Configuration.plain()
    config.image = UIImage(systemName: "hand.tap.fill")

   let button = UIButtonSubclass()
    button.configuration = config
    return button
  }

  func updateUIView(_ uiView: UIButton, context: Context) {
  }
}


class UIButtonSubclass: UIButton {
  override init(frame: CGRect) {
    super.init(frame: frame)

    self.addTarget(self, action: #selector(didTap), for: .touchUpInside)
  }

  required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }

  @objc private func didTap(_ sender: UIButton) {
    print("\(#function)")
  }
}

现状与需求

  • MyButton封装的外部UIButtonSubclass绑定了.touchUpInside事件
  • ContentView中给MyButton添加了SwiftUI的simultaneousGesture单指点击手势

当前问题:点击按钮仅触发TapGesture回调,完全不会执行.touchUpInside绑定的didTap方法;只有当TapGesture无法识别时(比如将点击次数改为2),didTap才会执行。

需要实现:让TapGesture与.touchUpInside的处理逻辑同时生效,且无法修改外部的UIButtonSubclass代码。


解决方案

方法1:用UIKit手势替代SwiftUI手势(简洁可靠)

直接在UIViewRepresentable中给按钮添加UIKit原生的点击手势,通过设置cancelsTouchesInView = false避免阻断按钮的原生事件:

struct MyButton: UIViewRepresentable {
  // 暴露外部回调,对应原SwiftUI手势的逻辑
  var onTap: (() -> Void)?

  func makeUIView(context: Context) -> UIButton {
    var config = UIButton.Configuration.plain()
    config.image = UIImage(systemName: "hand.tap.fill")

    let button = UIButtonSubclass()
    button.configuration = config

    // 添加UIKit手势,设置不阻断按钮原生触摸事件
    let tapGesture = UITapGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.handleTap))
    tapGesture.cancelsTouchesInView = false
    button.addGestureRecognizer(tapGesture)

    return button
  }

  func updateUIView(_ uiView: UIButton, context: Context) {
    context.coordinator.onTap = onTap
  }

  // 协调器处理手势回调
  class Coordinator: NSObject {
    var onTap: (() -> Void)?

    @objc func handleTap() {
      onTap?()
    }
  }

  func makeCoordinator() -> Coordinator {
    Coordinator()
  }
}

// ContentView中使用方式
struct ContentView: View {
    var body: some View {
      MyButton {
        print("\(#function)") // 原SwiftUI手势的逻辑
      }
    }
}

方法2:保留SwiftUI手势,通过代理修正事件传递

如果必须保留原SwiftUI的simultaneousGesture,可以通过协调器实现UIGestureRecognizerDelegate,阻止手势阻断按钮的原生事件:

struct MyButton: UIViewRepresentable {
  func makeUIView(context: Context) -> UIButton {
    var config = UIButton.Configuration.plain()
    config.image = UIImage(systemName: "hand.tap.fill")

    let button = UIButtonSubclass()
    button.configuration = config

    // 延迟获取SwiftUI添加的手势,设置代理
    DispatchQueue.main.async {
      button.gestureRecognizers?.forEach { $0.delegate = context.coordinator }
    }

    return button
  }

  func updateUIView(_ uiView: UIButton, context: Context) {
  }

  class Coordinator: NSObject, UIGestureRecognizerDelegate {
    // 允许触摸事件传递给按钮
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
      return true
    }

    // 不取消视图的触摸事件,保证按钮touchUpInside触发
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldCancelTouchesInView view: UIView) -> Bool {
      return false
    }
  }

  func makeCoordinator() -> Coordinator {
    Coordinator()
  }
}

// ContentView保持原代码不变
struct ContentView: View {
    var body: some View {
      MyButton()
        .simultaneousGesture(
          TapGesture(count: 1).onEnded { print("\(#function)") },
          including: .all
        )
    }
}

原理说明

问题核心是:SwiftUI的simultaneousGesture添加的手势默认会设置cancelsTouchesInView = true,导致UIButton的.touchUpInside事件被阻断。上述两种方法都是通过修改手势的事件传递规则,让按钮原生事件和SwiftUI手势可以同时响应。

内容的提问来源于stack exchange,提问作者Daniel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 17:13:09