iosswiftmapkitmkmapview

MapKit show local results without location permission


I am building an uber-like app that shows recommended addresses as the user types. Initially, when I started building the app it prompted for the user's location and the autocomplete was smart because it would search for local addresses first. I realized that having the user's location was unnecessary because this is more of a ride request service than a real-time service. After removing the user's location the autocomplete became very poor (expectedly). This app will be used in a very specific region and I want the autocomplete to be smart. Is there any way to set a region for MKLocalSearch without knowing the users location?

Here is the class that retrieves autocomplete addresses...

import Foundation
import MapKit

class LocationSearchViewModel: NSObject, ObservableObject{
    
    @Published var totalPrice:String = "Loading..."

    @Published var results = [MKLocalSearchCompletion]()
    @Published var fromResult = MKLocalSearchCompletion()
    @Published var toResult = MKLocalSearchCompletion()
    
    @Published var fromAveLocation : AveLocation?
    @Published var toAveLocation : AveLocation?
    
    private let searchCompleter = MKLocalSearchCompleter()
        
    @Published var fromQueryFragment: String = ""{
        didSet{
            searchCompleter.queryFragment = fromQueryFragment
        }
    }
    
    @Published var toQueryFragment: String = ""{
        didSet{
            searchCompleter.queryFragment = toQueryFragment
        }
    }
    
    override init() {
        super.init()
        searchCompleter.delegate = self
        searchCompleter.queryFragment = fromQueryFragment
    }
    
    func selectLocation(_ localSearch: MKLocalSearchCompletion,_ localSearch2: MKLocalSearchCompletion){
        locationSearch(forLocalSearchCompletion: localSearch){response, error in
            self.locationSearch(forLocalSearchCompletion: localSearch2){response2, error2 in
                if let error = error{
                    print("DEBUG: Location search failed with error \(error.localizedDescription)")
                    return
                }
                
                if let error2 = error2{
                    print("DEBUG: Location search failed with error \(error2.localizedDescription)")
                    return
                }
                
                guard let item = response?.mapItems.first else{return}
                guard let item2 = response2?.mapItems.first else{return}
                let coordinate = item.placemark.coordinate
                let coordinate2 = item2.placemark.coordinate
                self.fromAveLocation = AveLocation(title: localSearch.title, subtitle: localSearch.subtitle, coordinate: coordinate)
                self.toAveLocation = AveLocation(title: localSearch2.title, subtitle: localSearch2.subtitle, coordinate: coordinate2)
                                
            }
        }
    }
    
    
    func locationSearch(forLocalSearchCompletion localSearch: MKLocalSearchCompletion,
                        completion: @escaping MKLocalSearch.CompletionHandler){
        let searchRequest = MKLocalSearch.Request()
        searchRequest.naturalLanguageQuery = localSearch.title.appending(localSearch.subtitle)
        let search = MKLocalSearch(request: searchRequest)
        
        search.start(completionHandler: completion)
    }
    
}


extension LocationSearchViewModel: MKLocalSearchCompleterDelegate{
    func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
        self.results = completer.results
    }
}

Here is where it is invoked...

ScrollView{
                VStack(alignment: .leading){
                    ForEach(viewModel.results, id: \.self){ result in
                        LocationSearchResultCell(title: result.title, subtitle: result.subtitle)
                            .onTapGesture {
                                
                                switch whereIsUser {
                                case .from:
                                    viewModel.fromQueryFragment = result.title
                                    viewModel.fromResult = result
                                    whereIsUser = .to
                                    focusedField = .to
                                    break
                                    
                                case .lookingFrom:
                                    viewModel.fromQueryFragment = result.title
                                    viewModel.fromResult = result
                                    whereIsUser = .to
                                    focusedField = .to
                                    break
                                
                                case .to:
                                    viewModel.toQueryFragment = result.title
                                    viewModel.toResult = result
                                    whereIsUser = .done
                                    withAnimation(.spring()){
                                        mapState = .locationSelected
                                        viewModel.selectLocation(viewModel.fromResult, viewModel.toResult)
                                    }

                                    break
                                    
                                case .lookingTo:
                                    viewModel.toQueryFragment = result.title
                                    viewModel.toResult = result
                                    whereIsUser = .done
                                    withAnimation(.spring()){
                                        mapState = .locationSelected
                                        viewModel.selectLocation(viewModel.fromResult, viewModel.toResult)
                                    }
                                    break
                                    
                                default:
                                    break
                                }
                            }
                    }
                }
            }

checked to make sure the simulator location was set to the right spot. Just in case the MKLocalSearch works even without getting the users location.


Solution

  • You can manually create a region using MKCoordinateRegion(center: CLLocationCoordinate2D, latitudinalMeters: CLLocationDistance, longitudinalMeters: CLLocationDistance)

    searchRequest.region = MKCoordinateRegion(center: .init(latitude: /*region latitude*/, longitude: \*region longitude*/), latitudinalMeters: 500, longitudinalMeters: 500)
    

    and/or

     searchCompleter.region = MKCoordinateRegion(center: .init(latitude: /*region latitude*/, longitude: \*region longitude*/), latitudinalMeters: 500, longitudinalMeters: 500)