Search code examples
iosswiftuilocationmapkitxcode15

`UserAnnotation()` function doesn't work with new SwiftUI Map API from `_MapKit_SwiftUI` in Xcode15


From apple documentation Meet MapKit for SwiftUI video I have tried to follow the video tutorial and use UserAnnotation() function in order to get the user location in simulator as like the video. But it's not working.

BeantownButtons.swift


import SwiftUI
import MapKit

@available(iOS 17.0, *)
struct BeantownButtons: View {
    @Binding var searchResults: [MKMapItem]
    @Binding var position: MapCameraPosition
    
    var visibleRegion: MKCoordinateRegion?
    func search(for query: String) {
        let request = MKLocalSearch.Request()
        request.naturalLanguageQuery = query
        request.resultTypes = .pointOfInterest
        request.region = visibleRegion ?? MKCoordinateRegion(
            center: .parking,
            span: MKCoordinateSpan (latitudeDelta: 0.0125, longitudeDelta: 0.0125))
        Task {
            let search = MKLocalSearch(request: request)
            let response = try? await search.start()
            searchResults = response?.mapItems ?? []
            print("Search Results: \(searchResults)")
        }
    }
    
    var body: some View {
        
        HStack {
            Button {
                search(for: "playground")
            } label: {
                Label("Playgrounds", systemImage: "figure.and.child.holdinghands")
            }
            .buttonStyle(.borderedProminent)
            
            Button {
                search(for: "beach")
            } label: {
                Label("Beaches", systemImage: "beach.umbrella")
            }
            .buttonStyle(.borderedProminent)
            
            Button {
                position = .region(.boston)
            } label: {
                Label("Boston", systemImage: "building.2")
            }
            .buttonStyle(.bordered)
            
            Button {
                position = .region(.northShore)
            } label: {
                Label("North Shore", systemImage: "water.waves")
            }
            .buttonStyle(.bordered)
        }
        .labelStyle(.iconOnly)
    }
}

#Preview {
    if #available(iOS 17.0, *) {
        BeantownButtons(searchResults: .constant([]), position: .constant(.automatic))
    } else {
        EmptyView()
    }
}

ItemInfoView.swift


import SwiftUI
import _MapKit_SwiftUI

@available(iOS 17.0, *)
struct ItemInfoView: View {
    
    // ItemInfoView.swift - Fetch a Look Around scene
    @State  var lookAroundScene: MKLookAroundScene?
    @State  var route: MKRoute?
    @State  var selectedResult: MKMapItem

    func getLookAroundScene () {
        lookAroundScene = nil
        Task {
            let request = MKLookAroundSceneRequest(mapItem: selectedResult)
            lookAroundScene = try? await request.scene
        }
    }
    
    // ItemInfoView.swift - Format travel time for display
         var travelTime: String? {
            guard let route else { return nil }
            let formatter = DateComponentsFormatter()
            formatter.unitsStyle = .abbreviated
            formatter.allowedUnits = [.hour, .minute]
            return formatter.string(from: route.expectedTravelTime)
        }

    var body: some View {
        LookAroundPreview(initialScene: lookAroundScene)
            .overlay(alignment: .bottomTrailing) {
                HStack {
                    Text ("\(selectedResult.name ?? "")")
                    if let travelTime {
                        Text(travelTime)
                    }
                }
                .font(.caption)
                .foregroundStyle(.white)
                .padding (10)
            }
            .onAppear {
                getLookAroundScene()
            }
            .onChange(of: selectedResult) {
                getLookAroundScene()
            }
    }
}
#Preview {
    if #available(iOS 17.0, *) {
        ItemInfoView(selectedResult: MKMapItem(placemark: MKPlacemark(coordinate: .daraz)))
    } else {
        EmptyView()
    }
}

CustomMapViewLookAround.swift


import SwiftUI
import _MapKit_SwiftUI

@available(iOS 17.0, *)
struct CustomMapViewLookAround: View {
    @State private var position: MapCameraPosition = .automatic
    @State private var visibleRegion: MKCoordinateRegion?
    @State  var route: MKRoute?
    @State var searchResults: [MKMapItem] = []
    @State private var selectedResult: MKMapItem?
    
    func getDirections() {
        print("results count: \(searchResults.count)")
        route = nil
        guard let selectedResult else { return }
        let request = MKDirections.Request()
        request.source = MKMapItem(placemark: MKPlacemark(coordinate: .parking))
        request.destination = selectedResult
        Task {
            let directions = MKDirections(request: request)
            let response = try? await directions.calculate()
            route = response?.routes.first
        }
    }
    
    var body: some View {
        Map(position: $position, selection: $selectedResult) {
            Annotation("Parking", coordinate: .parking) {
                ZStack {
                    RoundedRectangle(cornerRadius: 5)
                        .fill(.background)
                    RoundedRectangle(cornerRadius: 5)
                        .stroke(.secondary, lineWidth: 5)
                    Image(systemName: "car")
                        .padding(5)
                }
            }
            .annotationTitles(.hidden)
            
            
            ForEach(searchResults, id: \.self) { result in
                Marker(item: result)
                //                    .tag(1)
            }
            .annotationTitles(.hidden)
            
            if let route {
                MapPolyline(route)
                    .stroke(.blue, lineWidth: 5)
            }
            
            UserAnnotation()
            
        }
        .mapStyle(.standard)
        .safeAreaInset(edge: .bottom) {
            HStack {
                Spacer()
                VStack(spacing: 0) {
                    if let selectedResult {
                        ItemInfoView(route: route, selectedResult: selectedResult)
                            .frame(height: 128)
                            .clipShape(RoundedRectangle (cornerRadius: 10))
                            .padding([.top, .horizontal])
                    }
                    BeantownButtons(searchResults: $searchResults, position: $position, visibleRegion: visibleRegion)
                        .padding(.top)
                }
                Spacer()
            }
            .background(.thinMaterial)
        }
        .onChange(of: searchResults) {
            position = .automatic
        }
        .onMapCameraChange { context in
            visibleRegion = context.region
        }
        .onChange(of: selectedResult) {
            getDirections()
        }
        .mapControls {
            MapUserLocationButton()
            MapCompass()
            MapScaleView()
        }
        .onAppear {
            let locationManager = CLLocationManager()
            locationManager.requestAlwaysAuthorization()
            locationManager.requestWhenInUseAuthorization()
        }
    }
}

#Preview {
    if #available(iOS 17.0, *) {
        CustomMapViewLookAround()
    } else {
        EmptyView()
    }
}


Error:

CLLocationManager(<CLLocationManager: 0x600000010b20>) for <MKCoreLocationProvider: 0x600003000c60> did fail with error: Error Domain=kCLErrorDomain Code=1 "(null)"

Whenever I click the location button Location Button. It buffers forever. I have also add the location access privacy in info.plist. Moreover, set the simulator location like this Set Simulator Location

Privacy - Location Always Usage Description
Privacy - Location Always and When In Use Usage Description

Current App View


Solution

  • I've found you still need to use the appropriate privacy keys in your Info.plist, and request location authorisation from CLLocationManager before the UserAnnotation() will display.