How to use PKToolPicker with PKCanvasView in SwiftUI
Asked Answered
O

1

8

Currently, I am able to have a separate PKCanvasView and a PKToolPicker that is shown when a button is tapped on. However, the tool is not being transferred between the picker and the canvas view. Does anyone have any idea how to link the two such that when I change the tool in the picker, the tool is updated in the canvas view as well? I've attached my code below. Thank you!

import SwiftUI
import PencilKit

struct DrawingView: View {
    @State private var showPicker = false
    @State private var canvasView = PKCanvasView()
    var body: some View {
        VStack {
            PencilKitView(isActive: $showPicker, canvasView: $canvasView)
            Button("Picker") { self.showPicker.toggle() }
        }
    }
}

struct PencilKitView: UIViewRepresentable {
    typealias UIViewType = PKCanvasView
    @Binding var isActive: Bool
    @Binding var canvasView: PKCanvasView

    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) -> PKCanvasView {
        canvasView
    }
    
    func updateUIView(_ uiView: PKCanvasView, context: Context) {
        uiView.isOpaque = true
        uiView.becomeFirstResponder()

        let toolPicker = PKToolPicker.init()
        toolPicker.addObserver(uiView)
        toolPicker.addObserver(coordinator)
        toolPicker.setVisible(isActive, forFirstResponder: uiView)
        
        DispatchQueue.main.async {
            uiView.becomeFirstResponder()
        }
    }
}

Overmuch answered 23/9, 2020 at 0:33 Comment(0)
O
10

Solved it!

import SwiftUI
import PencilKit

struct DrawingView: View {
    private var canvasView = PKCanvasView()

    var body: some View {
        MyCanvas(canvasView: canvasView)
    }
}

struct MyCanvas: UIViewRepresentable {
    var canvasView: PKCanvasView
    let picker = PKToolPicker.init()
    
    func makeUIView(context: Context) -> PKCanvasView {
        self.canvasView.tool = PKInkingTool(.pen, color: .black, width: 15)
        self.canvasView.becomeFirstResponder()
        return canvasView
    }
    
    func updateUIView(_ uiView: PKCanvasView, context: Context) {
        picker.addObserver(canvasView)
        picker.setVisible(true, forFirstResponder: uiView)
        DispatchQueue.main.async {
            uiView.becomeFirstResponder()
        }
    }
}

Overmuch answered 23/9, 2020 at 2:18 Comment(5)
There is not sense to use State/Binding for PKCanvasView, because it is a pointer, you can just pass it directly as-is.Filagree
Can I have Objective C version of this as I am new to IOS development?Culbreth
Sorry @Culbreth but the above code relies on the SwiftUI framework which is Swift only.Overmuch
@Overmuch It's unfortunate. Thanks anyway.Culbreth
The PKToolPicker seems to be a bit buggy in about 30% of the cases. Sometimes a PKTool change in the picker doesn't reflect to the actual drawing / stroke. Might be a SwiftUI bug as this reddit post described.Stichous

© 2022 - 2025 — McMap. All rights reserved.