paint-brush
Integrating Address-Based Location Search in an IOS App: A Step-By-Step Guideby@vladosius
125 reads

Integrating Address-Based Location Search in an IOS App: A Step-By-Step Guide

by Vladislav MokrovJuly 25th, 2023
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

In this article, we take a look at the fun process of implementing location search by address in an iOS app.
featured image - Integrating Address-Based Location Search in an IOS App: A Step-By-Step Guide
Vladislav Mokrov HackerNoon profile picture


Introduction

In this article, we take a look at the fun process of implementing location search by address in an iOS app. If you’re developing an app with maps or location-related functionality, this guide will help you get specific coordinates based on the address the user enters in the search bar.


1. Import into your file

import CoreLocation
import MapKit


What is the difference between CoreLocation and MapKit?


CoreLocation is the processing of location data. It uses all available device components, including cellular equipment, Wi-Fi, Bluetooth, GPS, barometer, and magnetometer.


MapKit is all about visual operations such as map rendering, route directions, address finding, and user-friendly operations. Anyway, who wants to type in latitude instead of a readable address?


2. Properties that will be needed

private lazy var completer = MKLocalSearchCompleter()


private lazy var searchResults: [(title: String?, subtitle: String?, coordinate: CLLocationCoordinate2D?)] = []


private var searchCompletion: (([(title: String?, subtitle: String?, coordinate: CLLocationCoordinate2D?)]) -> Void)?


private var userRegionCoordinate: CLLocationCoordinate2D?

3. Problem with geocodeAddressString from CLGeocoder

At the beginning of development, I decided to use the geocodeAddressString method from CLGeocoder in the searchAddressesForText method. However, I ran into a certain problem. This method always returned only one address with coordinates, not providing a complete list of addresses to choose from.


For example, in some countries, there may be many streets with the same name. If I passed such a string to the method, I received only one CLPlacemark representing a street in a random city. This limitation was not suitable for my application, so I needed to find an alternative solution.


func searchAddressesForText(_ text: String, completion: @escaping ([String]) -> Void) {
        let region = CLCircularRegion(center: userRegionCoordinate, radius: 30000, identifier: "SearchRegion")
        
        let geocoder = CLGeocoder()
        geocoder.geocodeAddressString(text, in: region, completionHandler: { (placemarks, error) in
            var addresses: [String] = []
            if let placemarks = placemarks {
                for placemark in placemarks {
                    if let thoroughfare = placemark.thoroughfare, let subThoroughfare = placemark.subThoroughfare {
                        let address = "\(thoroughfare), \(subThoroughfare)"
                        addresses.append(address)
                    } else if let thoroughfare = placemark.thoroughfare {
                        let address = "\(thoroughfare)"
                        addresses.append(address)
                    }
                }
            }
            completion(addresses)
        })
    }
}


4. Using MKLocalSearchCompleter

I later discovered that MKLocalSearchCompleter could solve this problem. It provides multiple addresses by closest match, allowing the user to select the correct address from the list.


However, this solution has the disadvantage that it does not provide coordinates for each address. Instead, the user must select one of the addresses and then call the searchCoordinatesForAddress method to get the coordinates of the selected address.


func searchAddressesForText(_ text: String,
                            completion: @escaping ([(title: String?,
                                                     subtitle: String?,
                                                     coordinate: CLLocationCoordinate2D?)]) -> Void) {
    completer.delegate = self
    completer.queryFragment = text
    
    if let userRegionCoordinate = self.userRegionCoordinate {
        let region = MKCoordinateRegion(center: userRegionCoordinate, latitudinalMeters: 5000, longitudinalMeters: 5000)
        completer.region = region
    }
    
    searchCompletion = completion
}


To use MKLocalSearchCompleter, I have created an instance of MKLocalSearchCompleter and set up its delegate. I then set the search text and region to get the appropriate results. After updating the search results, I retrieve the address list and pass it to the completion unit for further processing or display.


5. MKLocalSearchCompleterDelegate

To overcome the limitation of no coordinates for addresses obtained with MKLocalSearchCompleter, I used MKLocalSearchCompleter to get a list of addresses and then used MKLocalSearch to get the coordinates of the selected address.


extension SomeViewController: MKLocalSearchCompleterDelegate {
    public func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
        searchResults = completer.results.compactMap { result in
            let street = result.title
            let subtitle = result.subtitle
            let searchRequest = MKLocalSearch.Request(completion: result)
            let coordinate = searchRequest.region.center

            //If you want to exclude any result
            let isRelevant = number != "find near me"
            guard isRelevant else {
                return nil
            }
            return (title: street, subtitle: number, coordinate: coordinate)
        }
        searchCompletion?(searchResults)
    }
    
    public func completer(_ completer: MKLocalSearchCompleter, didFailWithError error: Error) {
        searchResults = []
        searchCompletion?(searchResults)
    }
}


6. Getting coordinates

The user is given a list of addresses obtained with MKLocalSearchCompleter and can select one of them. When the user selects an address, I pass it to the searchCoordinatesForAddress method to retrieve the corresponding coordinates using MKLocalSearch.


In this way, I combine the advantages of both approaches and get the full functionality of location search by address.

func searchCoordinatesForAddress(_ address: [(title: String?, subtitle: String?)],
                                 completion: @escaping ([(title: String?,
                                                          subtitle: String?,
                                                          coordinate: CLLocationCoordinate2D?)]?) -> Void) {
    let searchRequest = MKLocalSearch.Request()
    let entireAddress = address.title + " " + address.subtitle
    searchRequest.naturalLanguageQuery = entireAddress
    
    let search = MKLocalSearch(request: searchRequest)
    search.start { response, error in
        if let response = response,
            let firstPlacemark = response.mapItems.first?.placemark {
            let coordinate = firstPlacemark.coordinate
            completion(title: address.title, subtitle: address.subtitle, coordinate: coordinate)
        } else {
            completion(nil)
        }
    }
}


7. Cancel the search if it is no longer required

For example, if the user has typed an address and decides to close the screen immediately.
Or when entering each new character into searchBar in method:


func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String)


You must cancel the search with this method.

func cancelSearchAddresses() {
     if completer.isSearching {
         completer.cancel()
     }
}


Conclusion

In this article, we looked at the process of implementing location search functionality by address in an iOS application. We found a problem using the geocodeAddressString method from CLGeocoder, which returns only one address, and a solution to this problem using MKLocalSearchCompleter, which provides multiple addresses but no coordinates.


We also looked at a combined approach which allows us to get both a list of addresses and the corresponding coordinates of the selected address.


I hope this guide was useful for you and helped you solve the problem of finding a location by address in your iOS app.


If you have any further questions, please feel free to ask.


Also published here.