Adding Annotations and Overlays to Maps

In the last lesson, we explored the joys of making maps using the UIMapView and MapKit. While showing maps is great, we often want to add things to a map, such as locations and graphics. These are known as Annotations and Overlays. In this lesson we’ll make some of each. We’ll start by locating pizza restaurants in Chicago, then adding a 2 kilometer pizza delivery region for a pizza chain using overlays. Finally we’ll map out how to get from the former Marshall Field’s (now Macy’s) to the first Deep Dish pizza.

Make a New Project

I’ll quickly go through the the basic setup of a map. For more detail see the previous lesson. Start by making a New Single-view project Called MapAnnotationsOverlayDemo. Use a Universal device and Swift as the language. When it loads, we need to turn on MapKit. In the Project Properties, click on the the Capabilities tab. You will see a list of functions we can use. About halfway down the list you’ll See Maps

Turn on maps and the menu opens.

We won’t be changing anything here, but the framework will load when we need it.

Go to the storyboard. In the Object Library, find the Map Kit View object

Drag it somewhere on the story board.

Select the Map view. Click the button in the Auto Layout menu on the lower right of the storyboard. This will bring up the the pin menu. Click off Constrain to margins. Set the margins to be 0 on all sides. Change the Update Frames to Items of New Constraints. Your pin menu should look like this.

Click Add 4 Constraints. The map view takes the entire view on the story board.

With the map view still selected, look at the attribute inspector. At the top is some attributes specific to map views:

Turn off Points of Interest.They will get in the way of our app.

Open the Assistant editor. Go to the ViewController.swift file. Before we do anything else, we needto import MapKit. It is a separate framework than UIKit. Add importMapKit

import UIKit
import MapKit

Control-drag from the map view on the storyboard to the code. Add the outlet mapView. We also need to add the delegate MKMapViewDelegate. Just to keep things organized, I added a few marks. When done your ViewController code should look like this:

There’s a coordinate we’ll use as a reference point for the city of Chicago. The city, and some of the suburbs use the same grid system for addresses. There is an origin point for this grid in downtown Chicago, where we can say we are at 0,0. That is the intersection of State and Washington Streets. We’ll use it in a few places, thus I’ll use a global constant. Add this code above the class definition for ViewController just below the Global Declarations mark:

Adding a Single Annotation Pin

The most common and simplest annotation is a pin. There is a special class of annotations MKPinAnnotationView for displaying pins. This is a subclass of the more generic MKAnnotationView, which uses a custom class that adopts the MKAnnotation Protocol for its data source. Between the chicagoCoordinate and the ViewController declarations, add this class:

Any object can be an annotation object. Here we have a NSObject adopting the MKAnnotation protocol. Unlike other protocols, MKAnnotation does not have required methods, but required properties. You must declare a property coordinate. You can optionally declare title and subtitle as well. If you have other information, you can pass that along too.

The annotation must be added to mapView. Change viewDidLoad like this:

Using More Than One Pin

If you had only one annotation, this would be an acceptable way of using them. However you will likely have many annotations. Annotations work like table views, they reuse existing annotations to conserve resources. Unlike table views, they can work automatically if you want the default set of features.

A New Data Set

We’ll need a new data set of several locations. In this demo We’ll use a few deep dish pizza restaurants in Chicago. Press Command-N and make a new Cocoa Touch Class named PizzaLocation.swift, subclassing NSObject. Add the following code to the new class:

Once again we import MapKit to get the data types we need for locations. In line 3, We add the MKAnnotation protocol to the class. This time we have three properties: the required coordinate, title, and the reuse identifier for this annotation. In lines 7 – 10 we have an initializer taking a name, a latitude and longitude to populate the title and coordinate for each instance of this object.

This is the code for the annotation. Usually, you would have this class read an internal file or get annotation from a server. However, I want to keep this simple and on topic, so I’ll make a constant array for our annotations. Under this class we’ll add one more class. In this class, we’ll have one property: an array of PizzaLocations. Add this(which I seriously suggest cutting and pasting):

We added the addAnnotation counterpart addAnnotations, which takes an array of MKAnnotations instead of a single annotation.

Build and run. You get a series of pins.

Head south and you’ll find less pins. Click on the one I did and you’ll find it is Connie’s Pizza.

Using the Map Kit Delegate for Annotations

