I was confronted with the same problem and found a working solution for the PKCanvasView so that it can be properly used in the simulator.
In essence: In debug mode I replace the PKCanvasView by a subclass that adds an UIView with an image as a subview. The image is made from an UIBezierPath that is generated from the PKCanvasView' drawing. When drawing, the UIView updates its image from the PKCanvasView' drawing and draws itself.
Code:
In the CanvasUIViewControllerRepresentable:
func makeUIViewController(context: Context) -> CanvasUIViewController {
let theViewController = CanvasUIViewController()
var view: PKCanvasView
#if DEBUG
view = CanvasView(frame: CGRect(origin: .zero, size: CGSize(width:820, height: 1180)))
debugPrint("Using CanvasView")
#else
view = PKCanvasView(frame: CGRect(origin: .zero, size: CGSize(width:820, height: 1180)))
#endif
view.isOpaque = true
view.backgroundColor = UIColor.systemGray6
view.isUserInteractionEnabled = true
view.isMultipleTouchEnabled = true
view.autoresizesSubviews = true
view.autoresizingMask = [.flexibleHeight, .flexibleWidth]
view.contentSize = CGSize(width: 1000, height: 1000)
theViewController.view = view
view.maximumZoomScale = 10
view.minimumZoomScale = 0.1
theViewController.view = view
theViewController.canvasView = view
theViewController.representedObject = self.canvasObject
theViewController.delegate = context.coordinator
return theViewController
}
The class CanvasView:
class CanvasView: PKCanvasView {
var imageView: CanvasImageView? = nil
override func willMove(toWindow newWindow: UIWindow?) {
let imageView = CanvasImageView(frame: CGRect(origin: .zero, size: self.contentSize))
imageView.canvasView = self
self.addSubview(imageView)
imageView.backgroundColor = UIColor.clear
imageView.isOpaque = false
imageView.isUserInteractionEnabled = false
self.imageView = imageView
}
override public func draw(_ rect: CGRect) {
self.imageView?.setNeedsDisplay()
super.draw(rect)
}
}
The CanvasImageView class:
class CanvasImageView: UIView {
weak var canvasView: CanvasView? = nil
var image:UIImage? = nil
override var canBecomeFirstResponder: Bool {
return false
}
override public func draw(_ rect: CGRect) {
if let canvasView = self.canvasView {
debugPrint("Drawing ", canvasView.drawing.strokes.count, " Strokes", "zoomScale:", canvasView.zoomScale)
let bezierPath = canvasView.drawing.bezierPath()
let renderer = UIGraphicsImageRenderer(size: canvasView.contentSize)
let image = renderer.image { (context) in
UIColor.blue.setStroke()
bezierPath.stroke()
}
let scaledImage = image.resizeImage(toWidth: (image.size.width * canvasView.zoomScale))
self.image = scaledImage
}
self.image?.draw(at: .zero)
}
}
Extensions:
PKDrawing extension to generate a UIBezierPath:
import PencilKit
extension PKDrawing {
func bezierPath() -> UIBezierPath {
let theStrokes = self.strokes
let bezierPath = UIBezierPath()
theStrokes.forEach({ aStroke in
let strokePath = aStroke.path
let numberOfPoints = CGFloat(strokePath.count)
let newBezierPath = UIBezierPath()
var i: CGFloat = 0
if numberOfPoints > 0 {
let location = strokePath.interpolatedLocation(at: 0)
newBezierPath.move(to: location)
}
if numberOfPoints > 1 {
i = 1
repeat {
let location = strokePath.interpolatedLocation(at: i)
newBezierPath.addLine(to: location)
i += 1.0
} while i < numberOfPoints
}
newBezierPath.lineWidth = 1.0
bezierPath.append(newBezierPath)
})
return bezierPath
}
}
The UIImage extension to scale an image:
func resizeImage(toWidth newWidth: CGFloat) -> UIImage? {
let scale = newWidth/self.size.width
if abs(scale - 1.0) < 0.01 { return self }
let newHeight = self.size.height * scale
UIGraphicsBeginImageContext(CGSize(width: newWidth, height: newHeight))
self.draw(in: CGRect(x: 0, y: 0, width: newWidth, height: newHeight))
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage
}
In addition, I added some code to a PKCanvasViewDelegate methods:
//MARK: - PKCanvasViewDelegate
func canvasViewDrawingDidChange(_ canvasView: PKCanvasView) {
self.isDrawingModified = true
#if DEBUG
self.canvasView.setNeedsDisplay()
#endif
}
The interaction with the CanvasView in the simulator is not perfect - but I believe that it is a workable solution.