Search code examples
swiftswiftuimapkitgesturetouches

SwiftUI Views blocking touches to MapKit but not other views


I'm presenting a mapView via a UIViewRepresentable and want to have an image 'target' in the centre of the map. This image blocks both tap events and panning gestures that start on the image. So dropping an annotation onto the map below the image means that its callout can't be initiated.

enter image description here

I've read about problems with transparency so I remade the target image with the 'clear' area as a white with opacity 1% but no change in behaviour.

Setting allowsHitTesting to false doesn't work either although if I insert a button below the image (Test 1 in code) I can tap it, just not the map/annotation so there seems to be a difference between the MapKit elements and the SwiftUI views I add myself.

import SwiftUI
import MapKit

struct ContentView: View {
    var body: some View {
        ZStack {
            MapView()

/*Test 1*/  Button("Tap Me") {
                print("Text button was tapped")
            }

            Image("target")
                .frame(width: 90, height: 90)
                .allowsHitTesting(false)

/*Test 2*/  Rectangle()
                .fill(Color.red.opacity(0.2))
                .frame(width: 90, height: 90)
                .allowsHitTesting(false)

/*Test 3*/  Button(action: {print("Round button was tapped")}) {
                Circle()
                    .fill(Color.red.opacity(0.2))
                    .frame(width: 90, height: 90)
            }
            .allowsHitTesting(false)
            .disabled(true)
        }
    }
}

struct MapView: UIViewRepresentable {
    func makeUIView(context: Context) -> MKMapView {
        let map = MKMapView()
        map.mapType = .standard
        map.isRotateEnabled = false

        let location = CLLocation(latitude: 40.763783, longitude: -73.973133)
        let region = MKCoordinateRegion(center: location.coordinate, latitudinalMeters: 2000, longitudinalMeters: 2000)
        map.setRegion(region, animated: true)

        let annotation = MyAnnotation(coordinate: location.coordinate)
        annotation.title = "Title"
        annotation.subtitle = "Subtitle"
        map.addAnnotation(annotation)

        return map
    }

    func updateUIView(_ uiView: MKMapView, context: Context) {

    }
}

class MyAnnotation: NSObject, MKAnnotation {
    var coordinate: CLLocationCoordinate2D
    var title: String?
    var subtitle: String?

    init(coordinate: CLLocationCoordinate2D) {
        self.coordinate = coordinate
    }
}

Swapping the Image for a Rectangle (Test 2 in code) has the same behaviour, I can interact with Views I add but cannot pass my touches/pans through to the MapKit elements. I even tried using a disabled Button in the hope that its hit testing would be more in line with what I'm trying to achieve. Sadly not.

Does anyone know of a way to interact with the mapView, taps and pans, through another view?


Solution

  • Yes, even transparent images do not allow hit-through so far. In your case, simple enough, the possible approach is to create custom shape, like below. Shapes do pass hits.

    Here is simple demo of shape to show the direction.

    demo

    struct Cross: Shape {
        func path(in rect: CGRect) -> Path {
            return Path { path in
                path.move(to: CGPoint(x: rect.midX, y: 0))
                path.addLine(to: CGPoint(x: rect.midX, y: rect.maxY))
                path.move(to: CGPoint(x: 0, y: rect.midY))
                path.addLine(to: CGPoint(x: rect.maxX, y: rect.midY))
                path.move(to: CGPoint(x: rect.midX, y: rect.midY))
                path.addArc(center: CGPoint(x: rect.midX, y: rect.midY), radius: 10, startAngle: Angle(degrees: 0), endAngle: Angle(degrees: 360), clockwise: false)
            }
        }
    }
    
    struct ContentView: View {
        var body: some View {
            ZStack {
                MapView()
                Cross().stroke(Color.red)
                   .frame(width: 90, height: 90)
            }
        }
    }