[Swift MapKit Tutorial Series] How to make a map-based overlays

This is the fourth part of the MapKit tutorial series, each part was covering a specific tool of the wonderful MapKit arsenal.

This tutorial purpose is to show you how to use the Circle overlays for a specific region inside your map.

Overlays are a unique way to represent a given area in the map and it comes in handy when all you want is to focus on a region with radius instead of a single pin annotated geographical point.

Without further ado, let’s overlay it!

Download the starter project to work on here.

Open up the project with Xcode and select the Main.storyboard file from the Project navigator view.

The app you gonna build here will localise some of the coldest places on earth with blue circular overlays.

As you can see on the storyboard file, the app consists of two screens, the first will embed a map view, and the second will display a list of some places you can choose from.

Here is how the final app will look like at the end of the tutorial:

map overlay demo

Let’s first set up the list in the table view, select the RegionsListController.swift file from the Project navigator view and change the class declaration to the following:

class RegionsListController: UIViewController, UITableViewDataSource, UITableViewDelegate {

This will make the class conforms to both the table view data source and delegate protocols.

Next, add the following variable declarations (just after the class name):

var regions:[String]!
var latitudes:[Double]!
var longitudes:[Double]!

The regions variable will serve as the table view model while the latitudes and longitudes arrays will hold all the places coordinates, so that when a row is selected in the list, you return the relevant coordinates to the map view.

Let’s initialise the arrays you just declared. Place the following code inside the viewDidLoad method:

 regions = ["Verkhoyansk (Russia)","Fraser, Colo (United States)","Hell (Norway)","Barrow (Alaska)","Oymyakon (Russia)"]
 latitudes = [67.550592,39.944987,63.445171,71.290556,63.464138]
 longitudes = [133.399340,-105.817232,10.905217,-156.788611,142.773727]

Cool, time to populate all the date in the table view. Copy the following code before the closing bracket of the class:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
     let cell:UITableViewCell! = tableView.dequeueReusableCellWithIdentifier("regionCell")
     cell?.textLabel!.text = regions[indexPath.row]
     return cell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
     return regions.count
}

Don’t forget to set the current class as the data source and delegate of the table view. To do so, implement the following statements inside the viewDidLoad method:

self.tableView.dataSource = self
self.tableView.delegate = self

Run the app, click the search icon and make sure the list of items are loaded correctly inside the table view.

Once you select a place from the list, we want to dismiss the table view and load the coordinates inside the map. For this, you need a way to pass the coordinates data of the place back to the map view, and one way to do so is within a protocol method.

Define the following protocol in the RegionsListController class. Place the following code just above the class name:

protocol RegionsProtocol{
    func loadOverlayForRegionWithLatitude(latitude:Double, andLongitude longitude:Double)
}

Next, declare a reference for the protocol as below:

var delegate:RegionsProtocol!

And finally, call the protocol method once a click on the table view row is detected. For this, implement the didSelectForRowAtIndexPath delegate protocol method:

func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
     self.dismissViewControllerAnimated(true, completion: nil)
     delegate.loadOverlayForRegionWithLatitude(latitudes[indexPath.row], andLongitude: longitudes[indexPath.row])
}

The code above will dismiss the current view and call the loadOverlayForRegionWithLatitude:andLongitude: method on the protocol delegate after passing the longitude and latitude informations as arguments.

The protocol delegate we are dealing with here will be the class that handle the map view, in this case, it’s corresponding to the ViewController class.

Select the ViewController.swift file from the Project navigator view to load it in the editor.

Change the class declaration as below to make it adopt to both the RegionsProtocol you just defined and the MKMapViewDelegate protocol which you will need to implement its relevant method in order to draw the map overlay.

class ViewController: UIViewController, RegionsProtocol, MKMapViewDelegate {

Next, let’s implement the only defined method for the RegionsProtocol. Implement the following code inside the class:

 func loadOverlayForRegionWithLatitude(latitude: Double, andLongitude longitude: Double) {

   //1 
   let coordinates = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
   //2
   circle = MKCircle(centerCoordinate: coordinates, radius: 200000)
   //3 
   self.mapView.setRegion(MKCoordinateRegion(center: coordinates, span: MKCoordinateSpan(latitudeDelta: 7, longitudeDelta: 7)), animated: true)
   //4
   self.mapView.addOverlay(circle)
}

Some explanation is needed here:

//1 : Here, you will use the longitude/latitude parameters to construct a geographical 2D point, then you use it as the center of an MKCircle object.

//2 : An MKCircle object represents a circular area on the map, pretty cool and convenient to what we are trying to do here. As all circles, in order to draw an MKCircle, you set its center and radius.

Note: The radius is measured in meters from the center of the MKCircle, so 200000 means a 200 km from the center.

//3 : This will focus the map on the same region as the circle center.

//4 : Finally, you added the circle to the map view, note that this will call the mapView:rendererForOverlay: method of the MKMapViewDelegate protocol, so you need to implement this method in order to draw the circle overlay effectively.

To do so, place the following code before the closing bracket of the class:

func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer {

     let circleRenderer = MKCircleRenderer(overlay: overlay)
     circleRenderer.fillColor = UIColor.blueColor().colorWithAlphaComponent(0.1)
     circleRenderer.strokeColor = UIColor.blueColor()
     circleRenderer.lineWidth = 1
     return circleRenderer
}

Each time an overlay is added to the map, the protocol method above is called to ask the delegate for a renderer object to use when drawing the specified overlay. So what you did is just constructing a circle overlay filled with a blue color and return it as the overlay renderer of the MKCircle object you defined earlier.

Don’t forget to declare the circle object as an instance property, place the following declaration just below the class name:

var circle:MKCircle!

And set the delegate of the MKMapViewDelegate protocol to the current class. Place the following statement inside the viewDidLoad method:

self.mapView.delegate = self

Let’s finish up by setting the RegionsProtocol delegate. To do so, copy the method implementation below before the closing bracket of the class:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    let regionsController = segue.destinationViewController as! RegionsListController
    regionsController.delegate = self
}

The prepareForSegue method above will run just before moving to the RegionsListController view where the RegionsProtocol is defined. This is the best place to take advantage of the destinationViewController and cast it to our RegionsListController class, and hence set its delegate property to the current class.

That’s it! Run the app and enjoy your circles overlays 🙂

As usual, you can download the final project here.

Malek
iOS developer with over than 11 years of extensive experience working on several projects with different sized startups and corporates.

7 Comments

  1. Thank you for this tutorial Malek, really well done as always.

    Is there a lot of work required to show a ‘non circular’ overlay? For example if I had 8 or 9 coordinate points that were within a town or city that I wanted to highlight to the user (a park, or a group of buildings, an industrial estate perhaps)

    Thanks again!

  2. Sure, for example you can use the MKPolygonRenderer class to draw non circular shapes, It’s quite easy to implement. You can draw as much shapes as you want, loop through your objects and provide a renderer for them. That’s it 🙂

  3. Thank you very much. Been struggling with this for a while now. So many tutorials, but none of them updated for Swift, or is too brief.

  4. Thank you for this tutorial! It gave me an idea, I have incorporated users current location and have been trying to use MKPlacemark and MKMapitem to get a pin to show instead of a region and give directions. Can you help me with this? Thanks again.

  5. Thank you for this tutorial Malek!
    The only thing I have not understood is the function override func prepareForSegue. Can you explain to me what the keyword “override” means and what the function does, exactly?

Comments are closed.