I have in my app a layout showing a list of rectangular cards - each one should be tappable (once) to reveal a set of action buttons and more information, etc.
I have implemented this using .onTapGesture()
and I have also put .contentShape(Rectangle()
to enforce the tappable area. However, while my implementation works fine for touchscreen interface, when I'm using it with the iPadOS mouse support, and on Catalyst for that matter, I see some very unexpected behaviour.
I've made a minimal reproducible example below that you can copy to recreate the problem.
Where the problems are when using mouse/trackpad input:
- Not every click of the mouse is recorded as a tap gesture. This is happening mostly arbitrary except from in a few cases:
- It seems to click either only in very specific areas, or when clicking multiple times in the same spot.
- It seems that in a lot of cases only every other click is recorded. So I double click to get only one tap gesture.
- It isn't evident in this example code, but in my main app the tappable areas are seemingly arbitrary - you can usually click near text or in alignment with it to record a tap gesture, but not always.
If you are running the example code you should be able to see the problem by repeatedly moving the mouse and attempting one click. It doesn't work unless you click multiple times in the same spot.
What does work as expected:
- All input using touch instead of mouse; regardless of where you tap it records a tap gesture.
- Mouse input when running as a native Mac target. The issues mentioned above are only for mouse/trackpad when running the example under iPadOS and Mac Catalyst.
Code I used to recreate this problem (has a counter to count every time a tap gesture is recorded):
struct WidgetCompactTaskItemView: View {
let title: String
let description: String
var body: some View {
HStack {
Rectangle()
.fill(Color.purple)
.frame(maxWidth: 14, maxHeight: .infinity)
VStack(alignment: .leading) {
Text(title).font(.system(size: 14, weight: .bold, design: .rounded))
Text(description).font(.system(.footnote, design: .rounded))
.frame(maxHeight: .infinity)
.fixedSize(horizontal: false, vertical: true)
.lineLimit(1)
.padding(.vertical, 0.1)
Spacer()
}
.padding(.horizontal, 6)
.padding(.top, 12)
}
.frame(maxWidth: .infinity, maxHeight: 100, alignment: .leading)
.background(Color.black)
.cornerRadius(16)
.overlay(
RoundedRectangle(cornerRadius: 16)
.stroke(Color.green, lineWidth: 0.5)
)
}
}
struct ContentView: View {
@State var tapCounter = 0
var body: some View {
VStack {
Text("Button tapped \(tapCounter) times.")
WidgetCompactTaskItemView(title: "Example", description: "Description")
.contentShape(Rectangle())
.onTapGesture(count: 1) {
tapCounter += 1
}
Spacer()
}
}
}
I have tried several things including moving modifiers around, setting eoFill
to true on the contentShape
modifier (which didn't fix the problem but simply made different unexpected behaviour).
Any help to find a solution that works as expected and works consistently whether mouse or touch would be much appreciated. I am not sure if I am doing something wrong or if there is a bug here, so please try and recreate this example yourself using the code to see if you can reproduce the problem.