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

3. Displaying Annotations on a MapKit Map

Jeffrey Linwood1 
(1)
Austin, TX, USA
 

In Chapter 1, your first iOS map application displayed one point on the map, with the default red pin at its location. The MapKit framework is capable of much more when it comes to displaying annotations. We can customize the images for the annotations and the callouts seen when a user taps on an annotation.

Let’s continue building on top of the FirstMapsApp project that we created in Chapter 1 and then added user locations in Chapter 2.

Understanding MapKit and annotations

The MapKit framework maintains a distinction between annotations – the data displayed on the map – and annotation views, the user interface elements that appear on top of the map at a given coordinate for an annotation. While MapKit provides the MKPointAnnotation class for basic point display, most map applications will use a custom implementation of the MKAnnotation protocol .

Similarly, MapKit also includes the MKPinAnnotationView and MKMarkerAnnotationView classes for displaying pins or markers on the app. The MKAnnotationView class is useful for displaying your own custom images. You can subclass the MKAnnotationView class or use it directly.

The mapView(_:viewFor:) function on the MKMapViewDelegate protocol allows your application to map MKAnnotationView objects to MKAnnotation objects. Because the MKPointAnnotation class has a limited number of data fields, using your own implementation of MKAnnotation gives you the most flexibility.

Using a custom annotation class

Implementing your own annotation class is straightforward – you will need to extend the NSObject class and then implement the MKAnnotation protocol .

The title and subtitle properties of the MKAnnotation protocol are String optionals . The coordinate property is a little more complicated, as MapKit requires that it supports key-value observing (KVO). In practice, this means that it is not a simple declaration with a var keyword, but instead you would also have to state that it uses dynamic dispatch with the Objective-C runtime. Your declaration in code would look like this:
@objc dynamic var coordinate: CLLocationCoordinate2D
A basic custom annotation class, with an init method , would look like Listing 3-1.
import UIKit
import MapKit
class MapPoint: NSObject, MKAnnotation {
  @objc dynamic var coordinate: CLLocationCoordinate2D
  var title:String?
  var subtitle: String?
  init(coordinate:CLLocationCoordinate2D,
        title:String?, subtitle:String?) {
    self.coordinate = coordinate
    self.title = title
    self.subtitle = subtitle
  }
}
Listing 3-1

An implementation of an annotation class in Swift

We will be using this MapPoint class and then later extending it through the course of this chapter. It’s likely that you would add additional fields to this annotation class for your specific application. Go ahead and create a new file in your Xcode project named MapPoint.swift, and add the code in Listing 3-1.

Display custom annotations

As the MapPoint class implements the MKAnnotation protocol , your code simply needs to construct new MapPoint objects and then add them to the map view as annotations.

We will create two points on the map, with generic titles and subtitles. Add the code in Listing 3-2 to the end of your viewDidLoad() method in the ViewController class.
let point1 = MapPoint(
  coordinate: CLLocationCoordinate2D(latitude: 33.0,
    longitude: -97.0),
  title: "Point 1",
  subtitle: "Description 1"
)
mapView.addAnnotation(point1)
let point2 = MapPoint(
  coordinate: CLLocationCoordinate2D(latitude: 32.0,
    longitude: -98.0),
  title: "Point 2",
  subtitle: "Description 2"
)
mapView.addAnnotation(point2)
Listing 3-2

Displaying custom annotations on a map view

After adding this code and running your project, you will not see anything too different from our earlier chapters. We have not customized the view for the annotations yet.

Customizing pins for annotations

The easiest way to change the view displayed for each annotation is to use the MKPinAnnotationView class. Your code can set the pin color in the mapView(_:viewFor:) function . This function comes from the MKMapViewDelegate, so you will need to set the delegate on the map view to either a custom class or to the current view controller. For the purposes of this chapter, we will create an extension to our view controller class that implements the MKMapViewDelegate and then set the delegate on the map view in code, in the viewDidLoad() method .

