Tutorial

MapKit Beginner’s Guide: Polylines, Polygons, and Callouts


Welcome to part 2 of the MapKit tutorial series. If you read part 1 of this tutorial, you should already be familiar with the basics of MapKit. Let’s get started with some of MapKit’s more advanced features!

You can get started by downloading the starter project from GitHub. This starter project is the completed project from part 1 of this tutorial, which you should read if you haven’t done so already.

Creating Polylines

In the last part of this tutorial series, we worked with overlays in MapKit. We rendered overlays on top of the map to show users key areas that we wanted to highlight. By combining annotations and overlays, you can really easily display rich data on top of a map. In our last example, we rendered a circle.

Let’s go ahead and try something more complex. Unlike what we get with MKCircle, MapKit doesn’t provide any predefined classes for other shapes, so we have to make our own.

How about we start with the basics to create a polyline, or a line made up of multiple different points joined together? To keep things simple, we will draw a polyline between all of the cities we used in the last example. The class we use to draw polylines in MapKit is called MKPolyline, and it’s fairly easy to use. Let’s get started!

MKPolyline is a class that can be used to represent a connected sequence of line segments. You can create a polyline by constructing a MKPolyline object with a series of end-points like this:

map-route-3

First, head to ViewController.swift, where all of the magic related to our map is taking place. Then, move to the addAnnotations() method, where we are adding circular overlays to mapView. Go ahead and add the following to the end of the method:

These 3 lines of code do the following:

  1. Extract the coordinates of each of the places contained in places.
  2. Create a polyline from the places extracted in step 1.
  3. Add the polyline to the map view.

Here please take a special note of the way we pass in the coordinates parameter to the initializer for MKPolyline.

We need to provide an UnsafePointer<CLLocationCoordinate2D> instead of [CLLocationCoordinate2D]. Since CLLocationCoordinate2D is an Objective C structure and not an NSObject subclass, regular NSArray instances cannot handle it. We cannot provide our coordinates as an array directly because Swift is unable to bridge this array to its Objective C type, as there is no Objective C type for an array of C structs. Without getting into low level language topics such as bridging and memory management, it’s necessary to know that an array you pass to a function as an UnsafePointer must be declared with var, not let. You must also prefix the array’s name with an & to indicate to Swift that you wish to provide that parameter as an inout type. Additionally, you need to provide the count of the array you pass in so that Objective C can determine how many objects have been provided.

Your addAnnotations() function should look like this:

Now that we have provided the polyline overlay to MapKit, we need to tell MapKit how to render the overlay. Remember how we provided an MKCircleRenderer when we wanted to render a circle? We need to provide a renderer for polylines as well, but it’s suprisingly easy.

Let’s navigate to mapView(_: rendererFor:) in ViewController.swift and get started. The first thing we need to do is modify this method so that it can check what kind of overlay is being rendered and respond appropriately. We can do this using Swift’s is operator, which will allow us to check if one object is a subclass of another, like so:

As you can see, Swift’s type checking syntax is pretty awesome, and it lets us easily check the type of an object. Let’s use this principle and apply it to our MapKit scenario:

Easy, right? We just check what kind of overlay we’re rendering and act accordingly. MKPolylineRenderer is self explanatory for those who have worked with MKCircle renderer, as the setup process is identical. Build and run the app, and voila! You should see a blue polyline that runs between New York, LA, and San Francisco.

map-polyline

Drawing Polygons

Now that we’ve drawn a polyline, let’s move on and see how to draw a polygon. If you don’t remember from Math class, a polygon is a closed shape made up of multiple points, with straight lines connecting each of the points. MapKit also provides us with a class for rendering polygons. It’s called MKPolygon.

As you may have inferred already, it’s pretty easy to use. Let’s go back to addAnnotations() and add our polygon. But before we do this, let’s clean up our code a little bit. Currently, our addAnnotations() function is responsible for adding annotations and a polyline. This doesn’t hold true to its name, so let’s make a new method called addPolyline() and move our polyline code from addAnnotations() to our new function:

Let’s also create an empty method called addPolygon():

Finally, modify the viewDidLoad() method to call all of our overlay methods. Your viewDidLoad() method should look like this:

Okay, it is time to implement the addPolygon() method. To draw a polygon on map, you just need a few lines of code. Insert the following code in the method:

This is really similar to adding a polyline, and that’s intentional. Well-designed frameworks provide developers with consistency and predictability. MapKit’s mature API gives developers both of those things. Note that we still have the little oddity that we did when we worked with MKPolyline: we need to provide our [CLLocationCoordinate2D] as an UnsafePointer. Just like before, you need to declare your array with var and prefix its name with an & when passing it to the function.

Great! We just need to provide MapKit with a renderer like we did for MKCircle and MKPolyline. Head to mapView(_: rendererFor:) in ViewController.swift and add a third branch to the if statement we were using before:

Now we can check if we’re rendering a polygon and respond appropriately. As we did before, we’re going to set up a renderer and return it so that MapKit knows how to display our polygon. Add the following code to the if branch:

Easy, right? We set up another renderer just like we did with our other shapes and return it. Your mapView(_: rendererFor:) should look like this:

Now, build and run. Let’s take a look. You should see a polygon between the 3 cities shown on the map.

map-polygon

You should notice that the overlays we have provided scale proportionately, depending on the scale of the map. This makes adding overlays superior to adding a subview to MKMapView because overlays scale and translate automatically, but regular views would not.

Adding Callouts

So far, we’ve learned how to display annotations, circles, polylines, and polygons. Now, let’s revisit annotations and make ours better. What if someone using our app doesn’t know which city is which? For example, what if someone does not know which of the 3 cities is New York?

We should provide a way for them to check. We can do this with callouts, which are little bubbles that appear when annotations are tapped. Currently, tapping an annotation in our app does nothing. But we can make it work by showing a callout bubble.

Go to mapView(_: viewFor:) in ViewController.swift and update the if branch of our code that returns an annotation view for our custom annotations. We need to tell MapKit that the annotation views we’re providing can show callouts. Unsurprisingly, this is easy. Before return annotationView, add a line of code that reads annotationView.canShowCallout = true, so that mapView(_: viewFor:) reads like this:

Easy, right? Let’s build and run and see what happens. Tap on an annotation view and observe the callout that appears.

map-callouts

Now, our users can easily check what city they’re looking at, just by tapping. Let’s expand this ability a little bit further by allowing our users to view more detail within a callout. First, we need to add buttons to our callout. This is easy to do. Modify mapView(_: viewFor:):

Now our callout will display a detail disclosure button when it’s clicked. We can respond to this button being clicked with a MKMapViewDelegate function called mapView(_: annotationView: calloutAccessoryControlTapped:).

Let’s implement this function in the ViewController.swift:

This method will be called whenever a user taps on a button in a callout. It will retrieve the name of the city that belongs to that callout and display it in the alert. Give it a try. Build and run, tap a city, and then select a callout. You should see a message in the console that prints the name of the city the user selected.

map-callout-response

Wrapping up

I hope you enjoyed this tutorial! Even more importantly, I hoped you gained some valuable knowledge that you can use in your own apps. If you have any questions or comments, feel free to comment them below. Good luck!

For reference, you can download the complete Xcode project on GitHub.

iOS
Creating a SwiftUI TextView Using UIViewRepresentable
iOS
Realistic Rendering of 3D Photogrammetry Model in ARKit
macOS
Getting Started with macOS Programming
Shares