Make App Pie

Training for Developers and Artists

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

2016-05-04_05-42-52

Turn on maps and the menu opens.

2016-05-04_05-42-53

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

2016-05-04_05-47-46

Drag it somewhere on the story board.

2016-05-04_05-48-39

Select the Map view. Click the pinMenuButton  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.

2016-05-04_05-49-37

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

2016-05-04_05-51-02

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

2016-05-04_05-59-11

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 import MapKit

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:

//MARK: Global Declarations
class ViewController: UIViewController,MKMapViewDelegate {
    //MARK: Properties and Outlets
    @IBOutlet weak var mapView: MKMapView!
    //MARK: - Annotations
    //MARK: - Overlays 
    //MARK: - Map setup
    //MARK: Life Cycle
    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

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:

//MARK: Global Declarations
let chicagoCoordinate = CLLocationCoordinate2DMake(41.8832301, -87.6278121)// 0,0 Chicago street coordinates

Add the following method to ViewController to set the initial region for our app with 5 kilometers (3 miles) boundaries:

//MARK: - Map setup
func resetRegion(){
    let region = MKCoordinateRegionMakeWithDistance(chicagoCoordinate, 5000, 5000)
    mapView.setRegion(region, animated: true)
}

Add the region to viewDidLoad:

override func viewDidLoad() {
    super.viewDidLoad()
    resetRegion()
}

Build and run. You get a map of downtown Chicago.

2016-05-12_15-56-16

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:

class ChicagoCenterCoordinate: NSObject,MKAnnotation{
    var coordinate: CLLocationCoordinate2D = chicagoCoordinate
    var title: String? = "0,0 Street Numbers"
}

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:

override func viewDidLoad() {
    super.viewDidLoad()
    resetRegion()
    mapView.addAnnotation(ChicagoCenterCoordinate())
}

Build and run. You get a pin at the center coordinates:

2016-05-12_15-57-27

Tap the pin and you get a title.

2016-05-12_15-57-36

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:

import UIKit
import MapKit
class PizzaLocation: NSObject, MKAnnotation{
    var identifier = "pizza location"
    var title: String?
    var coordinate: CLLocationCoordinate2D
    init(name:String,lat:CLLocationDegrees,long:CLLocationDegrees){
        title = name
        coordinate = CLLocationCoordinate2DMake(lat, long)
    }
}

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):

class PizzaLocationList: NSObject {
    var restaurant = [PizzaLocation]()
    override init(){
        restaurant += [PizzaLocation(name:"Connie's Pizza",lat:41.84937922,long:-87.6410584  )]
        restaurant += [PizzaLocation(name:"Connie's Pizza",lat:41.90146341,long: -87.62848137 )]
        restaurant += [PizzaLocation(name:"Connie's Pizza",lat:41.85110748,long: -87.61286663 )]
        restaurant += [PizzaLocation(name:"Connie's Pizza",lat:41.89224916,long:-87.60951805  )]
        restaurant += [PizzaLocation(name:"Giordano's",lat:42.00302015,long:-87.81630768  )]
        restaurant += [PizzaLocation(name:"Giordano's",lat:41.79915575,long:-87.59028088)]
        restaurant += [PizzaLocation(name:"Giordano's",lat:41.85776469,long:-87.66138509)]
        restaurant += [PizzaLocation(name:"Giordano's",lat:41.95296188,long:-87.77541371)]
        restaurant += [PizzaLocation(name:"Pequod's",lat:41.92185084,long:-87.66451631)]
        restaurant += [PizzaLocation(name:"Pizzeria DUE",lat:41.89318499,long:-87.62661003)]
        restaurant += [PizzaLocation(name:"Pizzeria UNO",lat:41.8924923,long:-87.626859)]
        restaurant += [PizzaLocation(name:"Gino's East",lat:41.8959379,long:-87.6229284)]
        restaurant += [PizzaLocation(name:"Lou Malnati's",lat:41.95340615,long:-87.73214376)]
        restaurant += [PizzaLocation(name:"Lou Malnati's",lat:41.87169869,long:-87.62737565)]
        restaurant += [PizzaLocation(name:"Lou Malnati's",lat:41.96074325,long:-87.6835484)]
        restaurant += [PizzaLocation(name:"Lou Malnati's",lat:41.88411358,long:-87.65808167)]
        restaurant += [PizzaLocation(name:"Lou Malnati's",lat:41.85186556,long:-87.72202439)]
        restaurant += [PizzaLocation(name:"Lou Malnati's",lat:41.90239382,long:-87.62846892)]
        restaurant += [PizzaLocation(name:"Lou Malnati's",lat:41.89031406,long:-87.63388913)]
        restaurant += [PizzaLocation(name:"Lou Malnati's",lat:41.92911995,long:-87.65359186)]
        restaurant += [PizzaLocation(name:"Lou Malnati's",lat:41.9089214,long:-87.6775678)]
    }
}

