in a project we are working on using SwiftUI and PencilKit we are required to create a button that while being tapped, changes the PKInkingTool of the PKCanvasView we are using.
PencilKit uses UIKit so we have it enclosed in a UIViewRepresentable this way:
CanvasView
import PencilKit
import SwiftUI
struct CanvasView: UIViewRepresentable {
var canvas = PKCanvasView()
private var pen = PKInkingTool(.pen, color: .black , width: 2)
private var eraser = PKInkingTool(.pen, color: .white , width: 2)
private var marker = PKInkingTool(.marker, color: .white , width: 2)
private var corrector = PKInkingTool(.marker, color: .white , width: 2)
private var currentTool: PKInkingTool
private var width: CGFloat = 2
init() {
self.currentTool = pen
}
func makeUIView(context: Context) -> PKCanvasView {
canvas.drawingGestureRecognizer.cancelsTouchesInView = false
canvas.tool = pen
return canvas
}
func updateUIView(_ uiView: PKCanvasView, context: Context) {
DispatchQueue.main.async {
uiView.becomeFirstResponder()
}
}
mutating func setPen(color: Color) {
pen.color = color.uiColor()
pen.width = width
currentTool = pen
canvas.tool = currentTool
}
mutating func setCorrector(color: Color) {
corrector.color = color.uiColor()
corrector.width = width
currentTool = corrector
canvas.tool = currentTool
}
mutating func setMarker(color: Color) {
marker.color = color.uiColor()
marker.width = width
currentTool = marker
canvas.tool = marker
}
mutating func setWidth(width: CGFloat) {
self.width = width
currentTool.width = width
canvas.tool = currentTool
}
mutating func setEraser(width: CGFloat) {
eraser.width = width
canvas.tool = eraser
}
func setLasso(){
canvas.tool = PKLassoTool()
}
func setMagicButtonTool(tool: PKInkingTool) {
var newTool = tool
newTool.width = width
canvas.tool = newTool
}
func restoreTool() {
canvas.tool = currentTool
}
}
The button that changes the PKInkingTool only while is being tapped is as follows:
MagicButtonView
import SwiftUI
struct MagicButtonView: View {
// MARK: - View objects
@Environment(\.mainWindowSize) private var mainWindowSize
// MARK: - Public properties
var icon: Image
var color: Color
var action: (_ isTapped: Bool) -> Void
@State var isTapped: Bool = false
// MARK: - View body
var body: some View {
HStack {
Image.iconNotesPencil
.resizable()
.padding(.all, 18)
}
.background(Circle()
.foregroundColor(color)
.frame(
width: mainWindowSize.proportionalWidth(with: .widthSizeRatio),
height: mainWindowSize.proportionalHeight(with: .heightSizeRatio))
.overlay(
Circle()
.stroke(
Color.colorBackgroundGray,
lineWidth: 2
)
)
)
.frame(
width: mainWindowSize.proportionalWidth(with: .widthSizeRatio),
height: mainWindowSize.proportionalHeight(with: .heightSizeRatio))
.gesture(
DragGesture(minimumDistance: 0.01)
.onChanged({ _ in
if !isTapped {
isTapped = true
action(isTapped)
}
})
.onEnded({ _ in
isTapped = false
action(isTapped)
})
)
}
}
And would be implemented in the parent view as:
MagicButtonView(icon: .iconNotesPencil, color: .red, action: { isTapped in
if isTapped {
canvas.setMagicButtonTool(
tool: PKInkingTool(
.pen,
color: .red,
width: 2
)
)
} else {
canvas.restoreTool()
}
}),
The Problem
When we tap on the MagicButtonView , then CanvasView is unresponsive and won't register any UIGestureRecognizer event, and viceversa, when we are using CanvasView the MagicButtonView becomes unresponsive and won't register any SwiftUI.Gesture
What could be it?
Maybe its because its a mix of SwiftUI and UIKit views, we learn in this other question but we are note sure is that since in that question the problem is not with PencilKit
Another thing we thought is incompatibility with hand gestures and the Apple Pencil being on the screen due to palm rejection, but in other apps like GoodNotes, you can still change i.e. the color of the pencil while drawing, only that the change won’t be effective until you release the pencil of the screen and start drawing again, but that would be okay to us.
What have we tried?
We made a lot of combinations and try-outs, we changed MagicButtonView .gesture() to .highPriorityGesture() and also .simultaneousGesture() and no luck.
We also changed CanvasView canvas.drawingPolicy using .anyInput, .pencilOnly and .default with no results.
We also tried canvas.drawingGestureRecognizer.cancelsTouchesInView = **false** and fail.
We moved MagicBottonView to a higher parent view so it’s not in the same as CanvasView also with no results.
We also used other gestures than DragGesture() in MagicButtonView but DragGesture() is the only one that don't time out and can register got a longer time than LongPressGesture