swiftuilocation

onTapGesture hit-testing location, SwiftUI


I'm checking the location using the ontapGesture method on SwiftUI. By the way, I used the .onTapGesture method for the Rectangle shape I set, and when I printed the location, I can check the location as print even if I click on a fine area outside the Rectangle. The location is negative! If I want the value to be printed to fit the size of 200 * 200, do I have no choice but to use the if statement? Does the content shape method make a touchable area a little larger than the visible view?

struct TapGestureLocationExample: View {
    var body: some View {
        Rectangle()
              .frame(width: 200, height: 200)
              .background(Color.gray.opacity(0.3))
              
              .onTapGesture { location in
                print("Tapped at \(location)")
              }
              .clipShape(Rectangle())
              .contentShape(Rectangle())
    }
}

Tapped at (-8.666671752929688, 27.333328247070312) Tapped at (-5.6666717529296875, -9.0) Tapped at (9.0, -10.0)

I thought location information would only be printed for the size I want using the ontapGesture method.


Solution

  • By default, there is a small padding with gesture or even Button out of its bound. IMO, this is about UI/UX, for increasing tappable area. However, there are workarounds for your situation:

    1. I tried and saw that the padding is around ~10 for both x & y axes, so you can reduce the content inset:
    Rectangle()
        ....
        .onTapGesture { location in
            print("Tapped at \(location)")
        }
        .contentShape(Rectangle().inset(by: 10))
    
    1. If the first approach is unable to accomplish your objective, you have to filter the tap location, before firing any events:
    let rectangleSize: CGSize = .init(width: 200, height: 200)
    
    Rectangle()
        ...
        .simultaneousGesture(
            SpatialTapGesture().onEnded { value in
                guard (0...rectangleSize.width).contains(value.location.x) && (0...rectangleSize.height).contains(value.location.y) else {
                    return
                }
                print("Tapped at \(value.location)")
            }
        )