I did the work of looking up all those locations. The initializer here populates the array restuarant.

Adding an Array of Annotations to the Map

Go back to ViewController. Add a constant restaurants to the class

let restaurants = PizzaLocationList().restaurant

Change viewDidLoad to this:

override func viewDidLoad() {
    super.viewDidLoad()
    resetRegion()
    mapView.addAnnotation(ChicagoCenterCoordinate())
    mapView.addAnnotations(restaurants)
}

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.

2016-05-15_09-47-11

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

2016-05-15_09-47-12

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

func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
    var view : MKPinAnnotationView
    guard let annotation = annotation as? PizzaLocation else {return nil}
    if let dequeuedView = mapView.dequeueReusableAnnotationViewWithIdentifier(annotation.identifier) as? MKPinAnnotationView {
        view = dequeuedView
    }else { //make a new view
        view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: annotation.identifier
    }
    return view
}

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.

2016-05-15_09-47-11

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

    func pinColor(name:String) -> UIColor{
        var color = UIColor.purpleColor()
        switch name{
        case "Connie's Pizza":
            color = UIColor.blueColor()
        case "Lou Malnati's":
            color = UIColor.greenColor()
        case "Giordano's":
            color = UIColor.yellowColor()
        default:
            color = UIColor.orangeColor()
        }
        return color
    }

This changes the color of the pin based on the pizza chain. Just before the return view in mapview:viewForAnnotation add the highlighted line:

    func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
        var view : MKPinAnnotationView
        guard let annotation = annotation as? PizzaLocation else {return nil}
        if let dequeuedView = mapView.dequeueReusableAnnotationViewWithIdentifier(annotation.identifier) as? MKPinAnnotationView {
             view = dequeuedView
        }else {
            view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: annotation.identifier)
        }
        view.pinTintColor = pinColor(annotation.title!)
        return view
    }

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

2016-05-15_10-14-56

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.

2016-05-13_06-56-39

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:

 func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
    if let annotation = annotation as? PizzaLocation{
        if let view = mapView.dequeueReusableAnnotationViewWithIdentifier(annotation.identifier) as? MKPinAnnotationView {
            return view
        }else{
            let view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: annotation.identifier)
            view.pinTintColor = pinColor(annotation.title!)
            return view
        }
    }else{
        if let annotation = annotation as? ChicagoCenterCoordinate{
            if let view = mapView.dequeueReusableAnnotationViewWithIdentifier("center") as? MKPinAnnotationView {
                return view
            }else {
                let view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "center")
                view.pinTintColor = UIColor.blackColor()
                return view
            }
        }
    }
    return nil
}

We test for an annotation to cast properly to ChicagoCenterCoordinate, and then make a black pin if necessary. Build and run. You now have the black pin.

 2016-05-15_09-47-14

Customizing the Annotation

There are several properties in MKAnnotationView we can use to customize the annotation view. We can get back the call out with two lines of code. Add this under the pinTintColor of black.

view.pinTintColor = UIColor.blackColor()
view.enabled = true
view.canShowCallout = true

Build and Run. Click the black pin and you get a call out.

2016-05-13_15-07-42

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

pizza pin                   crosshairs

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.

2016-05-15_09-47-15

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

2016-05-15_09-47-16

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

2016-05-15_09-47-17

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

let pizzaPin = UIImage(named: "pizza pin")
let crossHairs = UIImage(named: "crosshairs")

Change the code for mapView:viewForAnnotation: to this

func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
    if let annotation = annotation as? PizzaLocation{
        if let view = mapView.dequeueReusableAnnotationViewWithIdentifier(annotation.identifier){
            return view
        }else{
            let view = MKAnnotationView(annotation: annotation, reuseIdentifier: annotation.identifier)
            view.image = pizzaPin
            view.enabled = true
             view.canShowCallout = true
             view.leftCalloutAccessoryView = UIImageView(image: pizzaPin)
             return view
         }
     }else{
         if let annotation = annotation as? ChicagoCenterCoordinate{
             if let dequeuedView = mapView.dequeueReusableAnnotationViewWithIdentifier("center"){
                return dequeuedView
             }else {
                let view = MKAnnotationView(annotation: annotation, reuseIdentifier: "center")
                view.image = crossHairs
                view.enabled = true
                view.canShowCallout = true
                return view
            }
        }
    }
    return nil
}

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.

