Search code examples
swiftuimapkit

How do I make a map with selectable `MKMapItem`s in iOS 17?


I want to display an Apple map in my UIKit app, but if I try to do so with UIKit I get concurrency-related warnings (Xcode 15.4 with strict concurrency warnings enabled) that I couldn't find a proper solution to.

So I opted for SwiftUI, and for the iOS 17 approach, specifically, (WWDC23).

The following code displays a map with a marker.

If you look at the Map initializer, you can see that a map camera position and a map selection are specified.

If you run the app, however, you can't actually select the marker, unless you uncomment .tag(mapItem).

import SwiftUI
import MapKit

struct MapView: View {
    @State private var mapCameraPosition: MapCameraPosition = .automatic
    @State private var mapSelection: MKMapItem?

    let mapItem = MKMapItem(
        placemark: .init(
            coordinate: .init(
                latitude: 37,
                longitude: -122
            )
        )
    )

    var body: some View {
        Map(
            position: $mapCameraPosition,
            selection: $mapSelection
        ) {
            Marker(item: mapItem)
            // .tag(mapItem)
        }
        .onChange(of: mapSelection) { _, newSelection in
            print("onChange") // never prints
            
            if let _ = newSelection {
                print("selected") // never prints
            }
        }
    }
}

If you give another tag, like 1, the code once again doesn't work.

Is that really the way to go (that is, is my code with fine once you uncomment .tag(mapItem))?

If not, how do I make a map with selectable MKMapItems in iOS 17?

iOS 17.5, iPhone 15 Pro simulator, Xcode 15.4, macOS 14.5, MacBook Air M1 8GB.


Solution

  • Ok you may as well display markers like this:

    ForEach(mapItems, id: \.self) {
        Marker(item: $0)
    }
    

    where mapItems is an array of type [MKMapItem].

    Complete code:

    import SwiftUI
    import MapKit
    
    struct MapView: View {
        @State private var mapCameraPosition: MapCameraPosition = .automatic
        @State private var mapSelection: MKMapItem?
        let mapItems = [MKMapItem(
            placemark: .init(
                coordinate: .init(
                    latitude: 37,
                    longitude: -122
                )
            )
        )]
    
        var body: some View {
            Map(
                position: $mapCameraPosition,
                selection: $mapSelection
            ) {
                ForEach(mapItems, id: \.self) {
                    Marker(item: $0)
                }
            }
            .onChange(of: mapSelection) { _, newSelection in
                print("onChange")
                
                if let _ = newSelection {
                    print("selected")
                }
            }
        }
    }