Add the following line of code to your viewDidLoad() method for the map view delegate (you can also set it in the storyboard, if you want):
mapView.delegate = self
Next, add an extension that implements the map view delegate to the bottom of your view controller class. We will add the mapView(_:viewFor:) function to this extension:
extension ViewController:MKMapViewDelegate {
}
The mapView(_:viewFor:) function returns an MKAnnotationView for a given MKAnnotation instance. We will create a pin annotation view and then do a little customization by setting the pin color to blue, as shown in Listing 3-3.
extension ViewController:MKMapViewDelegate {
  func mapView(_ mapView: MKMapView,
      viewFor annotation: MKAnnotation) ->
      MKAnnotationView? {
    let pin = MKPinAnnotationView(
      annotation: annotation,
      reuseIdentifier: "Pin")
    pin.pinTintColor = UIColor.blue
    return pin
  }
}
Listing 3-3

Using a pin annotation view

After including this code, you should see blue pins appear on the map in the state of Texas in the United States, as in Figure 3-1. If you worked through Chapter 2 and used that as a base, you will also see a blue pin over your user location. Feel free to adjust the points added to the map for your own location, of course!
../images/485790_1_En_3_Chapter/485790_1_En_3_Fig1_HTML.jpg
Figure 3-1

Changing the annotations to display as blue pins

Now that we’ve demonstrated basic view annotation features, let’s move on to some more advanced functionality. The first thing to change will be that the user’s location is now showing up as a pin.

Handling the user location

We generally want to keep the user location displaying as a pulsating blue dot, as appears by default. In our code, we can check to see if the annotation we provide a view for is an MKUserLocation annotation  – the current location of the user:
if annotation is MKUserLocation {
    return nil
}

This check would typically be done at the beginning of the mapView(_:viewFor:) function. When you return nil, the map will display the default annotation view.

Add the MKUserLocation check to the beginning of the mapView(_:viewFor:) method and then run the project again – the user location (if you have location enabled) will be the glowing blue dot, not a pin.

Reusing annotation views

While this code works, it could be improved. Similar to table views and reusable table view cells, map views support reusable annotations.

When working with annotation views, it’s important to use a reuse identifier. This allows the map view to recycle view objects as the user scrolls through the map. Some annotations will fall out of the visible rectangle of the map, and those views can be recycled for annotations that are now visible. Reusing these annotation views will allow for smooth scrolling, especially with large sets of annotations.

The MKMapView class has a dequeueReusableAnnotationView() function that takes a reuse identifier as an argument. If an annotation view is waiting in the reuse queue, you will get that instance. Any customizations you need to make to that annotation view (for instance, setting the annotation to the current annotation) should be done inside an if let block that checks if an instance was available.

If an instance wasn’t available in the reuse queue, you will need to create an annotation view and then properly configure it.

We can improve our annotation view code from Listing 3-3 by reusing views if they are available. Replace the method you already created in the map view delegate extension with the code in Listing 3-4. We also include the check for the MKUserLocation annotation .
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation)
  -> MKAnnotationView? {
  if annotation is MKUserLocation {
    return nil
  }
  let reuseId = "Pin"
  var pin: MKPinAnnotationView
  if let reusedPin =
      mapView.dequeueReusableAnnotationView(
      withIdentifier: reuseId)
    as? MKPinAnnotationView {
    pin = reusedPin
    pin.annotation = annotation
  } else {
    pin = MKPinAnnotationView(annotation: annotation,
      reuseIdentifier: reuseId)
    pin.pinTintColor = UIColor.blue
  }
  return pin
}
Listing 3-4

Reusing annotation views

While this code is more complicated than the previous code listing, scrolling the map will be much smoother if you have many annotations displayed on the map.

Dequeuing and creating annotation views

