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) } }
问题根源
- 第一响应者状态丢失:绘图操作后,
PKCanvasView可能失去第一响应者状态,此时调用picker.setVisible(_:forFirstResponder:)不会生效——PKToolPicker的可见性设置仅对当前第一响应者的视图生效。 - 绑定的
PKCanvasView实例不稳定:直接将PKCanvasView作为@Binding传递,可能导致视图实例在SwiftUI重绘时被替换,PKToolPicker与画布的关联关系失效。 - 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
相关产品推荐
相关产品推荐

