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

SwiftUI中ScrollView嵌套PKCanvasView时如何解决滚动手势冲突

解决ScrollView中PKCanvasView绘图手势被拦截的问题

核心思路

通过监听PKCanvasView的触摸事件,动态控制ScrollView的滚动状态:用户开始在Canvas上绘图时禁用ScrollView滚动,绘图结束后恢复滚动,完全匹配滚轮式DatePicker的交互逻辑,确保Canvas手势优先响应。

完整代码实现

1. 修改PKCanvasRepresentable

添加触摸事件监听与协调器,用于同步控制ScrollView的滚动状态:

struct PKCanvasRepresentable: UIViewRepresentable {
    @Binding var canvas: PKCanvasView
    @Binding var isScrollEnabled: Bool

    func makeUIView(context: Context) -> PKCanvasView {
        canvas.tool = PKInkingTool(.pen, color: .black, width: 2)
        canvas.drawingPolicy = .anyInput
        canvas.isOpaque = false
        canvas.backgroundColor = .clear
        
        // 监听触摸开始事件
        let touchDown = UITapGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.onTouchDown))
        touchDown.cancelsTouchesInView = false
        canvas.addGestureRecognizer(touchDown)
        
        // 监听触摸结束/取消事件
        let touchUp = UILongPressGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.onTouchUp))
        touchUp.minimumPressDuration = 0
        touchUp.cancelsTouchesInView = false
        canvas.addGestureRecognizer(touchUp)
        
        context.coordinator.scrollBinding = $isScrollEnabled
        return canvas
    }
    
    func updateUIView(_ uiView: PKCanvasView, context: Context) {}
    
    func makeCoordinator() -> Coordinator {
        Coordinator()
    }
    
    class Coordinator: NSObject {
        var scrollBinding: Binding<Bool>?
        
        @objc func onTouchDown(_ gesture: UITapGestureRecognizer) {
            guard gesture.state == .began else { return }
            scrollBinding?.wrappedValue = false
        }
        
        @objc func onTouchUp(_ gesture: UILongPressGestureRecognizer) {
            if gesture.state == .ended || gesture.state == .cancelled {
                scrollBinding?.wrappedValue = true
            }
        }
    }
}

2. 父视图中集成并控制滚动

在包含ScrollView的视图中,添加状态变量控制滚动,并绑定到PKCanvasRepresentable:

struct ContentView: View {
    @State var canvas = PKCanvasView()
    @State private var isScrollEnabled = true

    var body: some View {
        NavigationView {
            ScrollView {
                VStack(spacing: 16) {
                    // 其他输入组件示例
                    TextField("输入文本", text: .constant(""))
                        .textFieldStyle(.roundedBorder)
                        .padding(.horizontal)
                    
                    // 集成Canvas组件,必须设置固定高度确保触摸区域正常
                    PKCanvasRepresentable(canvas: $canvas, isScrollEnabled: $isScrollEnabled)
                        .frame(height: 300)
                        .border(Color.gray)
                    
                    TextField("另一输入框", text: .constant(""))
                        .textFieldStyle(.roundedBorder)
                        .padding(.horizontal)
                }
                .padding()
            }
            .scrollDisabled(!isScrollEnabled) // 根据状态控制滚动
            .navigationTitle("Canvas测试")
        }
    }
}

关键细节说明

  • 触摸事件监听:使用UITapGestureRecognizer和UILongPressGestureRecognizer覆盖Canvas的完整触摸周期,确保滚动状态切换及时且准确。
  • scrollDisabled修饰符:采用SwiftUI原生的scrollDisabled(_:)控制滚动,比直接修改UIKit的isScrollEnabled更贴合声明式编程风格。
  • 固定Canvas高度:必须给PKCanvasRepresentable设置明确高度,否则ScrollView无法正确计算布局,会导致Canvas触摸区域异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 18:45:19