Annotations default to a red pin with a call out for the title and subtitle. If all you want to do is put a set of red pins on the map, populate the array annotation with objects adopting the MKAnnotation protocol. You’ll notice however you can’t tell what these pins are without clicking them. To customize the annotations in any way, you’ll need a method in MKMapViewDelegate. The delegate method mapview:viewForAnnotation works like tableview:cellForRowAtIndexPath works for tables. It creates the annotations you need at any given time. Add this to the ViewController class

The function mapview:viewForAnnotation has a map view and an annotation for parameters, which we return a optional MKAnnotoationView. nil means we don’t have a pin here for this annotation for some reason. MKAnnotationView is a UIView for a more flexible annotation. For convenience MapKit has a subclass of MKAnnotationView named MKPinAnnotationView which sets a pin at a annotation’s location.

We start this function with declaring a view of class MKPinAnotationView, since they are the easiest type of annotation view. I’m a bit careful about my annotations, and use guard to make sure I get the correct type of annotation, a PizzaLocation. Like table cells, annotations dequeue. For that to work properly we need an identifier, which we find in annotation.identifier. I check for an existing dequeued view, using that identifier property of PizzaLocation. If the annotation has already been created I assign it to the view. If it isn’t I can make a new view, assigning the reuse identifier to the annotation view.
As this is a delegate, don’t forget to set the delegate in viewDidLoad.

mapkit.delegate = self

Build and run. You get the same map as before…almost.

You cannot get the title in a call out if you click on the pin. Once we start making our own MKAnnotationviews, we have to specify how we want annotation view to look and behave.

Changing Pin Color

Starting in iOS9, we can make the pin color any color we wish using the pinTintColor property of MKPinAnnotation. Before we do, add this function to ViewController

Once we get the view, we set the pin color. Build and run. Now the pins are colorful.

One thought about style and user interfaces when changing the pin colors. Looking at these pins can you tell which pins are Lou Malnati’s? Without a key on the map it’s difficult. Use color apraingly and for uses that color makes sense. If I was rating these restaurants. using red, green and and yellow tints tell me which ones to go to and which ones to avoid

Multiple Classes of Annotations

You’ll see one red pin. In our code, we never specified a red pin. Why it it there? Zoom in on the location (option-drag in the simulator) and you’ll find it is the center coordinate of State and Washington streets.

Look back a the delegate methods and find this line of code:

guard let annotation = annotation as? PizzaLocation else {return nil}

This code returns nil if we cannot downcast to PizzaLocation, which we can’t with class ChicagoCenterCoordinate. For any nil annotation view, we get our default red pin. We need to change the code to downcast for ChicagoCenterCoordinate, then set properties on that pin. Change the code to this:

We can customize the appearance of the pin. The pin is a UIImage. We can add an image to the annotation in its image property. Here are two images we can use for the annotations we have

Right click each and save the images as pizza pin and crosshairs respectively.
Go to Assets.xcassets. From where you saved the images, drag the pizza pin into the assets folder, so it creates a new image.

Check the image set attributes to make sure this image set has the name pizza pin.

Do the same for the cross hairs, making sure the image set name is crosshairs.

Add two news constants to the beginning of the ViewController code. This speeds loading the image later in the delegate.

Lines 3,6,14,and 18 usesMKAnnotationView instead of MKPinAnnotationView in order to use a custom image. A pin will replace your image if you use MKPinAnnotationView In line 7, we set the image property of the annotation view to our pizza pin. Similiarly line 19 add our crosshairs. Lines 8 and 9 enable the call out with the title. Line 10 adds a view to the callout. This can be any subclass of UIView, including controls such as buttons. We make a UIImageView from the pizza pin image.

Build and run. You get the pizza image instead of the pin, with cross hairs at State and Washington streets.

Click a Pizza restaurant and you get its name and the pizza icon

Working with Overlays

In some cases we don’t need a point for an annotation but an area, line or tile. To show those on a map, we use an overlay. Overlays set up a lot like annotations. We start with a overlay object and in the MapKitDelegate method for overlays, tell the system what overlay view goes where.

Overlays can have several standard shapes.You can use circles, polygons, and polylines as built in shapes, plus the tile overlay. We’ll look at the polyline and circle for our examples.

Circle Overlays

Suppose we want to show a delivery area based on a distance from the restaurant. We can display that with a circle for the delivery area. We need an instance of the MKCircle class to do that. MKCircle has several initializers depending on your measurement system. We’re sticking to coordinates, so we’ll use the coordinate of the restaurant as our center and a distance in meters which becomes the radius of our circle. Add this code:

We do a linear search for the specific restaurant, adding the circle overlay to mapView if it is the restaurant we are looking for. We’ll also need the delegate method to make a overlay renderer, just like an annotation needed an annotation view. Add this code to ViewController:

