© Jeffrey Linwood 2020
J. LinwoodBuild Location Apps on iOS with Swifthttps://doi.org/10.1007/978-1-4842-6083-8_4

4. Searching for Points of Interest

Jeffrey Linwood1 
(1)
Austin, TX, USA
 

Apple has its own database of places and addresses that you can use with your iOS application. Your code can call an instance of the MKLocalSearch class to get points of interest that you can then display on a map view. This makes it easy to implement search functionality based on what geographic region is currently displaying in the map.

Create a new Swift iOS project in Xcode with the Single View Application template. Name your application SearchingForPointsOfInterest. Go ahead and add a map view to the only screen in the storyboard, and connect it to an outlet called mapView on the ViewController class . Also import the MapKit framework, if you haven’t done so. These are the steps we’ve followed in the previous chapters, so they should be pretty straightforward.

Getting started with local search

Apple’s local search functionality for MapKit isn’t a web-accessible API that you can call with an HTTP request, unlike other integrations you may be using in your application. Apple provides two helper classes – MKLocalSearch and MKLocalSearch.Request – for working with local search. Use an MKLocalSearch.Request instance to define what to search for, and then use MKLocalSearch to execute the request and return the results.

The first step is to construct the search request:
let searchRequest = MKLocalSearch.Request()
The search request object has four different properties you may use to filter down the results:
  • naturalLanguageQuery – This is a word or phrase that the service will use to find results, such as “park” or “art museum”.

  • region – This is a map region, typically the region of a map on the current screen – useful for limiting search results to a geographic location.

  • pointOfInterestFilter – Only display points of interest that match one or more of the different types in the MKPointOfInterestCategory struct. For instance, beaches, restaurants, and stores are all different categories.

  • resultTypes – Local search can return addresses, points of interest, or both.

Not all of these need to be set to use local search – typically your map application would use naturalLanguageQuery and region. If you do not set the region, the local search will use the device’s location for the search. You do not need to ask for location permissions to use local search.

If we set the natural language query on the search request, the code will look like this:
searchRequest.naturalLanguageQuery = "coffee"

The search request object describes the search we want to do – the MKLocalSearch object will run the search. There are two steps to running a search – creating the MKLocalSearch instance with a search request object and then calling the start method on the local search. Listing 4-1 contains an example of each of these steps.

With the MKLocalSearch.Request object we created earlier, the initialization of an MKLocalSearch instance looks like this:
let search = MKLocalSearch(request: searchRequest)

The start method takes a completion handler as the only argument. The completion handler will run on the main thread in your iOS app and has two optional arguments – a response of type MKLocalSearch.Response and an error of type Error.

The response will consist of an array of MKMapItem map items with the results of the search and a map region that includes all of the results. You could set the map view on the screen to the new region if you wanted to display all results, or you could let the user scroll themselves to find results if you had an alternative search results display (such as a list).
let searchRequest = MKLocalSearch.Request()
searchRequest.naturalLanguageQuery = "coffee"
let search = MKLocalSearch(request: searchRequest)
search.start { (response, error) in
  guard let response = response else {
    print("Error searching - no response")
    if let error = error {
      print("Error: (error.localizedDescription)")
    } else {
      print("No error specified")
    }
    return
   }
   for mapItem in response.mapItems {
     print(mapItem.name ?? "No name specified")
   }
 }
Listing 4-1

Searching for points of interest with MKLocalSearch

Checking that the response is provided in the callback using guard let allows you to display an error to the user. For this code, the results will be found in the debug console, but you could display an alert message to the user.

Exploring the map items in the response

The local search results will consist of MKMapItem objects . Each of these map items in the result is a point of interest and contains location and address information in a placemark object, a point of interest category, and if available, the name, phone number, URL, and time zone for the map item. These MKMapItem objects are also used for directions and routing, as we will see in Chapter 5.

The placemark objects are from the MKPlacemark class and contain latitude, longitude, and street address information. These placemarks implement the MKAnnotation protocol discussed in Chapter 3. This means that you can add placemarks directly to a map view to display results.

Displaying the search results on a map

Now that you have learned the relationship between map items and annotations, it’s easy to see how these search results can be displayed on the map view. Be sure you have a map view on your storyboard and an outlet variable named mapView on your ViewController class before trying this code.

The previous code listing has been enhanced to work with the map view – the changes are in bold in the following code listing.
let searchRequest = MKLocalSearch.Request()
searchRequest.naturalLanguageQuery = "coffee"
searchRequest.region = mapView.region
let search = MKLocalSearch(request: searchRequest)
search.start { (response, error) in
  guard let response = response else {
    print("Error searching - no response")
    if let error = error {
      print("Error: (error.localizedDescription)")
    } else {
      print("No error specified")
    }
    return
   }
   for mapItem in response.mapItems {
     self.mapView.addAnnotation(mapItem.placemark)
   }
self.mapView.setRegion(response.boundingRegion, animated: true)
 }
