---
title: "MapKit Search: Local Search, Completions, and Geocoding"
slug: "mapkit-search"
category: "ios"
tags: ["ios", "mapkit", "swift", "search", "geocoding", "local-search", "swiftui", "mklocalSearch"]
status: "stable"
last_updated: 2026-05-14
summary: "Using MKLocalSearch, MKLocalSearchCompleter, and CLGeocoder to find places, provide autocomplete, and resolve addresses in MapKit for SwiftUI."
related: ["[[ios/mapkit]]", "[[ios/mapkit-annotations]]", "[[ios/mapkit-camera]]", "[[ios/mapkit-look-around]]", "[[ios/swiftui]]", "[[coding/swift]]", "[[ios/core-data]]"]
---

## Overview

MapKit provides three search primitives: `MKLocalSearch` for structured place and point-of-interest queries, `MKLocalSearchCompleter` for real-time autocomplete suggestions, and `CLGeocoder` for converting between addresses and coordinates. Combining them well produces a search experience that rivals Apple Maps itself. For displaying results as pins, see [[ios/mapkit-annotations]]; for animating to results, see [[ios/mapkit-camera]]; for street-level previews of a selected result, see [[ios/mapkit-look-around]].

## Use `MKLocalSearch` for place and POI queries

`MKLocalSearch` takes an `MKLocalSearch.Request` and returns `MKLocalSearch.Response` with an array of `MKMapItem`. Scope the search to the visible region so results are relevant.

```swift
func search(query: String, region: MKCoordinateRegion) async throws -> [MKMapItem] {
  let request = MKLocalSearch.Request()
  request.naturalLanguageQuery = query
  request.region = region
  request.resultTypes = [.pointOfInterest, .address]
  let response = try await MKLocalSearch(request: request).start()
  return response.mapItems
}
```

Limit result count with `request.resultTypes` and the `pointOfInterestFilter` property. Returning unbounded results and discarding them client-side wastes network and memory.

## Drive autocomplete with `MKLocalSearchCompleter`

`MKLocalSearchCompleter` produces suggestion strings as the user types. Observe completions using Combine or an `@Observable` wrapper.

```swift
@Observable final class SearchModel: NSObject, MKLocalSearchCompleterDelegate {
  var completions: [MKLocalSearchCompletion] = []
  private let completer = MKLocalSearchCompleter()

  override init() {
    super.init()
    completer.delegate = self
    completer.resultTypes = [.pointOfInterest, .query]
  }

  func update(query: String, region: MKCoordinateRegion) {
    completer.region = region
    completer.queryFragment = query
  }

  func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
    completions = completer.results
  }

  func completer(_ completer: MKLocalSearchCompleter, didFailWithError error: Error) {
    completions = []
  }
}
```

Bind `queryFragment` to a text field. Do not debounce the assignment; `MKLocalSearchCompleter` already rate-limits internally. Debouncing adds latency without benefit.

## Resolve a completion to `MKMapItem` before displaying on the map

`MKLocalSearchCompletion` is a display suggestion, not a coordinate. Convert it to an `MKMapItem` with a second `MKLocalSearch` call before placing an annotation or flying the camera.

```swift
func resolve(_ completion: MKLocalSearchCompletion) async throws -> MKMapItem? {
  let request = MKLocalSearch.Request(completion: completion)
  let response = try await MKLocalSearch(request: request).start()
  return response.mapItems.first
}
```

Show a loading indicator between tap and pin appearance. The round trip is fast but not instant, and a frozen UI is worse than a brief spinner.

## Geocode addresses with `CLGeocoder` when you have free-form text

`CLGeocoder` resolves unstructured address strings and reverse-geocodes coordinates to placemarks. Use it when the input is an address rather than a place name.

```swift
func geocode(_ addressString: String) async throws -> CLPlacemark? {
  let placemarks = try await CLGeocoder().geocodeAddressString(addressString)
  return placemarks.first
}

func reverseGeocode(_ coordinate: CLLocationCoordinate2D) async throws -> CLPlacemark? {
  let location = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
  return try await CLGeocoder().reverseGeocodeLocation(location).first
}
```

`CLGeocoder` makes a network request. Cache results in [[ios/core-data]] when you geocode the same addresses repeatedly (e.g. a stored address book).

## Cancel in-flight searches when the query changes

Holding a live `MKLocalSearch` instance and calling `cancel()` when a new query arrives avoids stale results overwriting fresher ones.

```swift
@Observable final class PlaceSearchModel {
  var results: [MKMapItem] = []
  private var activeSearch: MKLocalSearch?

  func search(query: String, region: MKCoordinateRegion) async {
    activeSearch?.cancel()
    guard !query.isEmpty else { results = []; return }
    let request = MKLocalSearch.Request()
    request.naturalLanguageQuery = query
    request.region = region
    let search = MKLocalSearch(request: request)
    activeSearch = search
    results = (try? await search.start())?.mapItems ?? []
  }
}
```

## Scope searches to the visible region with `region` and `regionPriority`

`MKLocalSearch.Request.regionPriority` controls whether the region is a strict filter or a ranking hint. Use `.required` when out-of-region results are useless; use `.default` to allow Apple Maps to include a few near-region results when local density is low.

```swift
request.region = mapRegion
request.regionPriority = .required
```

Update the region from the map's `onMapCameraChange(frequency: .onEnd)` callback; do not hard-code a fixed region.

## Filter by point-of-interest category for category browsing

`MKPointOfInterestFilter` restricts results to specific `MKPointOfInterestCategory` values. Use it for category browse screens (restaurants, parks, transit).

```swift
request.pointOfInterestFilter = MKPointOfInterestFilter(including: [.restaurant, .cafe])
```

Combine with a UI segmented control or a chip row mapped to category groups. Store the user's last-used category filter preference in `UserDefaults`.

## Related

- [[ios/mapkit]]
- [[ios/mapkit-annotations]]
- [[ios/mapkit-camera]]
- [[ios/mapkit-look-around]]
- [[ios/mapkit-overlays]]
- [[ios/swiftui]]
- [[coding/swift]]
