Sample Code

Searching for Nearby Points of Interest

Provide automatic search completions based on a user’s partial search query, and search the map for relevant locations nearby.

Download

Overview

The MapSearch code sample demonstrates how to programmatically search for map-based addresses and points of interest using a natural language string. The places found are centered around the user’s current location.

Request Search Completions

MKLocalSearchCompleter retrieves auto-complete suggestions for a partial search query within a map region. A user can type “cof,” and a search completion will suggest “coffee” as the query string. As the user types a query into a search bar, your app updates the queryFragment through the UISearchResultsUpdating protocol.

func updateSearchResults(for searchController: UISearchController) {
    // Ask `MKLocalSearchCompleter` for new completion suggestions based on the change in the text entered in `UISearchBar`.
    searchCompleter.queryFragment = searchController.searchBar.text ?? ""
}

Receive Completion Results

Completion results represent fully formed query strings based on the query fragment typed by the user. You can use completion results to populate UI elements like a table view, to quickly fill in a search query. You receive the latest completion results as an array of MKLocalSearchCompletion objects by adopting the MKLocalSearchCompleterDelegate protocol.

func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
    // As the user types, new completion suggestions are continuously returned to this method.
    // Overwrite the existing results, and then refresh the UI with the new results.
    completerResults = completer.results
    tableView.reloadData()
}

func completer(_ completer: MKLocalSearchCompleter, didFailWithError error: Error) {
    // Handle any errors returned from MKLocalSearchCompleter.
    if let error = error as NSError? {
        print("MKLocalSearchCompleter encountered an error: \(error.localizedDescription)")
    }
}

Highlight the Relationship of a Query Fragment to the Suggestion

Within the UI elements representing each query result, you can use the titleHighlightRanges and subtitleHighlightRanges on a MKLocalSearchCompletion to show how the query entered by the user relates to the suggested result. For example, apply a highlight with NSAttributedString.

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: SuggestedCompletionTableViewCell.reuseID, for: indexPath)

    if let suggestion = completerResults?[indexPath.row] {
        // Each suggestion is a MKLocalSearchCompletion with a title, subtitle, and ranges describing what part of the title
        // and subtitle matched the current query string. The ranges can be used to apply helpful highlighting of the text in
        // the completion suggestion that matches the current query fragment.
        cell.textLabel?.attributedText = createHighlightedString(text: suggestion.title, rangeValues: suggestion.titleHighlightRanges)
        cell.detailTextLabel?.attributedText = createHighlightedString(text: suggestion.subtitle, rangeValues: suggestion.subtitleHighlightRanges)
    }

    return cell
}

private func createHighlightedString(text: String, rangeValues: [NSValue]) -> NSAttributedString {
    let attributes = [NSAttributedString.Key.backgroundColor: UIColor(named: "suggestionHighlight")! ]
    let highlightedString = NSMutableAttributedString(string: text)
    
    // Each `NSValue` wraps an `NSRange` that can be used as a style attribute's range with `NSAttributedString`.
    let ranges = rangeValues.map { $0.rangeValue }
    ranges.forEach { (range) in
        highlightedString.addAttributes(attributes, range: range)
    }
    
    return highlightedString
}

Search for Map Items

An MKLocalSearch.Request takes either an MKLocalSearchCompletion or a natural language query string, and returns an array of MKMapItem objects. Each MKMapItem represents a geographic location, like a specific address, matching the search query. You asynchronously retrieve the array of MKMapItem objects by calling start(completionHandler:) on MKLocalSearch.

private func search(using searchRequest: MKLocalSearch.Request) {
    // Confine the map search area to an area around the user's current location.
    if let region = boundingRegion {
        searchRequest.region = region
    }
    
    // Use the network activity indicator as a hint to the user that a search is in progress.
    UIApplication.shared.isNetworkActivityIndicatorVisible = true
    
    localSearch = MKLocalSearch(request: searchRequest)
    localSearch?.start { [weak self] (response, error) in
        guard error == nil else {
            self?.displaySearchError(error)
            return
        }
        
        self?.places = response?.mapItems
        
        // Used when setting the map's region in `prepareForSegue`.
        self?.boundingRegion = response?.boundingRegion
        
        UIApplication.shared.isNetworkActivityIndicatorVisible = false
    }
}

See Also

Placemark Search

class MKLocalSearch

A utility object for initiating map-based searches and processing the results.

class MKLocalSearchCompleter

A utility object for generating a list of completion strings based on a partial search string that you provide.

class MKLocalSearchCompletion

A fully formed string that completes a partial string.