2016-05-15_11-43-44

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

2016-05-15_09-47-18

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:

 func deliveryOverlay(restaurantName:String, radius:CLLocationDistance){
        for restaurant in restaurants{
            if restaurant.title == restaurantName{
                let center = restaurant.coordinate
                let circle = MKCircle(centerCoordinate: center, radius: radius)
                mapView.addOverlay(circle)
            }
        }
    }

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:

func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer {
    if overlay.isKindOfClass(MKCircle){
        let circleRenderer = MKCircleRenderer(overlay: overlay)
        circleRenderer.fillColor = UIColor.blueColor().colorWithAlphaComponent(0.1)
        circleRenderer.strokeColor = UIColor.blueColor()
        circleRenderer.lineWidth = 1
        return circleRenderer
    }
        return MKOverlayRenderer(overlay: overlay)
    }

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.

override func viewDidLoad() {
        super.viewDidLoad()
        resetRegion()
        mapView.addAnnotation(ChicagoCenterCoordinate())
        mapView.addAnnotations(restaurants)
        deliveryOverlay("Connie's Pizza",radius: 2000)
        mapView.delegate = self
    }

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

2016-05-15_11-57-33

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

2016-05-15_11-57-35

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.

uno'sUnlike 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:

func UnoDirections(){
    var coordinates = [CLLocationCoordinate2D]()
    coordinates += [ChicagoCenterCoordinate().coordinate] //State and Washington
    coordinates += [CLLocationCoordinate2D(latitude: 41.8924847, longitude: -87.6280187)]
    coordinates += [restaurants[10].coordinate] //Uno's
    let path = MKPolyline(coordinates: &coordinates, count: coordinates.count)
    mapView.addOverlay(path)
}

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.

Next we make a few additions to the delegate method.

func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer {
    if overlay.isKindOfClass(MKCircle){
        let circleRenderer = MKCircleRenderer(overlay: overlay)
        circleRenderer.fillColor = UIColor.blueColor().colorWithAlphaComponent(0.1)
        circleRenderer.strokeColor = UIColor.blueColor()
        circleRenderer.lineWidth = 1
        return circleRenderer
     }    
     if overlay.isKindOfClass(MKPolyline){
        let polylineRenderer = MKPolylineRenderer(overlay: overlay)
        polylineRenderer.strokeColor = UIColor.blueColor()
        polylineRenderer.lineWidth = 2
        return polylineRenderer
    } 
    return MKOverlayRenderer(overlay: overlay)
}

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.

2016-05-15_09-47-19

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.

2016-05-15_09-47-20

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.

The Whole Code

ViewController.swift

//
//  ViewController.swift
//  MapAnnotationsOverlayDemo
//
//  Created by Steven Lipton on 5/10/16.
//  Copyright © 2016 MakeAppPie.Com. All rights reserved.
//

import UIKit
import MapKit
//MARK: Global Declarations
let chicagoCoordinate = CLLocationCoordinate2DMake(41.8832301, -87.6278121)// 0,0 Chicago street coordinates

class ChicagoCenterCoordinate: NSObject,MKAnnotation{
    var coordinate: CLLocationCoordinate2D = chicagoCoordinate
    var title: String? = "0,0 Street Numbers"
}


class ViewController: UIViewController,MKMapViewDelegate {
    //MARK: Properties and outlets
    let pizzaPin = UIImage(named: "pizza pin")
    let crossHairs = UIImage(named: "crosshairs")
    let restaurants = PizzaLocationList().restaurant
    @IBOutlet weak var mapView: MKMapView!
    
    
    //MARK: - Annotations
    /* Since the annotations get loaded in viewDidLoad, this is three iterations of the delegate mapView:viewForAnnotation: */
    
    func pinColor(name:String) -> UIColor{
        var color = UIColor.purpleColor()
        switch name{
        case "Connie's Pizza":
            color = UIColor.blueColor()
        case "Lou Malnati's":
            color = UIColor.greenColor()
        case "Giordano's":
            color = UIColor.yellowColor()
        default:
            color = UIColor.orangeColor()
        }
        return color
    }

