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