Introduction to MapKit



This time, we will look into how maps, locations and annotations work in iOS. Start by creating a new Single View Application project in Xcode. Go to the storyboard and drag a Map Kit View from the Object Library on top of the view controller. Make it fit the entire view and set necessary constraints. There is one more thing we need to do in the storyboard, create an outlet named mapView for our MKMapView object, and connect it to the view controller:

@IBOutlet weak var mapView: MKMapView!

In viewDidLoad() let’s create an array of dictionaries and hardcode a couple of locations into it:

let locations = [
    ["name" : "Apple Inc.",
    "latitude" : 37.33187,
    "longitude" : -122.02951,
    "mediaURL" : "http://www.apple.com"],
    ["name" : "BJ's Restaurant & Brewhouse",
    "latitude" : 37.33131,
    "longitude" : -122.03175,
    "mediaURL" : "http://www.bjsrestaurants.com"]
]

Next, we will create a MKPointAnnotation object for each dictionary in the locations array, and later we will store the object in an array:

var annotations = [MKPointAnnotation]()

Now let’s iterate through our locations, populate the annotations with data, and store them in the annotations array:

for dictionary in locations {
    let latitude = CLLocationDegrees(dictionary["latitude"] as! Double)
    let longitude = CLLocationDegrees(dictionary["longitude"] as! Double)
    let coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
    let name = dictionary["name"] as! String
    let mediaURL = dictionary["mediaURL"] as! String
    let annotation = MKPointAnnotation()
    annotation.coordinate = coordinate
    annotation.title = "\(name)"
    annotation.subtitle = mediaURL
    annotations.append(annotation)
}

Next we add the annotations to the map view:

mapView.addAnnotations(annotations)

If you run the app now, you will notice that the map loads but it shows our current location and the annotations we set are not visible (unless you are located in Apple’s headquarters). What we need to do is center our map on one of our annotations. For this to work, let’s create the following method:

func centerMapOnLocation(location: MKPointAnnotation, regionRadius: Double) {
    let coordinateRegion = MKCoordinateRegionMakeWithDistance(location.coordinate,
        regionRadius * 2.0, regionRadius * 2.0)
    mapView.setRegion(coordinateRegion, animated: true)
}

This method takes in as arguments a location (annotation) and a radius in meters of the region we want to see in the map view. Next, we call this method in viewDidLoad() right at the end of it, and we center on the Apple’s headquarters, while setting a radius of one kilometer:

centerMapOnLocation(annotations[0], regionRadius: 1000.0)

Now if you run the app again you can finally see the two annotation pins, and you can click them to see the name and URL we set for each. There is one problem with the annotations, however. Probably you noticed you cannot load the URL in a browser. In order for this to work we need to conform to the MKMapViewDelegate protocol:

class ViewController: UIViewController, MKMapViewDelegate {

Next we implement two of the delegate methods. First method configures the annotation with a small info button on the right side of each pin:

func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
    let reuseIdentifier = "pin"
    var pin = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseIdentifier) as? MKPinAnnotationView
    if pin == nil {
        pin = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseIdentifier) 
        pin!.pinColor = .Red
        pin!.canShowCallout = true
        pin!.rightCalloutAccessoryView = UIButton(type: .DetailDisclosure)
    } else {
        pin!.annotation = annotation
    }
    return pin
}

Notice the similarities between this method and the cellForRowAtIndexPath method in a table view. The second method responds when the info button is tapped:

func mapView(mapView: MKMapView, annotationView: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
    if control == annotationView.rightCalloutAccessoryView {
        let app = UIApplication.sharedApplication()
        app.openURL(NSURL(string: (annotationView.annotation!.subtitle!)!)!)
    }
}

There is one more thing we need to do before we can call these two methods. Our view controller needs to be set as the MapKit delegate. Add this line at the top of ViewDidLoad:

mapView.delegate = self

Now if you run the app and tap on the info button in any annotation, the URL should load in a browser.

alt text

There are a few interesting features that could be implemented further on. Notice that the locations we hardcoded in the array are similar to the JSON data that you can download from Parse. It would be great if we can get them from the cloud instead of hardcoding them. Another useful feature would be to implement geocoding both forward and reverse, so you can either input the latitude and longitude to find an address, or the opposite, input an address to find the latitude and longitude. You could also implement searching and displaying of nearby POI (points of interest). The possibilities are unlimited.

Until next time!