     /* Iteration 1 -- single annotation type */
    /*
     func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
        guard let annotation = annotation as? PizzaLocation else {return nil}
        if let view = mapView.dequeueReusableAnnotationViewWithIdentifier(annotation.identifier) as? MKPinAnnotationView {
            return view
        }else {
            let view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: annotation.identifier)
            view.pinTintColor = pinColor(annotation.title!)
            return view
        }
     }
 */
    
 
    
    /* Iteration 2 -- two classes of annotation using MKPinAnnotationView */
    /*
    func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
        if let annotation = annotation as? PizzaLocation{
            if let view = mapView.dequeueReusableAnnotationViewWithIdentifier(annotation.identifier) as? MKPinAnnotationView {
                return view
            }else{
                let view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: annotation.identifier)
                view.pinTintColor = pinColor(annotation.title!)
                return view
            }
        }else{
            if let annotation = annotation as? ChicagoCenterCoordinate{
                if let view = mapView.dequeueReusableAnnotationViewWithIdentifier("center") as? MKPinAnnotationView {
                    return view
                }else {
                    let view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "center")
                    view.pinTintColor = UIColor.blackColor()
                    return view
                }
            }
        }
        return nil
    }

    */
    /* iteration 3 Using MKAnnotationView and custom images */
    func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
        
        if let annotation = annotation as? PizzaLocation{
            if let view = mapView.dequeueReusableAnnotationViewWithIdentifier(annotation.identifier){
                return view
            }else{
                let view = MKAnnotationView(annotation: annotation, reuseIdentifier: annotation.identifier)
                view.image = pizzaPin
                view.enabled = true
                view.canShowCallout = true
                view.leftCalloutAccessoryView = UIImageView(image: pizzaPin)
                return view
            }
        }else{
            if let annotation = annotation as? ChicagoCenterCoordinate{
                if let dequeuedView = mapView.dequeueReusableAnnotationViewWithIdentifier("center"){
                    return dequeuedView
                }else {
                    let view = MKAnnotationView(annotation: annotation, reuseIdentifier: "center")
                    view.image = crossHairs
                    view.enabled = true
                    view.canShowCallout = true
                    return view
                }
            }
        }
        return nil
    }
    //MARK: - Overlays 
    
    
    func deliveryOverlay(restaurantName:String, radius:CLLocationDistance){
        for restaurant in restaurants{
            if restaurant.title == restaurantName{
                let center = restaurant.coordinate
                let circle = MKCircle(centerCoordinate: center, radius: radius)
                mapView.addOverlay(circle)
            }
        }
    }
    
    func UnoDirections(){
        var coordinates = [CLLocationCoordinate2D]()
        coordinates += [ChicagoCenterCoordinate().coordinate] //State and Washington
        coordinates += [CLLocationCoordinate2D(latitude: 41.8924847, longitude: -87.6280187)]
        coordinates += [restaurants[10].coordinate] //Uno's
        let path = MKPolyline(coordinates: &coordinates, count: coordinates.count)
        mapView.addOverlay(path)
    }
    //MARK: Overlay delegate
    
    func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer {
        if overlay.isKindOfClass(MKCircle){
            let circleRenderer = MKCircleRenderer(overlay: overlay)
            circleRenderer.fillColor = UIColor.blueColor().colorWithAlphaComponent(0.1)
            circleRenderer.strokeColor = UIColor.blueColor()
            circleRenderer.lineWidth = 1
            return circleRenderer
        }
        
        if overlay.isKindOfClass(MKPolyline){
            let polylineRenderer = MKPolylineRenderer(overlay: overlay)
            polylineRenderer.strokeColor = UIColor.blueColor()
            polylineRenderer.lineWidth = 2
            return polylineRenderer
        }
        
        return MKOverlayRenderer(overlay: overlay)
    }
    //MARK: - Map setup
    func resetRegion(){
        let region = MKCoordinateRegionMakeWithDistance(chicagoCoordinate, 5000, 5000)
        mapView.setRegion(region, animated: true)
    }
    
    //MARK: Life Cycle
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        resetRegion()
        mapView.addAnnotation(ChicagoCenterCoordinate())
        mapView.addAnnotations(restaurants)
        deliveryOverlay("Connie's Pizza",radius: 2000)
        UnoDirections()
        mapView.delegate = self
    }

}

