iosswiftswiftuiuiviewrepresentablepencilkit

Using PKToolPicker without PKCanvasView


I am trying to use PKToolPicker from PencilKit (iOS/Swift) from a custom view (which is NOT PKCanvasView). The custom view conforms to PKToolPickerObserver. Everything works fine during compile time but I never get to see the PKToolPicker! If I replace my custom view with PKCanvasView, everything works fine!

I am doing this in SwiftUI with UIViewRepresentable (thus First Responder seems a mystery!).

Here is the SwiftUI view in question:

struct PencilKitView: UIViewRepresentable {
    typealias UIViewType = myView
    
    let coordinator = Coordinator()
    
    class Coordinator: NSObject, PKToolPickerObserver {
        
        func toolPickerSelectedToolDidChange(_ toolPicker: PKToolPicker) {
            // some code
        }
        func toolPickerVisibilityDidChange(_ toolPicker: PKToolPicker) {
            // some code
        }
        
    }
    func makeCoordinator() -> PencilKitView.Coordinator {
        return Coordinator()
    }
    
    func makeUIView(context: Context) -> myView {
        let canvasView = myView()
        
        canvasView.isOpaque = false
        canvasView.backgroundColor = UIColor.clear
        canvasView.becomeFirstResponder()

        if let window = UIApplication.shared.windows.filter({$0.isKeyWindow}).first,
            let toolPicker = PKToolPicker.shared(for: window) {
            toolPicker.addObserver(canvasView)
            toolPicker.addObserver(coordinator)
            toolPicker.setVisible(true, forFirstResponder: canvasView)
        }
        return canvasView
    }
    
    func updateUIView(_ uiView: myView, context: Context) {
        
    }
}

If I replace myView with PKCanvasView above, the PKToolPicker will be seen.

For the sake of completeness, here is the MyView stub:

class myView: UIScrollView, PKToolPickerObserver {
    override init(frame: CGRect) {
        super.init(frame: frame)
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
    }
    
    public func toolPickerVisibilityDidChange(_ toolPicker: PKToolPicker) {
        /// some code
    }
    
    public func toolPickerSelectedToolDidChange(_ toolPicker: PKToolPicker) {
        /// some code
    }
    
    public func toolPickerIsRulerActiveDidChange(_ toolPicker: PKToolPicker) {
        /// some code
    }
    
    public func toolPickerFramesObscuredDidChange(_ toolPicker: PKToolPicker) {
        /// some code
    }
}

Anyone has succeeded in doing this? Is there some undocumented requirement for adopting PKToolPicker?


Solution

  • Here is simplest demo to show PKToolPicker for any custom UIView in SwiftUI.

    Tested with Xcode 11.4 / iOS 13.4

    demo

    struct ToolPickerDemo: View {
        @State private var showPicker = false
        var body: some View {
            Button("Picker") { self.showPicker.toggle() }
                .background(ToolPickerHelper(isActive: $showPicker))
        }
    }
    
    class PickerHelperView: UIView {
        override var canBecomeFirstResponder: Bool { true }
    }
    
    struct ToolPickerHelper: UIViewRepresentable {
        @Binding var isActive: Bool
    
        func makeUIView(context: Context) -> PickerHelperView {
            PickerHelperView()
        }
    
        func updateUIView(_ uiView: PickerHelperView, context: Context) {
            guard let window = uiView.window else { return }
    
            let picker = PKToolPicker.shared(for: window)
            picker?.setVisible(isActive, forFirstResponder: uiView)
    
            DispatchQueue.main.async {
                uiView.becomeFirstResponder()
            }
        }
    }