SwiftUI action when clicking on map annotation

Viewed 1046

When clicking a specific annotation in MapView I wish to add an overlay with info about the annotation.

What i am looking for is being able to update a variable and then call updateUIView() such that the map shows the overlay corresponding to the annotation that was clicked.

2 Answers

You can do it like this:

let places: [Place] = [
    .init(name: "One", coordinate: .init(latitude: 56.951924, longitude: 24.125584)),
    .init(name: "Two", coordinate: .init(latitude:  56.967520, longitude: 24.105760)),
    .init(name: "Five", coordinate: .init(latitude: 56.9539906, longitude: 24.13649290000000))
]

@State var coordinateRegion = MKCoordinateRegion(center: .init(latitude: 54.6872, longitude: 25.2797), latitudinalMeters: 2000000, longitudinalMeters: 2000000)

var body: some View {
    Map(coordinateRegion: $coordinateRegion, annotationItems: places) { (place) in
        MapAnnotation(coordinate: place.coordinate) {
            Button(action: { print(place.name) }, label: {
                Text(place.name)
            })
        }
    }
}

The way to achieve this is track the selected item:

@State private var results: [SearchResult] = []
@State private var selectedResult: UUID?

Your items should already be Identifiable, something like this:

struct SearchResult: Identifiable {
    let id = UUID()
    let mapItem: MKMapItem
}

Then when you iterate through your map annotations you can decide to generate the selected one, or non-selected one:

Map(coordinateRegion: $region, interactionModes: .all, showsUserLocation: true, userTrackingMode: $trackUser, annotationItems: results) { result in
    buildAnnotation(result, isSelected: result.id == selectedResult)
}

And when building the annotation, add a tap gesture recognizer to change the selected item, and center the map if desired:

private func buildAnnotation(_ result: SearchResult, isSelected: Bool) -> some MapAnnotationProtocol {
    MapAnnotation(coordinate: result.mapItem.placemark.coordinate, anchorPoint: CGPoint(x: 0.5, y: 1.0)) {
        MyCustomView(isSelected: isSelected)
            .onTapGesture {
                withAnimation {
                    selectedResult = result.id
                    region.center = result.mapItem.placemark.coordinate
                }
            }
    }
}

The MyCustomView is just a normal SwiftUI View with body and you can pass in whatever parameters you want to use from the item it represents, including whether it's selected or not.

Related