Listing 4-2

Displaying search results on the map

The changes in the preceding code listing accomplish a few things. The first change sets the region used in the search request to be the current region displaying in the map view. The second change adds the map item’s placemark as an annotation, instead of printing the name to the debug console. The last change sets the boundaries of the visible region on the map view to the region that contains all search results.

Create a new function named showCoffeeOnMap() and put the code in Listing 4-2 into this function. Then call your new function from the viewDidLoad() function in your ViewController class . Go ahead and run this application in the Simulator or on your device. You should see results similar to the following screenshot, depending on how much coffee is for sale in your area.
../images/485790_1_En_4_Chapter/485790_1_En_4_Fig1_HTML.jpg
Figure 4-1

Displaying search results on a map view

Creating annotations for results

One downside of adding the placemark directly as an annotation is that Apple uses the street address of the placemark as the title for the annotation, instead of using the name from the map item. This is not the best user experience, so let’s modify our code a little bit to create a new annotation from each map item, as seen in Listing 4-3.
for mapItem in response.mapItems {
  let annotation = MKPointAnnotation()
  annotation.coordinate =
    mapItem.placemark.coordinate
  annotation.title = mapItem.name
  annotation.subtitle = mapItem.phoneNumber
  self.mapView.addAnnotation(annotation)
}
Listing 4-3

Create an annotation for each search result

Replace the existing for loop with the preceding code block, and you will see the names of the coffee shops, rather than just the street addresses.

Filtering with points of interest categories

Apple’s search works well with the natural language query, whether you look for something generic or something more specific. However, if your application will be looking for points of interest in categories that Apple has defined, then using the point of interest filter will improve your user’s experience.

Set the point of interest filter on the search request with an instance of the MKPointOfInterestFilter class . This filter is new to iOS 13 and isn’t available in previous versions of iOS. When you create a point of interest filter, you have two choices – you can create a filter that includes only one or more categories, or you can create a filter that excludes one or more categories. Typically, you would probably be using the inclusion filter, but if your search results are being populated with irrelevant results from categories that don’t fit your app, it’s certainly easy to exclude categories.

The MKPointOfInterestCategory struct defines all of the different categories you can use. A partial list follows:
  • Airport

  • Amusement Park

  • Aquarium

  • ATM

  • Bakery

  • Bank

  • Beach

  • Brewery

And so forth, down to the zoo category. As you can see, if you are building an application that relies on local search, these filters could be quite useful.

To use an MKPointOfInterestFilter class with the MKLocalSearch.Request search request, you just need to set the pointOfInterestFilter property. For instance, to perform a search for local bakeries, use the following code:
searchRequest.pointOfInterestFilter =
  MKPointOfInterestFilter(including: [.bakery])
If we were to combine this point of interest filter with the preceding code listing that creates annotations from the local search map item results and then remove the natural language query string, we would have a function that looks like Listing 4-4.
let searchRequest = MKLocalSearch.Request()
searchRequest.pointOfInterestFilter =
  MKPointOfInterestFilter(including: [.bakery])
searchRequest.region = mapView.region
let search = MKLocalSearch(request: searchRequest)
search.start { (response, error) in
  guard let response = response else {
    print("Error searching - no response")
    if let error = error {
      print("Error: (error.localizedDescription)")
    } else {
      print("No error specified")
    }
    return
  }
  for mapItem in response.mapItems {
    let annotation = MKPointAnnotation()
    annotation.coordinate =
      mapItem.placemark.coordinate
    annotation.title = mapItem.name
    annotation.subtitle = mapItem.phoneNumber
    self.mapView.addAnnotation(annotation)
  }
  self.mapView.setRegion(response.boundingRegion, animated: true)
}
Listing 4-4

Category filter with map annotations

Running this code on your iOS app will yield similar results to this screenshot, depending on how many bakeries are in your area.
../images/485790_1_En_4_Chapter/485790_1_En_4_Fig2_HTML.jpg
Figure 4-2

Points of interest from a category filter with annotations

Working with local search is fairly straightforward, as the MKLocalSearch class that Apple provides does not have any advanced functionality, such as pagination of search results. Creating your own annotations from map items in the search results generally makes the most sense. Using the techniques in Chapter 3, you can create custom annotation views, for instance, to match the categories for the points of interest.

In the next chapter, we will use Apple’s directions API to create routes for driving or walking. The directions API also uses the MKMapItem class , and we will explore other functionality for map items that wasn’t covered in this chapter, such as user location and opening map items in the Apple Maps app.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.143.228.40