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

SwiftUI中PKToolPicker显示/隐藏异常问题排查

PencilKit PKToolPicker在SwiftUI中的问题及修复方案

问题现象

  • 在PKCanvasView上绘图后,切换showToolPicker的值无法隐藏PKToolPicker
  • 关闭视图时,PKToolPicker有时不会自动消失

原代码

struct CanvasView: UIViewRepresentable {
  final class Coordinator: NSObject {
    @Binding var canvasView: PKCanvasView

    let canvasDidChange: (PKCanvasView) -> Void
    let picker: PKToolPicker

    init(
      canvasView: Binding<PKCanvasView>,
      canvasDidChange: @escaping (PKCanvasView) -> Void,
      picker: PKToolPicker
    ) {
      _canvasView = canvasView
      self.canvasDidChange = canvasDidChange
      self.picker = picker
    }

    deinit {
      picker.setVisible(false, forFirstResponder: canvasView)
      picker.removeObserver(canvasView)
    }
  }

  @Binding var canvasView: PKCanvasView
  @Binding var showToolPicker: Bool

  let canvasDidChange: (PKCanvasView) -> Void

  let picker = PKToolPicker()

  func makeCoordinator() -> Coordinator {
    Coordinator(canvasView: $canvasView, canvasDidChange: canvasDidChange, picker: picker)
  }

  func makeUIView(context: Context) -> some UIView {
    canvasView.tool = PKInkingTool(.pen, color: .black, width: 10)
    #if targetEnvironment(simulator)
    canvasView.drawingPolicy = .anyInput
    #endif
    canvasView.delegate = context.coordinator
    picker.addObserver(canvasView)
    canvasView.becomeFirstResponder()
    return canvasView
  }

  func updateUIView(_ uiView: UIViewType, context: Context) {
    picker.setVisible(showToolPicker, forFirstResponder: canvasView)
  }
}

问题根源

  1. 第一响应者状态丢失:绘图操作后,PKCanvasView可能失去第一响应者状态,此时调用picker.setVisible(_:forFirstResponder:)不会生效——PKToolPicker的可见性设置仅对当前第一响应者的视图生效。
  2. 绑定的PKCanvasView实例不稳定:直接将PKCanvasView作为@Binding传递,可能导致视图实例在SwiftUI重绘时被替换,PKToolPicker与画布的关联关系失效。
  3. Deinit逻辑顺序错误:Coordinator的deinit中先设置picker不可见再移除观察者,可能因实例状态异常导致操作失效,正确顺序应为先移除观察者再处理可见性。

修复方案

1. 确保画布始终保持第一响应者状态

在updateUIView中先检查并恢复第一响应者状态,再设置PKToolPicker的可见性:

func updateUIView(_ uiView: UIViewType, context: Context) {
    // 确保画布是第一响应者,否则picker的可见性设置不生效
    if !canvasView.isFirstResponder {
        canvasView.becomeFirstResponder()
    }
    picker.setVisible(showToolPicker, forFirstResponder: canvasView)
}

2. 调整Coordinator的Deinit逻辑

先移除观察者,再隐藏picker,避免无效操作:

deinit {
    picker.removeObserver(canvasView)
    picker.setVisible(false, forFirstResponder: canvasView)
}

3. 避免直接绑定PKCanvasView(可选优化)

如果不需要外部管理PKCanvasView实例,建议在CanvasView内部创建并维护,避免实例替换导致的关联失效:

// 替换原有的@Binding var canvasView: PKCanvasView
private let canvasView = PKCanvasView()

完整修复后的代码

struct CanvasView: UIViewRepresentable {
    final class Coordinator: NSObject, PKCanvasViewDelegate {
        let canvasView: PKCanvasView
        let canvasDidChange: (PKCanvasView) -> Void
        let picker: PKToolPicker

        init(
            canvasView: PKCanvasView,
            canvasDidChange: @escaping (PKCanvasView) -> Void,
            picker: PKToolPicker
        ) {
            self.canvasView = canvasView
            self.canvasDidChange = canvasDidChange
            self.picker = picker
        }

        deinit {
            picker.removeObserver(canvasView)
            picker.setVisible(false, forFirstResponder: canvasView)
        }

        // 实现PKCanvasViewDelegate的必要方法(如果需要监听绘图变化)
        func canvasViewDrawingDidChange(_ canvasView: PKCanvasView) {
            canvasDidChange(canvasView)
        }
    }

    @Binding var showToolPicker: Bool
    let canvasDidChange: (PKCanvasView) -> Void

    private let canvasView = PKCanvasView()
    private let picker = PKToolPicker()

    func makeCoordinator() -> Coordinator {
        Coordinator(canvasView: canvasView, canvasDidChange: canvasDidChange, picker: picker)
    }

    func makeUIView(context: Context) -> some UIView {
        canvasView.tool = PKInkingTool(.pen, color: .black, width: 10)
        #if targetEnvironment(simulator)
        canvasView.drawingPolicy = .anyInput
        #endif
        canvasView.delegate = context.coordinator
        picker.addObserver(canvasView)
        canvasView.becomeFirstResponder()
        return canvasView
    }

    func updateUIView(_ uiView: UIViewType, context: Context) {
        if !canvasView.isFirstResponder {
            canvasView.becomeFirstResponder()
        }
        picker.setVisible(showToolPicker, forFirstResponder: canvasView)
    }
}

额外说明

如果问题仍然存在,可能是PencilKit在SwiftUI环境下的适配问题,可以尝试在隐藏picker时先移除观察者再重新添加(仅在需要时),但这种情况极少出现。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 06:35:34