Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to search for cities in MapKit, Swift 5?

I'm trying to build a simple weather app and I'm stuck when trying to implement the search city function. I managed to implement the search function to return a list of CLPlacemarks, however, this includes a lot of points of interest (e.g. restaurants, street names..) which make the results very messy. Is there a way to limit the results to only cities with that name? Here's the code I have:

func updateSearchResults(for searchController: UISearchController) {

    var searchText = searchController.searchBar.text
    
    request.naturalLanguageQuery = searchText
    localSearch = MKLocalSearch(request: request)
    localSearch?.start { (searchResponse, _) in
        guard let items = searchResponse?.mapItems else {
            return
        }
        self.placemarks = [CLPlacemark]()
        for pm in items {
            self.placemarks.append(pm.placemark)
        }
    }
}
like image 983
Filippo Bertolina Avatar asked Jan 24 '26 11:01

Filippo Bertolina


1 Answers

Tested using Combine and SwiftUI (Swift 5.3)

This is not the perfect solution but more or less you get only cities and countries:

MKLocalSearchCompleter settings:

searchCompleter = MKLocalSearchCompleter()    
searchCompleter.delegate = self
searchCompleter.region = MKCoordinateRegion(.world)
searchCompleter.resultTypes = MKLocalSearchCompleter.ResultType([.address])

Code to add on delegate methods:

func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
    
    let searchResults = self.getCityList(results: completer.results)
    
    print(searchResults)
}

func completer(_ completer: MKLocalSearchCompleter, didFailWithError error: Error) {
    
    print(error.localizedDescription)
}

Get the cities:

func getCityList(results: [MKLocalSearchCompletion]) -> [(city: String, country: String)]{
    
    var searchResults: [(city: String, country: String)] = []
    
    for result in results {
        
        let titleComponents = result.title.components(separatedBy: ", ")
        let subtitleComponents = result.subtitle.components(separatedBy: ", ")
        
        buildCityTypeA(titleComponents, subtitleComponents){place in
            
            if place.city != "" && place.country != ""{
                
                searchResults.append(place)
            }
        }
        
        buildCityTypeB(titleComponents, subtitleComponents){place in
            
            if place.city != "" && place.country != ""{
                
                searchResults.append(place)
            }
        }
    }
    
    return searchResults
}

You can get two types of cities:

func buildCityTypeA(_ title: [String],_ subtitle: [String], _ completion: @escaping ((city: String, country: String)) -> Void){
    
    var city: String = ""
    var country: String = ""
    
    if title.count > 1 && subtitle.count >= 1 {
        
        city = title.first!
        country = subtitle.count == 1 && subtitle[0] != "" ? subtitle.first! : title.last!
    }
    
    completion((city, country))
}

func buildCityTypeB(_ title: [String],_ subtitle: [String], _ completion: @escaping ((city: String, country: String)) -> Void){
    
    var city: String = ""
    var country: String = ""
    
    if title.count >= 1 && subtitle.count == 1 {
        
        city = title.first!
        country = subtitle.last!
    }
    
    completion((city, country))
}

👍

like image 65
Matthew RodrĂ­guez Avatar answered Jan 27 '26 02:01

Matthew RodrĂ­guez



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!