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
相关产品推荐
相关产品推荐