This delegate method returns a MKOverlayRenderer, from the MKOverlay parameter. MKOverlayRenderer is a form of UIView, which means most core graphics properties and function work on it. Since we may have more than one subclass of MKOverlay in the delegate, Line 2 checks if we have an MKCircle before we make it a MKCircleRenderer. Lines 3 through 5 set the color and stroke of the circle. Line 6 returns our circle to the map for rendering. In case this was not a circle, we have a catch all return to end the code.

Add the function at 2 Kilometers for Connie’s pizza into viewDidLoad. This really isn’t their delivery area, we’re making this up for the example.

Build and run. You get the circles, but they are a bit big for our view.

Zoom out a little (use the option key to pinch on the simulator) to see the full circles.

Rendering Polylines in Overlays

Both poly lines and polygons are an array of coordinates making up a line. The difference is the polygon closes the shapes and allows filling while the the polyline doesn’t Polylines are the way we mark routes on a map.

Unlike the last lesson, the only Chicago trivia I’ve given is the address center of Chicago at State and Washington streets. Let’s make a walking path from the center of the addresses to the center of the Deep Dish pizza universe: Pizzeria Uno. There’s plenty of debate wiether it was Owner Ike Sewell or Chef Rudy Malnati who invented deep dish pizza, but it’s clear this was the restaurant that it happened. As you can tell from the pins on our map, Rudy’s son Lou (who was also a manager at UNO’s) started his own chain first in the North and northwest suburbs and then pretty much spread to most of Chicago. I didn’t include all 45 Chicago area Malnati’s locations in this data set. Lou Malnati’s #46 in Phoenix Arizona opens the same day I’m posting this.

One of the great things about Chicago is the grid system. We need to walk in one direction for a while, make a turn and then walk in the new direction. Without using the directions API, we can get there with only three coordinates. We already have two, and I’ll give you the third. Add this function to our code:

MKPolyline and MKPolygon need an array of CLLocationCoordinate2D. We make that array by taking our center coordinate, the coordinate of the traffic intersection we make our turn and the restaurant coordinates. We get a MKPolyLine object path from the coordinates and a count of the number of coordinates. You’ll notice we used &coordinates and not coordinates. This parameter takes the array as an evil (or at least dangerous) UnsafeMutablePointer. We don’t pass values to this, we share them with an &. Once we have the path, we add it to the overlay array in mapView.

The MKpolylineRenderer works much like the MKCircleRenderer. We check if overlay is the correct class. If so, we get an instance of the renderer, and set some properties of the line. In this case, we have no fill.
Add UnoDirections to viewDidload:

UnoDirections()

Build and run. You’ll see a line on the map now.

Since Uno’s is only a block east of State Street. We are losing our line under the annotations. Zoom in on the map and you can see it better.

Where to Go from Here

This is the basics of annotations and overlays. From this you can do a lot, as long as you have good coordinate data. What I’ve covered in this and the last lesson was how to make a user interface with the map. What I didn’t cover here was any of the core location stuff. I didn’t tell you how to find the location of the device, nor did I show you how to get directions. Those are lessons unto themselves.

I’m going to use maps and tables in upcoming lessons to discuss the form for large data sets, such as JSON and XML. You’ll see more about annotations and the like in upcoming lessons as we get data from external sources.

Post navigation

Subscribe to A Slice of App Pie Newsletter

If you are making your own stuff, doing your own work and trying to sell it in the market, most advice isn't enough. You want it to work. You want to sell your works to those who most are touched by them. This newsletter is for you. I am one of those people like you, creative, independent and maybe a little bit crazy to the outside world. I'll talk about writing good apps for Apple platforms, the tools and API's to get there. Along the way I will show you how to become you best creative self. I will show you how to build and create, and make some money at it too.

Get exclusive content, new post notifications, tips and more in A Slice of App Pie.

Archives

Archives

Current Published Works

Pratical Autolayout for Xcode 8

In Practical Auto Layout for Xcode 8, using simple, practical, easy to follow examples, you will learn how to master auto layout, size classes and stack views on the Xcode 8 storyboard. Using easy to follow examples, you will learn how to make universal apps quickly easily and in far less time than ever before.

Swift Swift: View Controllers

You will learn to use auto layout, size classes and new Swift implementations of view controllers. With plenty of color illustrations and code snippets, Swift Swift View Controllers will take you step by step through many easy demonstrations, teaching you the stuff you really need to know to implement any of these view controllers.