We can simplify the code in Listing 3-4 by letting the map view either dequeue a reusable annotation view or create a new annotation view for us automatically. Your code will need to register the annotation view class with the map view for the reuse identifier, using the registerClass:forAnnotationViewWithReuseIdentifier: function on the MKMapView class. Our code would use the following line of code in the viewDidLoad() method:
mapView.register(MKPinAnnotationView.self, forAnnotationViewWithReuseIdentifier: "Pin")
After registering the pin annotation view class with the map view, we can remove the pin annotation view creation code from the viewFor() method that we implemented in the MKMapViewDelegate extension . This leads to the simpler implementation shown in Listing 3-5.
override func viewDidLoad() {
  ...
  mapView.register(MKPinAnnotationView.self,
    forAnnotationViewWithReuseIdentifier: "Pin")
}
func mapView(_ mapView: MKMapView,
  viewFor annotation: MKAnnotation) -> MKAnnotationView? {
  if annotation is MKUserLocation {
    return nil
  }
  let reuseId = "Pin"
  if let pin = mapView.dequeueReusableAnnotationView(
      withIdentifier: reuseId)
    as? MKPinAnnotationView {
    pin.annotation = annotation
    pin.pinTintColor = UIColor.blue
    return pin
  } else {
    return nil
  }
}
Listing 3-5

Simplified annotation reuse and creation after registering annotation view with map view

This has cleaned up our code considerably and would allow us to easily add additional annotation view types if we had multiple types of markers on our map.

Setting images on annotation views

Some of your map application ideas may work fine with the built-in pin or marker annotation view classes. However, it’s likely that you will need to use the MKAnnotationView class so that you can set your own images.

We will change our code slightly to create annotation views for a dog park. The reuse identifier will be “DogPark”, and we will also have an image of a dog in a park, stored in our iOS app’s assets. This image will also be named “DogPark”. You will need to download your own image and place it into your Xcode project’s assets, so that you have something to display.

In the viewDidLoad() function, add this code:
mapView.register(MKAnnotationView.self, forAnnotationViewWithReuseIdentifier: "DogPark")
Next, change the contents of the viewFor() function . This code is simplified, as we don’t need to downcast the dequeued annotation view any more. We can also simply return the optional value from this function. The only other change we make is to set the UIImage on the annotation view, instead of changing the pin tint color. The complete changes are shown in Listing 3-6.
func mapView(_ mapView: MKMapView,
  viewFor annotation: MKAnnotation)
    -> MKAnnotationView? {
  if annotation is MKUserLocation {
    return nil
  }
  let reuseId = "DogPark"
  let view = mapView.dequeueReusableAnnotationView(
      withIdentifier: reuseId)
  view?.annotation = annotation
  view?.image = UIImage(named:"DogPark")
  return view
}
Listing 3-6

Use an image on the annotation view

After you replace the code that dequeued the reusable pin annotation views with Listing 3-6, run the project. You will see the dog park image appear in place of your pins.

Using callouts with annotations

After using a custom image for your annotation, it’s likely that you will want to display a callout when a user taps on your annotation. The canShowCallout property of the MKAnnotationView controls whether or not this is shown. When creating the view for the annotation, add the following line of code:
view?.canShowCallout = true
And then run your application. After tapping on an annotation, you will see a pop-up with the title and subtitle that you set, as shown in Figure 3-2.
../images/485790_1_En_3_Chapter/485790_1_En_3_Fig2_HTML.jpg
Figure 3-2

Using images and callouts for the annotations

As you can see, the way you display annotations on your map is extremely customizable. Try changing up the images for each annotation based on a data point, or try displaying some as images and some as pins.

Summary

In this chapter, we’ve built on the mapping fundamentals from Chapter 1 to customize the markers we display on the map. We’ve seen how to use reusable annotation views to improve performance for maps with many markers.

In the next chapter, we will discuss how to use Apple’s local search functionality to provide points of interest from their database for your map.

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

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