MRE
I've spent so many hours trying to figure out whats happening but I cant figure it out
struct ContentView: View {
@State private var canvasView = PKCanvasView()
@State private var rendition = PKDrawing()
func save() {
rendition = canvasView.drawing
}
func load() {
canvasView.drawing = rendition
}
func delete() {
canvasView.drawing = PKDrawing()
}
var body: some View {
VStack {
Button {
save()
} label: {
Text("Save")
}
Button {
load()
} label: {
Text("load")
}
Button {
delete()
} label: {
Text("delete")
}
CanvasView(canvasView: $canvasView)
}
}
}
What's going on?
struct CanvasView {
@Binding var canvasView: PKCanvasView
@State var toolPicker = PKToolPicker()
}
// MARK: - UIViewRepresentable
extension CanvasView: UIViewRepresentable {
func makeUIView(context: Context) -> PKCanvasView {
canvasView.tool = PKInkingTool(.pen, color: .gray, width: 10)
#if targetEnvironment(simulator)
canvasView.drawingPolicy = .anyInput
#endif
canvasView.delegate = context.coordinator
showToolPicker()
return canvasView
}
func updateUIView(_ uiView: PKCanvasView, context: Context) {}
func makeCoordinator() -> Coordinator {
Coordinator(canvasView: $canvasView)
}
}
// MARK: - Private Methods
private extension CanvasView {
func showToolPicker() {
toolPicker.setVisible(true, forFirstResponder: canvasView)
toolPicker.addObserver(canvasView)
canvasView.becomeFirstResponder()
}
}
// MARK: - Coordinator
class Coordinator: NSObject {
var canvasView: Binding<PKCanvasView>
// MARK: - Initializers
init(canvasView: Binding<PKCanvasView>) {
self.canvasView = canvasView
}
}
// MARK: - PKCanvasViewDelegate
extension Coordinator: PKCanvasViewDelegate {
func canvasViewDrawingDidChange(_ canvasView: PKCanvasView) {
if !canvasView.drawing.bounds.isEmpty {
}
}
}
For others that encounter this. This solution is given to me from another forum.
I would try giving the CanvasView a binding or regular property for the PKDrawing rather than the PKCanvasView, and initialize a PKCanvasView and set it’s drawing in makeUIView. No idea if that’s your root cause but it is definitely wonky to own a UIKit view as a @State var in your content view. You can also save and load by adding a second @State var of type PKDrawing, and assigning the working drawing to or from the backup / “saved” drawing
Edit: it’d need to be a binding PKDrawing because the Controller delegate would need to update the ContentView’s @State drawing via the binding whenever the delegate method flags that the drawing changed.
In addition to the changes above, I found that PKCanvasView was somehow preserving the previous drawing even though we're explicitly setting the drawing. I came up with a wonky fix:
Add a @State var id = 0
Add a .id(id) to the CanvasView
After restoring the drawing in load(), increment id with id += 1 This will cause a new PKCanvasView to be created every time you tap Load
import SwiftUI
import PencilKit
struct ContentView: View {
@State private var rendition = PKDrawing()
@State private var backup = PKDrawing()
@State private var strokes = 0
func save() {
backup = rendition
}
func load() {
rendition = backup
}
func delete() {
rendition = PKDrawing()
}
var body: some View {
VStack {
Button {
save()
} label: {
Text("Save")
}
Button {
load()
strokes += 1
} label: {
Text("load")
}
Button {
delete()
} label: {
Text("delete")
}
CanvasView(rendition: $rendition)
.id(strokes)
}
}
}
struct CanvasView {
@Binding var rendition: PKDrawing
var toolPicker = PKToolPicker()
}
// MARK: - UIViewRepresentable
extension CanvasView: UIViewRepresentable {
func makeUIView(context: Context) -> PKCanvasView {
print("made view!")
let canvasView = PKCanvasView()
canvasView.drawing = rendition
canvasView.tool = PKInkingTool(.pen, color: .gray, width: 10)
#if targetEnvironment(simulator)
canvasView.drawingPolicy = .anyInput
#endif
canvasView.delegate = context.coordinator
showToolPicker(canvasView: canvasView)
return canvasView
}
func updateUIView(_ uiView: PKCanvasView, context: Context) {
uiView.delegate = nil
uiView.drawing = rendition
uiView.delegate = context.coordinator
}
func makeCoordinator() -> Coordinator {
Coordinator(parent: self)
}
}
// MARK: - Private Methods
private extension CanvasView {
func showToolPicker(canvasView: PKCanvasView) {
toolPicker.setVisible(true, forFirstResponder: canvasView)
toolPicker.addObserver(canvasView)
canvasView.becomeFirstResponder()
}
}
// MARK: - Coordinator
class Coordinator: NSObject {
let parent: CanvasView
// MARK: - Initializers
init(parent: CanvasView) {
self.parent = parent
}
}
// MARK: - PKCanvasViewDelegate
extension Coordinator: PKCanvasViewDelegate {
func canvasViewDrawingDidChange(_ canvasView: PKCanvasView) {
DispatchQueue.main.async {
self.parent.rendition = canvasView.drawing
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}