swiftuigesture-recognition

Why doesn't SwiftUI onTapGesture always work


The onTapGesture in SWiftUI doesn't work reliably. This example shows the problem, which is that sometimes when you tap on a cell, the background changes to grey as it should, and another time an adjacent cell changes and at other time nothing happens at all. Any ideas why?

struct ContentView: View {
    @State var cellFg: [[Color]] = [
        [.clear, .clear, .clear, .clear, .clear, .clear, .clear, .clear, .clear],
        [.clear, .clear, .clear, .clear, .clear, .clear, .clear, .clear, .clear],
        [.clear, .clear, .clear, .clear, .clear, .clear, .clear, .clear, .clear],
        [.clear, .clear, .clear, .clear, .clear, .clear, .clear, .clear, .clear],
        [.clear, .clear, .clear, .clear, .clear, .clear, .clear, .clear, .clear],
        [.clear, .clear, .clear, .clear, .clear, .clear, .clear, .clear, .clear],
        [.clear, .clear, .clear, .clear, .clear, .clear, .clear, .clear, .clear],
        [.clear, .clear, .clear, .clear, .clear, .clear, .clear, .clear, .clear],
        [.clear, .clear, .clear, .clear, .clear, .clear, .clear, .clear, .clear]
    ]
    
    var body: some View {
        VStack {
            Spacer()
            ForEach(0..<9) { row in
                HStack {
                    Spacer()
                    ForEach(0..<9) { column in
                        Rectangle()
                            .foregroundColor(cellFg[row][column])
                            .border(Color.gray, width: 1)

                            // When you tap, it sometimes works, sometimes selects
                            // an adjacent cell and sometimes does nothing
                            .onTapGesture {
                                print("Row \(row) - Column\(column)")
                                cellFg[row][column] = .gray
                            }
                    }
                    Spacer()
                }
            }
            Spacer()
        }
    }
}

Solution

  • Rectangle here is transparent but gesture requires content to be opaque.

    Here is a fix (tested with Xcode 11.4 / iOS 13.4)

    Rectangle()
        .foregroundColor(self.cellFg[row][column])
        .border(Color.gray, width: 1)
        .contentShape(Rectangle())         // << here !!
    

    the .contentShape make hit-testable entire frame independently of transparency.