Day 60 - 100 Days of Swift

4 minute read

Project 16 (part 1)

Day 60 is the first part of the sixteenth project. It is a simple little project using MapKit to display some capitals of the world and a single fact about each of them. You get the mapView set up, you make a custom class that conforms to MKAnnotation, and you add some of those annotations to the map. You also adopt a couple of MKMapViewDelegate methods to provide a custom MKAnnotationView for the capitals and to give them a button that will display the info.

First, you make a new project, add a map view in the storyboard, constrain it, make an outlet for it, and set its delegate to be ViewController. That gives you a basic empty map that pretty much works out of the box.

Screenshot of empty map view.

Then you add a new class that inherits from NSObject and conforms to MKAnnotation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import UIKit
import MapKit

class Capital: NSObject, MKAnnotation {

    var title: String?
    var coordinate: CLLocationCoordinate2D
    var info: String?

    init(title: String,
         coordinate: CLLocationCoordinate2D,
         info: String) {

        self.title = title
        self.coordinate = coordinate
        self.info = info
    }
}

Then you make a few of those in ViewContoller.viewDidLoad:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
let londonCoordinates = CLLocationCoordinate2D(latitude: 51.507222,
                                               longitude: -0.1275)
let london = Capital(title: "London",
                     coordinate: londonCoordinates,
                     info: "Home to the 2012 Summer Olympics.")

let osloCoordinates = CLLocationCoordinate2D(latitude: 59.95,
                                             longitude: 10.75)
let oslo = Capital(title: "Oslo",
                   coordinate: osloCoordinates,
                   info: "Founded over a thousand years ago.")

let parisCoordinates = CLLocationCoordinate2D(latitude: 48.8567,
                                              longitude: 2.3508)
let paris = Capital(title: "Paris",
                    coordinate: parisCoordinates,
                    info: "Often called the City of Light.")

let romeCoordinates = CLLocationCoordinate2D(latitude: 41.9,
                                             longitude: 12.5)
let rome = Capital(title: "Rome",
                   coordinate: romeCoordinates,
                   info: "Has a whole country inside it.")

let washingtonCoordinates = CLLocationCoordinate2D(latitude: 38.895111,
                                                   longitude: -77.036667)
let washington = Capital(title: "Washington DC",
                         coordinate: washingtonCoordinates,
                         info: "Named after George himself.")

And add them to the mapView, also in viewDidLoad:

1
mapView.addAnnotations([london, oslo, paris, rome, washington])

That leads to a map that shows pins for all the capitals we added, but not much else:

Screenshots of app with default annotation views.

Then you adopt a MKMapViewDelegate method that provides the view for a given annotation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func mapView(_ mapView: MKMapView,
             viewFor annotation: MKAnnotation) -> MKAnnotationView? {
    guard annotation is Capital else { return nil }

    let identifier = "Capital"

    var annotationView = mapView
        .dequeueReusableAnnotationView(withIdentifier: identifier)

    if annotationView == nil {
        annotationView = MKPinAnnotationView(annotation: annotation,
                                             reuseIdentifier: identifier)
        annotationView?.canShowCallout = true

        let button = UIButton(type: .detailDisclosure)
        annotationView?.rightCalloutAccessoryView = button
    } else {
        annotationView?.annotation = annotation
    }

    return annotationView
}

This makes sure that the annotation is a Capital, tries to dequeue a view with that identifier, and builds a new one if it can’t dequeue one.

Then you adopt another method that is called when the callout button is tapped:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func mapView(_ mapView: MKMapView,
             annotationView view: MKAnnotationView,
             calloutAccessoryControlTapped control: UIControl) {

    guard let capital = view.annotation as? Capital else { return }

    let placeName = capital.title
    let placeInfo = capital.info

    let alertController = UIAlertController(title: placeName,
                                            message: placeInfo,
                                            preferredStyle: .alert)
    let action = UIAlertAction(title: "Ok",
                               style: .default)
    alertController.addAction(action)

    present(alertController,
            animated: true)
}

This just checks if the annotation is a Capital and if it is, it presents a UIAlertController with the information about that capital. This ends up looking like this:

Screenshots of working app with custom annotation views.

You can find my version of this project at the end of day 60 on Github here.