Search for nearby points of interest in an iOS app using Apple's MKLocalSearch API from the MapKit framework. MKLocalSearch accepts a natural language query like "coffee shop" or "gas station", runs the search asynchronously against Apple's servers, and returns an MKLocalSearchResponse containing an array of MKMapItem objects. Each MKMapItem provides the name, phone number, URL, and placemark for a discovered location. The entire workflow requires four steps: create an MKLocalSearch.Request, configure its query and region, initialize an MKLocalSearch, and call start(completionHandler:) to execute.
Create an MKLocalSearch.Request to Define the Query
Start with MKLocalSearch.Request(). Set its naturalLanguageQuery property to the type of place you want to find. Set its region property to the visible map region so results stay local.
let request = MKLocalSearch.Request()
request.naturalLanguageQuery = "pizza restaurant"
request.region = mapView.region
The naturalLanguageQuery accepts any string. Examples include "sushi", "hotel", "pharmacy", or a specific business name. The region property narrows results to the current map area. Without a region, Apple's servers return results from a large geographic scope.
Initialize MKLocalSearch and Start the Request
Pass the request to MKLocalSearch and call start(completionHandler:).
let search = MKLocalSearch(request: request)
search.start { response, error in
guard let response = response else {
print("Search failed: (error?.localizedDescription ?? "unknown error")")
return
}
// Process response.mapItems
}
MKLocalSearch runs asynchronously. The completion handler fires on a background queue. Move UI updates to the main queue inside the handler. The error parameter captures network failures or rate-limiting issues from Apple's servers.
Convert MKMapItem Results into Map Annotations
The response object provides a mapItems array. Iterate through each MKMapItem and create MKPointAnnotation objects to display on the map.
var annotations: [MKPointAnnotation] = []
for item in response.mapItems {
let annotation = MKPointAnnotation()
annotation.title = item.name
annotation.subtitle = item.phoneNumber
if let location = item.placemark.location {
annotation.coordinate = location.coordinate
}
annotations.append(annotation)
}
mapView.showAnnotations(annotations, animated: true)
MKPointAnnotation stores a title, subtitle, and coordinate. The MKMapItem's placemark property contains a location with latitude and longitude. MKMapView.showAnnotations(_:animated:) automatically adjusts the visible region to display all annotations.
Open Results in Apple Maps or Get Directions
Each MKMapItem can launch Apple Maps or provide turn-by-turn directions.
// Open in Apple Maps
mapItem.openInMaps(launchOptions: nil)
// Get directions
let request = MKDirections.Request()
request.source = MKMapItem.forCurrentLocation()
request.destination = mapItem
let directions = MKDirections(request: request)
directions.calculate { response, error in
// Process response.routes
}
MKDirections supports driving, walking, and transit routes. Use MKMapItem.forCurrentLocation() as the starting point. The response contains an array of MKRoute objects with step-by-step instructions.
Use MKLocalSearchCompleter for Auto-Complete
MKLocalSearchCompleter provides real-time search suggestions as the user types. Configure it with a region and a search fragment.
let completer = MKLocalSearchCompleter()
completer.region = mapView.region
completer.queryFragment = "piz"
// Adopt MKLocalSearchCompleterDelegate
func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
for result in completer.results {
print(result.title, result.subtitle)
}
}
MKLocalSearchCompleter delivers results through a delegate callback. Each MKLocalSearchCompletion object contains a title and subtitle. Pass the selected completion to a full MKLocalSearch for detailed results.
Cluster Annotations for Large Result Sets
Large numbers of annotations clutter the map. iOS 11 introduced MKClusterAnnotation for automatic clustering.
mapView.register(MKMarkerAnnotationView.self,
forAnnotationViewWithReuseIdentifier: "cluster")
let clusterView = MKMarkerAnnotationView(annotation: clusterAnnotation,
reuseIdentifier: "cluster")
clusterView.clusteringIdentifier = "poi"
Set the clusteringIdentifier on each annotation view. MKMapView groups nearby annotations automatically and displays a count label. Users tap the cluster to zoom in and see individual pins.
Handle Camera Permission and Errors
MKLocalSearch requires no camera or location permission to run the search itself. However, showing the user's location on the map requires CLLocationManager authorization. Always add the NSLocationWhenInUseUsageDescription key to Info.plist.
Handle MKLocalSearch errors by checking for network connectivity before starting the search. Use NWPathMonitor from the Network framework to detect offline status. Display a user-friendly message when results are unavailable.
Best Practices for Local Search
Use specific naturalLanguageQuery values. "Thai food" returns better results than "food". "BP gas station" returns better results than "gas". Set the region to the map's visible bounds to keep results relevant. Call MKLocalSearch on the main thread but leave the completion handler as-is since it already runs asynchronously. Limit annotations by using MKClusterAnnotation when results exceed 10 items. Test on a physical device with a real location for accurate results.
Performance Considerations
MKLocalSearch sends requests to Apple's servers over the network. Typical response time is one to three seconds on a reliable connection. Cache results when the user stays in the same region to avoid redundant network calls. Cancel in-flight searches when the user pans the map by calling cancel() on the MKLocalSearch object. This prevents stale results from appearing after the region changes.
Practical Tips for Production Apps
Provide a list of common search categories like "Restaurants", "Hotels", and "Gas Stations" as preset buttons. Let users filter results by distance or rating if you integrate with your own backend. Combine MKLocalSearch with CLGeocoder to search for addresses when natural language queries fail. Use MKMapView's visibleMapRect to dynamically adjust the search region as the user moves. Log search analytics to understand what your users are looking for most frequently.
Summary
This tutorial covered the complete implementation process from setup to working code. You now have a functional implementation that you can extend and customize for your own projects. The same patterns apply to related features in the iOS ecosystem.
Frequently Asked Questions
Can I test this on the iOS Simulator? Some features require a physical device with a compatible processor. The article notes specific sections where the Simulator is sufficient and where a real device is required.
What iOS version is required? This tutorial targets iOS 11. Most of the APIs shown work on earlier versions as well, but some newer APIs may require the version specified in the article.
What are the prerequisites for this tutorial? You need Xcode installed on a Mac and basic familiarity with Swift programming. No prior experience with the specific framework is required.