Search in MapKit iOS Tutorial

In MapKit the MKLocalSearchRequest object type can be used toprovide a search query. The response can then be manipulated. In this tutorial a search for Hotels and Musea in the city of Amsterdam wil be made. The results of the search will be displayed on the map using annotations. This tutorial is made with Xcode 10 and built for iOS 12.

Open Xcode and create a new Single View App.

For product name, use IOS12SearchMapKitTutorial and then fill out the Organization Name and Organization Identifier with your customary values. Enter Swift as Language and choose Next.

Go to the Project Settings and select the Capabilities tab. Turn the Maps Capabilities on to link the MapKit framework to the project.

Go to the Storyboard and drag a MapKit View to the main view. Select the MapKit View and go to the Size Inspector, enter the following values. 

Also drag a Segmented Control to the main View and place it below the MapKit View. Double-click to change the predefined titles into "Hotel" and "Museum".  Go to the Editor menu and choose Resolve Auto Layout Issues -> Add Missing Constraints.

The Storyboard should like this.

Select the Assistant Editor and make sure the ViewController.swift is visible. Ctrl and drag from the Map View to the ViewController class and create the following Outlet.

Ctrl and drag from the Segmented Control to the ViewController class and create the following Outlet.

Finally, Ctrl and drag from the Segmented Control to the ViewController class and create the following Action.

Go to the ViewController.swift file and at the top of the file import the MapKit framework.

import MapKit

Change the class declaration to make the ViewController adopt the MPMapViewDelegate protocol.

class ViewController: UIViewController, MKMapViewDelegate {

Add the following property constants the ViewController class.

let initialLocation = CLLocation(latitude: 52.3740300, longitude: 4.8896900)
let searchRadius: CLLocationDistance = 2000

These are the coordinates of the center of Amsterdam with a search radius of 2000 metres. Change the viewDidLoad method to

override func viewDidLoad() {
    super.viewDidLoad()
        
    mapView.delegate = self
        
    let coordinateRegion = MKCoordinateRegion.init(center: initialLocation.coordinate, latitudinalMeters: searchRadius * 2.0, longitudinalMeters: searchRadius * 2.0)
    mapView.setRegion(coordinateRegion, animated: true)
}

The coordinateRegion variable is created from the initial loaction, with a latitiude and longitude twice the value of the searchRadius. Build and Run the project to see the visible Region.

To search inside a Map View a new searchInMap() method is created.

func searchInMap() {
    // 1
    let request = MKLocalSearch.Request()
    request.naturalLanguageQuery = segmentedControl.titleForSegment(at: segmentedControl.selectedSegmentIndex)
    // 2
    let span = MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1) 
    request.region = MKCoordinateRegion(center: initialLocation.coordinate, span: span)
    // 3
    let search = MKLocalSearch(request: request)
    search.start(completionHandler: {(response, error) in
            
        for item in response!.mapItems { 
            self.addPinToMapView(title: item.name, latitude: item.placemark.location!.coordinate.latitude, longitude: item.placemark.location!.coordinate.longitude)
        }
    })
}
  1.  a MKLocalSearchRequest object is created, which can be used to specify search parameters. The search query equals to the title of the currently selected index of the segmented control.
  2. The region on which the search will be applied is defined.
  3. a MKLocalSearch object initiates a search operation and will deliver the results back into an array of MKMapItems. This will contain the name, latitude and longitude of the current POI. 

Xcode will give an error of a non-existent appPintoMapView method, this will be implemented shortly. Add the following line to the end of the viewDidLoad method to call the searchinMap method

searchInMap()

Add the addPinToMapView method

func addPinToMapView(title: String?, latitude: CLLocationDegrees, longitude: CLLocationDegrees) {
    if let title = title {
        let location = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
        let annotation = MKPointAnnotation()
        annotation.coordinate = location
        annotation.title = title
        
        mapView.addAnnotation(annotation)
    }
}

This will add the pin including the annotation to the map. In the current state the pins will be displayed even if we change the value in the segmented control. Change the searchOnValueChanged method.

 @IBAction func searchOnValueChanged(sender: AnyObject) {
        mapView.removeAnnotations(mapView.annotations)
        
        searchInMap()
}

The currently displayed annotations will be removed, and the new annotations will be displayed. Build and Run the project view the pins including the annotations.

You can download the source code of the IOS12SearchMapKitTutorial at the ioscreator repository on Github.