PizzaLocation.swift

//
//  PizzaLocation.swift
//  mapAnnotationsDemo
//
//  Created by Steven Lipton on 5/10/16.
//  Copyright © 2016 MakeAppPie.Com. All rights reserved.
//

import UIKit
import MapKit

class PizzaLocation: NSObject, MKAnnotation{
    var identifier = "pizza location"
    var title: String?
    var coordinate: CLLocationCoordinate2D
    init(name:String,lat:CLLocationDegrees,long:CLLocationDegrees){
        title = name
        coordinate = CLLocationCoordinate2DMake(lat, long)
    }
}


class PizzaLocationList: NSObject {
    var restaurant = [PizzaLocation]()
    override init(){
        restaurant += [PizzaLocation(name:"Connie's Pizza",lat:41.84937922,long:-87.6410584  )]
        restaurant += [PizzaLocation(name:"Connie's Pizza",lat:41.90146341,long: -87.62848137 )]
        restaurant += [PizzaLocation(name:"Connie's Pizza",lat:41.85110748,long: -87.61286663 )]
        restaurant += [PizzaLocation(name:"Connie's Pizza",lat:41.89224916,long:-87.60951805  )]
        restaurant += [PizzaLocation(name:"Giordano's",lat:42.00302015,long:-87.81630768  )]
        restaurant += [PizzaLocation(name:"Giordano's",lat:41.79915575,long:-87.59028088)]
        restaurant += [PizzaLocation(name:"Giordano's",lat:41.85776469,long:-87.66138509)]
        restaurant += [PizzaLocation(name:"Giordano's",lat:41.95296188,long:-87.77541371)]
        restaurant += [PizzaLocation(name:"Pequod's",lat:41.92185084,long:-87.66451631)]
        restaurant += [PizzaLocation(name:"Pizzeria DUE",lat:41.89318499,long:-87.62661003)]
        restaurant += [PizzaLocation(name:"Pizzeria UNO",lat:41.8924923,long:-87.626859)]
        restaurant += [PizzaLocation(name:"Gino's East",lat:41.8959379,long:-87.6229284)]
        restaurant += [PizzaLocation(name:"Lou Malnati's",lat:41.95340615,long:-87.73214376)]
        restaurant += [PizzaLocation(name:"Lou Malnati's",lat:41.87169869,long:-87.62737565)]
        restaurant += [PizzaLocation(name:"Lou Malnati's",lat:41.96074325,long:-87.6835484)]
        restaurant += [PizzaLocation(name:"Lou Malnati's",lat:41.88411358,long:-87.65808167)]
        restaurant += [PizzaLocation(name:"Lou Malnati's",lat:41.85186556,long:-87.72202439)]
        restaurant += [PizzaLocation(name:"Lou Malnati's",lat:41.90239382,long:-87.62846892)]
        restaurant += [PizzaLocation(name:"Lou Malnati's",lat:41.89031406,long:-87.63388913)]
        restaurant += [PizzaLocation(name:"Lou Malnati's",lat:41.92911995,long:-87.65359186)]
        restaurant += [PizzaLocation(name:"Lou Malnati's",lat:41.9089214,long:-87.6775678)]
    }
}


7 responses to “Adding Annotations and Overlays to Maps”

  1. Hello! thank you very much for a very useful tutorial. I need to make clusters in mapView by MapKit. Do you have got tutorial about it?

    1. I’m glad you liked it.
      Not yet, No. Something I’ll have to add to the list of things to do.

  2. This was incredibly helpful in improving my app, which relies heavily on a clean, engaging map. Thank you for sharing this example project and providing detail+images for every step!

  3. This was so helpful, thank you! It’s the best MapKit polyline tutorial on the web!

  4. I’ve used your code to build out my annotations and thank you! it worked incredibly well. One question though. I’m trying to allow my end users to add map annotations. How do I update my array after the app is already running? I’m using “.append” and can see that my array is increasing in size, but the map annotations are not showing up. Any thoughts?

    1. You need to make an annotation and then use the `addAnnotation` method. Here’s the basic idea in a method.
      “`
      func addAnnotation(coordinate:CLLocationCoordinate2D){
      let annotation = MKPointAnnotation()
      annotation.coordinate = coordinate
      mapView.addAnnotation(annotation)
      }
      “`
      To see this in action, download the code from https://github.com/MakeAppPiePublishing/Tips_02_06_TapAMap_End

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: