Overlay Images and Overlay Views with MapKit Tutorial

Cesare Rocchi
Intro

Learn how to add an overlay image using MapKit!

Update 7/13/2014: Updated for iOS 7 and Xcode 5 by Cesare Rocchi. Original version by Chris Wagner.

It’s quite easy to add a map into your app using MapKit. But what if you want to decorate or customize the map provided by Apple with your own annotations and images?

Luckily, Apple provides and easy way to do this with Custom Overlay Views.

In this tutorial, you create an app for the Six Flags Magic Mountain amusement park. If you’re a roller coaster fan in the LA area, you’ll be sure to appreciate this app :]

Just think about what a visitor to the park would be interested in. Things like the location of specific attractions, routes to the various rides and roller coasters, and the location of characters around the park. These are perfect candidates for custom overlay images – and that’s exactly what you’ll be adding in this tutorial.

Keep reading to add some excitement to these vanilla maps!

Note: You have two options for how to proceed with this tutorial based on your experience level:

  1. Already familiar with MapKit? If you’re already familiar with MapKit and want to dive right into the Overlay Images code, you can skip (or scan) ahead to the “What a View” section – we have a starter project waiting for you there.
  2. New to MapKit? If you are new to MapKit, keep reading and I’ll walk you through adding map into your app from the very beginning!


Apple Maps vs Google Maps

Apple Maps no longer uses Google Maps!

Before you start coding, I feel like I should say a few words about the Apple Maps vs Google Maps controversy.

Apple has provided a stock Maps app since the inception of iOS, which was originally backed by the Google Maps API. That all changed in iOS 6, when Apple broke their ties with Google and released their own Maps app powered by their own backend.

This has been a hot topic for bloggers, the media, users and possibly even your mom. Some will tell you that Apple has done an incredible job and made the right choice dropping Google as their mapping provider. Others will tell you it was one of the worst decisions Apple has made since the iPhone’s inception in 2007.

The bottom line is, if you’re using MapKit you are now using Apple Maps. If you’ve used MapKit in the past, you’ll be happy to know that the API is exactly the same between the two versions!

And regardless of your position, there is always room for more information on a Map! So, in this tutorial you will learn how to take Apple’s popular (whether it be infamous or famous) maps and add your own pertinent information.

Getting Started

To get started, download the starter project which provides you with a basic application for both the iPhone and the iPad, with some rudimentary navigation included – but no maps yet!

The interface provided in the starter app contains a UISegmentedControl to switch between the different map types you will implement, and an action button which presents a table of options to control which map features should be displayed. You select or deselect options by tapping them, and tapping the Done button will dismiss the options list.

The class PVMapOptionsViewController drives the options view, which defines an important enum that you’ll use later on. The rest of the code in this class is outside the scope of this tutorial. However, if you need more information on UITableView, check out one of the many UITableView tutorials to quickly get up to speed.

Open up the starter project in Xcode, and build and run. I bet you didn’t expect that so soon in the tutorial! You’ll see the following:

Park view app screenshots

As advertised, the starter app is pretty basic! If your map application is going to do anything useful, then it’s going to need a map, for starters!

Do You Know the Way To San Jose? – Adding A MapView

To add a MapView to your app, start by opening MainStoryboard_iPhone.storyboard. Select the Park Map View Controller scene, and drop a Map View object on the view. Position the MapView so it fills the entire view, as shown below:

Adding map sto the storyboard

Now open up MainStoryboard_iPad.storyboard, add a MapView as above, and again adjust it so it fills the entire view.

You may be tempted to build and run the app at this point to see what it looks like with your map added; however, if you do, you will see the following exception and your app will crash:

*** Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: 'Could not instantiate class named MKMapView'

This is because you haven’t yet linked the MapKit.framework to your target!

To link it up, select the Park View project in the Project Navigator, then ensure the Park View target is selected. Next, navigate to the Build Phases tab and click the + button under the Link Library With Binary section, as shown below:

Search for MapKit in the popup window, select it, and click Add to add it to your project, as seen in the screenshot below:

MapKit

Build and run to check out your snazzy new map! It should look like the screenshot below:

RunAfterAddingMapView

As you can see, it doesn’t take much work to add a map to your app.

As cool as it is to have a map in your application, it would be even cooler if you could actually do something with the map! :] The next section describes how to get your MapView set up to interact with your app.

The Long and Winding Road – Wiring Up Your MapView

To do anything with a MapView, you need to do two things – associate it with an outlet, and register the view controller as the MapView’s delegate.

But first things first – you need to import the MapKit header file. Open PVParkMapViewController.h and add this to the top of the file:

#import <MapKit/MapKit.h>

Next, open MainStoryboard_iPhone.storyboard and make sure your Assistant Editor is open with PVParkMapViewController.h visible. Then control-drag from the map view to below the first property, as shown below:

Add MapView Outlet

In the popup that appears, name the outlet mapView, and click Connect.

Now you need to set the delegate for your MapView. To do this, right-click on the MapView object to open the context menu, then drag the delegate outlet to Park Map View Controller, as shown below:

Set MapView Delegate

Now perform the same steps for your iPad storyboard — connect the MapView to the mapView outlet (but this time drag on top of the existing outlet rather than creating a new one), and make the view controller the MapView’s delegate.

Now that you have finished wiring your outlets, you need to modify the PVParkMapViewController header’s interface declaration to register that it conforms to the MKMapViewDelegate protocol.

Finally modify the interface declaration in PVParkMapViewController.h as follows:

@interface PVParkMapViewController : UIViewController <MKMapViewDelegate>

That’s it for setting your outlets, delegates, and controllers. Now you can start adding some interactions to your map!

I’ve Been There, You Can’t Get There from Here – Interacting With MKMapView

Although the default view of the map looks nice, it’s a little too broad to be of use since the user is interested in only the theme park, not the entire continent! It would probably make more sense to center the map view on the park when the app is launched.

There are many ways to get position information for a specific location; you could fetch it from a web service, or you could simply package it and ship it with the app itself.

To keep things simple, you will include the location information for the park with your app in this tutorial. Download the resources for this project, which contains a file named MagicMountain.plist with the park information.

The contents of MagicMountain.plist are listed below:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>midCoord</key>
        <string>{34.4248,-118.5971}</string>
        <key>overlayTopLeftCoord</key>
        <string>{34.4311,-118.6012}</string>
        <key>overlayTopRightCoord</key>
        <string>{34.4311,-118.5912}</string>
        <key>overlayBottomLeftCoord</key>
        <string>{34.4194,-118.6012}</string>
        <key>boundary</key>
        <array>
                <string>{34.4313,-118.59890}</string>
                <string>{34.4274,-118.60246}</string>
                <string>{34.4268,-118.60181}</string>
                <string>{34.4202,-118.6004}</string>
                <string>{34.42013,-118.59239}</string>
                <string>{34.42049,-118.59051}</string>
                <string>{34.42305,-118.59276}</string>
                <string>{34.42557,-118.59289}</string>
                <string>{34.42739,-118.59171}</string>
        </array>
</dict>
</plist>

This file contains more information than you need right now to center the map on the park; specifically, it contains boundary information of the park which you’ll use a bit later.

All information in this file is provided in the form of latitude/longitude coordinates.

Add this file to your project by dragging it to the Park Information group and choosing to copy it to the project.

Now that you have some geographical information about the park, you should model it into a first-class Objective-C object in order to work with it in your app.

Select the Models group selected, and choose File > New > File… > Objective-C Class under Cocoa Touch. Name your new class PVPark, and make it a subclass of NSObject.

Once the new class is created, add the following and initializer method and properties to PVPark.h:

#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
 
@interface PVPark : NSObject
 
@property (nonatomic, readonly) CLLocationCoordinate2D *boundary;
@property (nonatomic, readonly) NSInteger boundaryPointsCount;
 
@property (nonatomic, readonly) CLLocationCoordinate2D midCoordinate;
@property (nonatomic, readonly) CLLocationCoordinate2D overlayTopLeftCoordinate;
@property (nonatomic, readonly) CLLocationCoordinate2D overlayTopRightCoordinate;
@property (nonatomic, readonly) CLLocationCoordinate2D overlayBottomLeftCoordinate;
@property (nonatomic, readonly) CLLocationCoordinate2D overlayBottomRightCoordinate;
 
@property (nonatomic, readonly) MKMapRect overlayBoundingMapRect;
 
@property (nonatomic, strong) NSString *name;
 
- (instancetype)initWithFilename:(NSString *)filename;
 
@end

Most of these properties should look familiar, as they were referenced in the plist file above.

Also note the init method initWithFileName, which will allow you to pass the filename of the plist file containing the coordinate information required to initialize this object.

Note:
You may have noticed that you’re returning instancetype rather than id from your init method. This is a relatively new best-practice based on recent additions to the LLVM complier. You can read more about this practice over on NSHipster.

Now on to the implementation of PVPark.m. Here you’ll add two methods. The first one is initWithFileName, which reads all of the information from the plist file into the defined properties. This will be pretty straightforward if you have done any file I/O with property lists.

Add the following code to PVPark.m:

- (instancetype)initWithFilename:(NSString *)filename {
    self = [super init];
    if (self) {
        NSString *filePath = [[NSBundle mainBundle] pathForResource:filename ofType:@"plist"];
        NSDictionary *properties = [NSDictionary dictionaryWithContentsOfFile:filePath];
 
        CGPoint midPoint = CGPointFromString(properties[@"midCoord"]);
        _midCoordinate = CLLocationCoordinate2DMake(midPoint.x, midPoint.y);
 
        CGPoint overlayTopLeftPoint = CGPointFromString(properties[@"overlayTopLeftCoord"]);
        _overlayTopLeftCoordinate = CLLocationCoordinate2DMake(overlayTopLeftPoint.x, overlayTopLeftPoint.y);
 
        CGPoint overlayTopRightPoint = CGPointFromString(properties[@"overlayTopRightCoord"]);
        _overlayTopRightCoordinate = CLLocationCoordinate2DMake(overlayTopRightPoint.x, overlayTopRightPoint.y);
 
        CGPoint overlayBottomLeftPoint = CGPointFromString(properties[@"overlayBottomLeftCoord"]);
        _overlayBottomLeftCoordinate = CLLocationCoordinate2DMake(overlayBottomLeftPoint.x, overlayBottomLeftPoint.y);
 
        NSArray *boundaryPoints = properties[@"boundary"];
 
        _boundaryPointsCount = boundaryPoints.count;
 
        _boundary = malloc(sizeof(CLLocationCoordinate2D)*_boundaryPointsCount);
 
        for(int i = 0; i < _boundaryPointsCount; i++) {
            CGPoint p = CGPointFromString(boundaryPoints[i]);
            _boundary[i] = CLLocationCoordinate2DMake(p.x,p.y);
        }
    }
 
    return self;
}

In the code above, CLLocationCoordinate2DMake() is used to make a CLLocationCoordinate2D structure using latitude and longitude coordinates. CLLocationCoordinate2D structures are used throughout the MapKit APIs for representing geographical locations.

This initialization method also creates a CLLocationCoordinate2D array, and sets the pointer of that array to _boundary. This will be important later when you will be required to pass a pointer to an array of CLLocationCoordinate2D structures.

One property you may have noticed was missing from the property list file is overlayBottomRightCoordinate. The other three corners (top right, top left, and bottom left) were provided, but not the bottom right one. Why?

The reason is that you can calculate this final corner of the square based on the other three points — it would be redundant to include this information when it can be calculated.

Add the following code to PVPark.m in order to implement the calculated bottom right coordinate:

- (CLLocationCoordinate2D)overlayBottomRightCoordinate {
    return CLLocationCoordinate2DMake(self.overlayBottomLeftCoordinate.latitude, self.overlayTopRightCoordinate.longitude);
}

This method generates the bottom right coordinate using the bottom left and top right coordinates, and acts as our getter method.

Finally, you’ll need a method to create a bounding box based on the coordinates read in above.

Add the following code to PVPark.m:

- (MKMapRect)overlayBoundingMapRect {
 
    MKMapPoint topLeft = MKMapPointForCoordinate(self.overlayTopLeftCoordinate);
    MKMapPoint topRight = MKMapPointForCoordinate(self.overlayTopRightCoordinate);
    MKMapPoint bottomLeft = MKMapPointForCoordinate(self.overlayBottomLeftCoordinate);
 
    return MKMapRectMake(topLeft.x,
                  topLeft.y,
                  fabs(topLeft.x - topRight.x),
                  fabs(topLeft.y - bottomLeft.y));
}

This method generates a MKMapRect which is a bounding rectangle for the park’s boundaries. It’s really just a rectangle that defines how big the park is, based on the provided coordinates, and is centered on the midpoint of the park.

Now it’s time to put this new class to use. Update PVParkMapViewController.m to import PVPark.h and add a park property to the class extension:

#import "PVPark.h"
 
@interface PVParkMapViewController ()
 
@property (nonatomic, strong) PVPark *park;
@property (nonatomic, strong) NSMutableArray *selectedOptions;
 
@end

Then change viewDidLoad as follows:

- (void)viewDidLoad
{
    [super viewDidLoad];
 
    self.selectedOptions = [NSMutableArray array];
    self.park = [[PVPark alloc] initWithFilename:@"MagicMountain"];
 
 
    CLLocationDegrees latDelta = self.park.overlayTopLeftCoordinate.latitude - self.park.overlayBottomRightCoordinate.latitude;
 
    // think of a span as a tv size, measure from one corner to another
    MKCoordinateSpan span = MKCoordinateSpanMake(fabsf(latDelta), 0.0);
 
    MKCoordinateRegion region = MKCoordinateRegionMake(self.park.midCoordinate, span);
 
    self.mapView.region = region;    
}

The code above initializes the park property using the MagicMountain property list. Next, the code creates a latitude delta, which is the distance from the top left coordinate of the park’s property to the bottom right coordinate of the park’s property.

The latitude delta is then used to generate an MKCoordinateSpan structure which defines the area spanned by a map region.

MKCoordinateSpan is then used along with the park’s midCoordinate property (which is just the midpoint of the park’s bounding rectangle) to create an MKCoordinateRegion. This MKCoordinateRegion structure is then used to position the map in the map view using the region property.

Build and run your app. You can see that the map is centered right on the Six Flags Magic Mountain park, just like in the image below:

Centered On Park

Okay! Now the map is centered on the park, which is great. But the display doesn’t look terribly exciting. It’s just a big beige blank spot with a few streets on the edges.

If you’ve ever played with the Maps app, you know that the satellite imagery looks pretty cool. You can easily leverage the same satellite data in your app to dress it up a little!

I’ve Been Everywhere, Man – Switching The Map Type

In PVParkMapViewController.m, you will find a method at the bottom that looks like the following:

- (IBAction)mapTypeChanged:(id)sender {
    // TODO: Implement
}

Hmm, that’s a pretty ominous-sounding TODO comment in there! :]

Fortunately, the starter project has much of what you’ll need to flesh out this method. Did you note the UISegmentedControl sitting above the map view that seems to be doing a whole lot of nothing?

That UISegmentedControl is actually calling mapTypeChanged, but as you can see above, the method does nothing — yet!

Add the following code to mapTypeChanged method:

- (IBAction)mapTypeChanged:(id)sender {
    switch (self.mapTypeSegmentedControl.selectedSegmentIndex) {
        case 0:
            self.mapView.mapType = MKMapTypeStandard;
            break;
        case 1:
            self.mapView.mapType = MKMapTypeHybrid;
            break;
        case 2:
            self.mapView.mapType = MKMapTypeSatellite;
            break;
        default:
            break;
    }
}

Believe it or not, adding standard, satellite, and hybrid map types to your app is as simple as a switch statement on mapTypeSegmentedControl.selectedSegmentIndex, as seen in the code above! Wasn’t that easy?

Build and run your app. Using the UISegmentedControl at the top of the screen, you should be able to flip through the map types, as seen below:

Park Satellite View

Even though the satellite view still is much better than the standard map view, it’s still not very useful to your park visitors. There’s nothing labelled — how will your users find anything in the park?

One obvious way is to drop a UIView on top of the MapView, but you can take it a step further and instead leverage the magic of MKOverlayRenderer to do a lot of the work for you!

What a View – All About Overlay Views

Note: If you’re skipping ahead from earlier in this tutorial, you can pick up at this point with this starter project. Also, you should download the resources for this project which you’ll be adding in as you go.

Before you start creating your own views, let’s first talk about the classes that makes this all possible – MKOverlay and MKOverlayRenderer.

A MKOverlay is how you tell MapKit where you want the overlays drawn. There are three steps:

  1. Create your own custom class that implements the MKOverlay protocol, which has two required properties: coordinate and boundingMapRect. These two properties define where the overlay resides on the map, as well as its size.
  2. Create some instances of this class for every area you want to display an overlay for. For example, in this app, you might create one instance for a rollercoaster overlay, and one for a restaurant overlay.
  3. Finally, add the overlays to your Map View by calling this code:
[self.mapView addOverlay:overlay];

Now the MapView knows where it’s supposed to display overlays – but how does it know what to display in each region?

Enter MKOverlayRenderer. You create a subclass of this to set up what you want to display in each spot. For example, in this app you’ll just draw an image of the rollercoaster or restaurant.

A MKOverlayRenderer is really just a UIView in disguise, as it inherits from UIView. However, MKOverlayRenderer is a special kind of object that you don’t add directly to the MKMapView. This is an object the MapKit framework expects you to provide. After you give it to MapKit, it will render it as an overlay on top of the map.

Remember how a MapView has a delegate – and you set it to your view controller in this tutorial? Well, there’s a delegate method you implement to return an overlay view:

- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay

This method is invoked when the map view realizes there is an MKOverlay object in the region that the map view’s view-port is displaying.

To sum it up, you don’t add MKOverlayRenderer objects directly to the map view; rather, you tell the map about MKOverlays to to display and return them when requested in the delegate method.

Now that you’ve covered the theory, it’s time to put those concepts to use!

Put Yourself on the Map – Adding Your Own Information

As you saw earlier, the satellite view still doesn’t provide enough information about the park. Your task is to create an object that represents an overlay for the entire park to dress it up a little.

Select the Overlays group and create a new class that derives from NSObject named PVParkMapOverlay. Then replace PVParkMapOverlay.h with the following:

#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
 
@class PVPark;
 
@interface PVParkMapOverlay : NSObject <MKOverlay>
 
- (instancetype)initWithPark:(PVPark *)park;
 
@end

In the code above, you import the MapKit header, add the PVPark forward declaration, and then tell the compiler that this class conforms to the MKOverlay protocol. Finally, you define the method initWithPark.

Next, replace PVParkMapOverlay.m with the following:

#import "PVParkMapOverlay.h"
#import "PVPark.h"
 
@implementation PVParkMapOverlay
 
@synthesize coordinate;
@synthesize boundingMapRect;
 
- (instancetype)initWithPark:(PVPark *)park {
    self = [super init];
    if (self) {
        boundingMapRect = park.overlayBoundingMapRect;
        coordinate = park.midCoordinate;
    }
 
    return self;
}
 
@end

Above, you import the PVPark header. Then, since you are implementing the coordinate and boundingMapRect protocols that define properties, you must explicitly @synthesize them. Then implement the initWithPark method. This method simply takes the properties from the passed PVPark object, and sets them to the corresponding MKOverlay properties.

Now you need to create a view derived from the MKOverlayRenderer class.

Create a new class in the Overlays group called PVParkMapOverlayView that is a subclass of MKOverlayRenderer .

Add the following code to PVParkMapOverlayView.h, which defines a single method:

#import <MapKit/MapKit.h>
 
@interface PVParkMapOverlayView : MKOverlayRenderer
 
- (instancetype)initWithOverlay:(id<MKOverlay>)overlay overlayImage:(UIImage *)overlayImage;
 
@end

The implementation of PVParkMapOverlayView contains two methods, as well as a UIImage property in the class extension.

Next add the following code to PVParkMapOverlayView.m:

#import "PVParkMapOverlayView.h"
 
@interface PVParkMapOverlayView ()
 
@property (nonatomic, strong) UIImage *overlayImage;
 
@end
 
@implementation PVParkMapOverlayView
 
- (instancetype)initWithOverlay:(id<MKOverlay>)overlay overlayImage:(UIImage *)overlayImage {
    self = [super initWithOverlay:overlay];
    if (self) {
        _overlayImage = overlayImage;
    }
 
    return self;
}
 
- (void)drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale inContext:(CGContextRef)context {
    CGImageRef imageReference = self.overlayImage.CGImage;
 
    MKMapRect theMapRect = self.overlay.boundingMapRect;
    CGRect theRect = [self rectForMapRect:theMapRect];
 
    CGContextScaleCTM(context, 1.0, -1.0);
    CGContextTranslateCTM(context, 0.0, -theRect.size.height);
    CGContextDrawImage(context, theRect, imageReference);
}
 
@end

Okay, here’s a quick review of the code above.

initWithOverlay:overlayImage effectively overrides the base method initWithOverlay by providing a second argument overlayImage. The passed image is stored in the class extension property to be referenced in the next method,

- (void)drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale inContext:(CGContextRef)context

This method is the real meat of this class; it defines how this view is rendered when given a specific MKMapRect, MKZoomScale, and the CGContextRef of the graphic context, with the intent to draw the overlay image onto the context at the appropriate scale.

Details on CoreGraphics drawing is quite far out of scope for this tutorial. However, you can see that the code above uses the passed MKMapRect to get a CGRect, in order to determine the location to draw the CGImageRef of the UIImage on the provided context. If you want to learn more about Core Graphics, check out our Core Graphics tutorial series.

Okay! Now that you have both an MKOverlay and MKOverlayRenderer, you can add them to your map view.

In PVParkMapViewController.m import both new classes:

#import "PVParkMapOverlayView.h"
#import "PVParkMapOverlay.h"

Next, add the code below to define a new method for adding an MKOverlay to the map view:

- (void)addOverlay {
    PVParkMapOverlay *overlay = [[PVParkMapOverlay alloc] initWithPark:self.park];
    [self.mapView addOverlay:overlay];
}

addOverlay should be called in loadSelectedOptions if the user has opted to show the Map Overlay.

Update loadSelectedOptions with the following code:

- (void)loadSelectedOptions {
    [self.mapView removeAnnotations:self.mapView.annotations];
    [self.mapView removeOverlays:self.mapView.overlays];
    for (NSNumber *option in self.selectedOptions) {
        switch ([option integerValue]) {
            case PVMapOverlay:
                [self addOverlay];
                break;
            default:
                break;
        }
    }
}

loadSelectedOptions is called every time the user dismisses the options selection view; it determines which options were selected and calls the appropriate methods to render those selections on the map view.

loadSelectedOptions also removes any annotations and overlays that may be present so that you don’t end up with duplicate renderings. This is not necessarily efficient, but it is a simple approach for the purposes of this tutorial.

To implement the delegate method, add the code below (still in PVParkMapViewController.m):

- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay {
    if ([overlay isKindOfClass:PVParkMapOverlay.class]) {
        UIImage *magicMountainImage = [UIImage imageNamed:@"overlay_park"];
        PVParkMapOverlayView *overlayView = [[PVParkMapOverlayView alloc] initWithOverlay:overlay overlayImage:magicMountainImage];
 
        return overlayView;
    }
 
    return nil;
}

When the MKOverlay is determined to be in view, the map view will call on PVParkMapViewController as the delegate to invoke this method. It expects that an MKOverlayRenderer will be returned for the matching MKOverlay.

In this case, you check to see if the overlay is of the class type PVParkMapOverlay; if so, the overlay image is loaded, a PVParkMapOverlayView instance is created with the overlay image, and then is returned to the caller.

There’s one little piece missing, though — where does that suspicious little overlay_park come from?

That’s a PNG file that was created to overlay the map view for the defined boundary of the park. The overlay_park image (from the resources for this tutorial) looks like this:

Add both the non-retina and retina images to your project under the Images group.

Build and run, choose the Map Overlay option, and voila! There’s the park overlay drawn on top of your map, just like in the screenshot below:

Map Overlay

Zoom in, zoom out, and move around as much as you want — the overlay scales and moves as you would expect. Cool!

If You Liked It Then You Should Have Put a Pin On It – Annotations

If you’ve ever searched for a location in the Maps app, then you’ve seen those colored pins that appear on the map. These are known as annotations, which are created with MKAnnotationView. You can use annotations in your own app — and you can use any image you want, not just pins!

Annotations will be useful in your app to help point out specific attractions to the park visitors. Annotation objects work similarly to MKOverlay and MKOverlayRenderer, but instead you will be working with MKAnnotation and MKAnnotationView.

Create a new class in the Annotations group called PVAttractionAnnotation as a subclass of NSObject.

Then replace PVAttractionAnnotation.h with the following:

#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
 
typedef NS_ENUM(NSInteger, PVAttractionType) {
    PVAttractionDefault = 0,
    PVAttractionRide,
    PVAttractionFood,
    PVAttractionFirstAid
};
 
@interface PVAttractionAnnotation : NSObject <MKAnnotation>
 
@property (nonatomic) CLLocationCoordinate2D coordinate;
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *subtitle;
@property (nonatomic) PVAttractionType type;
 
@end

Here you first import MapKit, then you define an enum for PVAttractionType. This enum lists four types of annotations: rides, foods, first aid, and default.

Next you declare that this class conforms to the MKAnnotation Protocol. Much like MKOverlay, MKAnnotation has a required coordinate property. Finally, you define a handful of properties specific to this implementation.

Okay, now you can move on to the actual implementation of PVAttractionAnnotation.

Modify PVAttractionAnnotation.m as below:

#import "PVAttractionAnnotation.h"
 
@implementation PVAttractionAnnotation
 
@end

This is probably the simplest implementation in this whole tutorial! Nothing specific needs to be implemented here; you just need to keep track of some specific properties that are defined on the header. That’s it!

Now you need to create a specific instance of MKAnnotation to use for your annotations.

Create another class called PVAttractionAnnotationView under the Annotations group as a subclass of MKAnnotationView. The interface is stock — there is nothing that needs to be added here.

Replace PVAttractionAnnotationView.h with the following:

#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
 
@interface PVAttractionAnnotationView : MKAnnotationView
 
@end

To flesh out the implementation, add the code below to PVAttractionAnnotationView.m:

#import "PVAttractionAnnotationView.h"
#import "PVAttractionAnnotation.h"
 
@implementation PVAttractionAnnotationView
 
- (id)initWithAnnotation:(id<MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier {
    self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];
    if (self) {
        PVAttractionAnnotation *attractionAnnotation = self.annotation;
        switch (attractionAnnotation.type) {
            case PVAttractionFirstAid:
                self.image = [UIImage imageNamed:@"firstaid"];
                break;
            case PVAttractionFood:
                self.image = [UIImage imageNamed:@"food"];
                break;
            case PVAttractionRide:
                self.image = [UIImage imageNamed:@"ride"];
                break;
            default:
                self.image = [UIImage imageNamed:@"star"];
                break;
        }
    }
 
    return self;
}
 
@end

Here you override initWithAnnotation:reuseIdentifier:; based on the annotation’s type property, you set a different image on the image property of the annotation.

Great! Now that you have created the annotation and its associated view, you can start adding them to your map view!

First you’ll first need a few resource files that were referenced in initWithAnnotation:reuseIdentifier: (included in annotation-images.zip), and the property list that defines attraction locations (MagicMountainAttractions.plist). They’re included in the resources for this tutorial – copy them to the Images group in your project.

For the curious among you, the plist file contains coordinate information and other details about the attractions at the park:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
        <dict>
                <key>name</key>
                <string>Goliath</string>
                <key>location</key>
                <string>{34.42635,-118.59712}</string>
                <key>type</key>
                <string>1</string>
                <key>subtitle</key>
                <string>Intensity: 8/10</string>
        </dict>
        <dict>
                <key>name</key>
                <string>Batman</string>
                <key>location</key>
                <string>{34.42581,-118.60089}</string>
                <key>type</key>
                <string>1</string>
                <key>subtitle</key>
                <string>Intensity: 6/10</string>
        </dict>
        <dict>
                <key>name</key>
                <string>Ridler&apos;s Revenge</string>
                <key>location</key>
                <string>{34.42430,-118.60074}</string>
                <key>type</key>
                <string>1</string>
                <key>subtitle</key>
                <string>Intensity: 6/10</string>
        </dict>
        <dict>
                <key>name</key>
                <string>X2</string>
                <key>location</key>
                <string>{34.42156,-118.59556}</string>
                <key>type</key>
                <string>1</string>
                <key>subtitle</key>
                <string>Intensity: 10/10</string>
        </dict>
        <dict>
                <key>name</key>
                <string>Tatsu</string>
                <key>location</key>
                <string>{34.42150,-118.59741}</string>
                <key>type</key>
                <string>1</string>
                <key>subtitle</key>
                <string>Intensity: 7/10</string>
        </dict>
        <dict>
                <key>name</key>
                <string>Panda Express</string>
                <key>location</key>
                <string>{34.42126,-118.595637}</string>
                <key>type</key>
                <string>2</string>
                <key>subtitle</key>
                <string>Cost: $$</string>
        </dict>
        <dict>
                <key>name</key>
                <string>Cold Stone</string>
                <key>location</key>
                <string>{34.42401,-118.59495}</string>
                <key>type</key>
                <string>2</string>
                <key>subtitle</key>
                <string>Cost: $</string>
        </dict>
        <dict>
                <key>name</key>
                <string>First Aid</string>
                <key>location</key>
                <string>{34.42640,-118.59918}</string>
                <key>type</key>
                <string>3</string>
                <key>subtitle</key>
                <string>Call 911 For Emergency</string>
        </dict>
</array>
</plist>

Now that you have the above resource files in place, you can leverage your new annotations!

Go back to PVParkMapViewController.m and import your new MKAnnotation and MKAnnotationView classes, as shown in the code snippet below:

#import "PVAttractionAnnotation.h"
#import "PVAttractionAnnotationView.h"

Next, define a new method to add the attraction annotations to the map view.

Then add the following new method (still in PVParkMapViewController.m):

- (void)addAttractionPins {
    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"MagicMountainAttractions" ofType:@"plist"];
    NSArray *attractions = [NSArray arrayWithContentsOfFile:filePath];
    for (NSDictionary *attraction in attractions) {
        PVAttractionAnnotation *annotation = [[PVAttractionAnnotation alloc] init];
        CGPoint point = CGPointFromString(attraction[@"location"]);
        annotation.coordinate = CLLocationCoordinate2DMake(point.x, point.y);
        annotation.title = attraction[@"name"];
        annotation.type = [attraction[@"type"] integerValue];
        annotation.subtitle = attraction[@"subtitle"];
        [self.mapView addAnnotation:annotation];
    }
}

This method reads MagicMountainAttractions.plist and enumerates over the array of dictionaries. For each entry, it creates an instance of PVAttractionAnnotation with the attraction’s information, and then adds each annotation to the map view.

Now you need to update loadSelectedOptions to accommodate this new option and execute your new method when it is selected.

Add the following code to loadSelectedOptions (still in PVParkMapViewController.m):

- (void)loadSelectedOptions {
    [self.mapView removeAnnotations:self.mapView.annotations];
    [self.mapView removeOverlays:self.mapView.overlays];
    for (NSNumber *option in self.selectedOptions) {
        switch ([option integerValue]) {
            case PVMapOverlay:
                [self addOverlay];
                break;
            case PVMapPins:
                [self addAttractionPins];
                break;
            default:
                break;
        }
    }
}

You’re almost there! Last but not least, you need to implement another delegate method that provides the MKAnnotationView instances to the map view so that it can render them on itself.

Add the code below to PVParkMapViewController.m:

- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation {
    PVAttractionAnnotationView *annotationView = [[PVAttractionAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"Attraction"];
    annotationView.canShowCallout = YES;
    return annotationView;
}

The method above receives the MKAnnotation which is used to create the PVAttractionAnnotationView. The property canShowCallout is set to YES so that when the annotation is touched by the user, a call-out is shown with more information. Finally, the annotation view is returned.

Build and run to see your annotations in action!

Turn on the Attraction Pins to see the result as in the screenshot below:

Annotations

The Attraction pins are looking rather…sharp at this point! :]

So far you’ve covered a lot of complicated bits of MapKit, including overlays and annotations. But what if you need to use some drawing primitives, like lines, shapes, and circles?

The MapKit framework also gives you the ability to draw directly on a map view! MapKit provides MKPolyline, MKPolygon, and MKCircle for just this purpose.

I Walk The Line – MKPolyline

If you’ve ever been to Magic Mountain, you know that the Goliath hypercoaster is an incredible ride, and some riders like to make a beeline for it once they walk in the gate! :]

To help out these riders, you’ll plot a path from the entrance of the park to the Goliath.

MKPolyline is a great solution for drawing a path that connects multiple points, such as plotting a non-linear route from point A to point B. You’ll use MKPolyline in your app to draw the route that the Goliath fans should follow to ensure they get to the ride as quickly as possible!

To draw a polyline, you need a series of longitude and latitude coordinates in the order that they should be plotted. Order is important here — otherwise, you’ll have a meandering mess of connected points, which won’t do your riders any good!

The resources for this tutorial contains a file called EntranceToGoliathRoute.plist that contains the path information, so add it to your project.

The contents of the plist file are as follows:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
	<string>{34.42367,-118.594836}</string>
	<string>{34.423597,-118.595205}</string>
	<string>{34.423004,-118.59537}</string>
	<string>{34.423044,-118.595806}</string>
	<string>{34.423419,-118.596126}</string>
	<string>{34.423569,-118.596229}</string>
	<string>{34.42382,-118.596192}</string>
	<string>{34.42407,-118.596283}</string>
	<string>{34.424323,-118.596534}</string>
	<string>{34.42464,-118.596858}</string>
	<string>{34.42501,-118.596838}</string>
	<string>{34.42537,-118.596688}</string>
	<string>{34.425690,-118.596683}</string>
	<string>{34.42593,-118.596806}</string>
	<string>{34.42608,-118.597101}</string>
	<string>{34.42634,-118.597094}</string>
</array>
</plist>

The property list is simply an array of strings that have the longitude and latitude coordinates of each of the points in the path.

Now you need a way to read in that plist file and create the route for the riders to follow.

Add the following code to PVParkMapViewController.m:

- (void)addRoute {
    NSString *thePath = [[NSBundle mainBundle] pathForResource:@"EntranceToGoliathRoute" ofType:@"plist"];
    NSArray *pointsArray = [NSArray arrayWithContentsOfFile:thePath];
 
    NSInteger pointsCount = pointsArray.count;
 
    CLLocationCoordinate2D pointsToUse[pointsCount];
 
    for(int i = 0; i < pointsCount; i++) {
        CGPoint p = CGPointFromString(pointsArray[i]);
        pointsToUse[i] = CLLocationCoordinate2DMake(p.x,p.y);
    }
 
    MKPolyline *myPolyline = [MKPolyline polylineWithCoordinates:pointsToUse count:pointsCount];
 
    [self.mapView addOverlay:myPolyline];
}

This method reads EntranceToGoliathRoute.plist, and enumerates over the contained array where it converts the individual coordinate strings to CLLocationCoordinate2D structures.

It’s remarkable how simple it is to implement your polyline in your app; you simply create an array containing all of the points, and pass it to MKPolyline! It doesn’t get much easier than that.

Now you need to add an option to allow the user to turn the polyline path on or off.

Update loadSelectedOptions to match the code below:

- (void)loadSelectedOptions {
    [self.mapView removeAnnotations:self.mapView.annotations];
    [self.mapView removeOverlays:self.mapView.overlays];
    for (NSNumber *option in self.selectedOptions) {
        switch ([option integerValue]) {
            case PVMapOverlay:
                [self addOverlay];
                break;
            case PVMapPins:
                [self addAttractionPins];
                break;
            case PVMapRoute:
                [self addRoute];
                break;
            default:
                break;
        }
    }
}

Finally, to tie it all together, you need to update the delegate method to return the actual view to be rendered on the map view.

Update mapView:rendererForOverlay: to handle the case of a polyline overview, as follows:

- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay {
    if ([overlay isKindOfClass:PVParkMapOverlay.class]) {
        UIImage *magicMountainImage = [UIImage imageNamed:@"overlay_park"];
        PVParkMapOverlayView *overlayView = [[PVParkMapOverlayView alloc] initWithOverlay:overlay overlayImage:magicMountainImage];
 
        return overlayView;
    } else if ([overlay isKindOfClass:MKPolyline.class]) {
        MKPolylineRenderer *lineView = [[MKPolylineRenderer alloc] initWithOverlay:overlay];
        lineView.strokeColor = [UIColor greenColor];
 
        return lineView;
    }
 
    return nil;
}

The process of displaying the polyline view is very similar to previous overlay views. However, in this case, you do not need to create any custom view objects. You simply use the MKPolyLineRenderer framework provided, and initialize a new instance with the overlay.

MKPolyLineRenderer also provides you with the ability to change certain attributes of the polyline. In this case, you’ve modified the the stroke color to show as green.

Build and run your app, enable the route option, and it should appear on the screen as in the screenshot below:

Route

Goliath fanatics will now be able to make it to the coaster in record time! :]

It would be nice to show the park patrons where the actual park boundaries are, as the park doesn’t actually occupy the entire space shown on the screen.

Although you could use MKPolyline to draw a shape around the park boundaries, MapKit provides another class that is specifically designed to draw closed polygons: MKPolygon.

Don’t Fence Me In – MKPolygon

MKPolygon is remarkably similar to MKPolyline, except that the first and last points in the set of coordinates are connected to each other to create a closed shape.

You’ll create an MKPolygon as an overlay that will show the park boundaries. The park boundary coordinates are already defined in the MagicMountain.plist; go back and look at initWithFilename: to see where the boundary points are read in from the plist file.

Add the following code to PVParkMapViewController.m:

- (void)addBoundary {
    MKPolygon *polygon = [MKPolygon polygonWithCoordinates:self.park.boundary
                                                     count:self.park.boundaryPointsCount];
    [self.mapView addOverlay:polygon];
}

The implementation of addBoundary above is pretty straightforward. Given the boundary array and point count from the park instance, you can quickly and easily create a new MKPolygon instance!

Can you guess the next step here? It’s very similar to what you did for MKPolyline above.

Yep, that’s right — update loadSelectedOptions to handle the new option of showing or hiding the park boundary, as shown below:

- (void)loadSelectedOptions {
    [self.mapView removeAnnotations:self.mapView.annotations];
    [self.mapView removeOverlays:self.mapView.overlays];
    for (NSNumber *option in self.selectedOptions) {
        switch ([option integerValue]) {
            case PVMapOverlay:
                [self addOverlay];
                break;
            case PVMapPins:
                [self addAttractionPins];
                break;
            case PVMapRoute:
                [self addRoute];
                break;
            case PVMapBoundary:
                [self addBoundary];
                break;
            default:
                break;
        }
    }
}

MKPolygon conforms to MKOverlay just as MKPolyline does, so you need to update the delegate method again.

Update the delegate method in PVParkMapViewController.m:

- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay {
    if ([overlay isKindOfClass:PVParkMapOverlay.class]) {
        UIImage *magicMountainImage = [UIImage imageNamed:@"overlay_park"];
        PVParkMapOverlayView *overlayView = [[PVParkMapOverlayView alloc] initWithOverlay:overlay overlayImage:magicMountainImage];
 
        return overlayView;
    } else if ([overlay isKindOfClass:MKPolyline.class]) {
        MKPolylineRenderer *lineView = [[MKPolylineRenderer alloc] initWithOverlay:overlay];
        lineView.strokeColor = [UIColor greenColor];
 
        return lineView;
    } else if ([overlay isKindOfClass:MKPolygon.class]) {
        MKPolygonRenderer *polygonView = [[MKPolygonRenderer alloc] initWithOverlay:overlay];
        polygonView.strokeColor = [UIColor magentaColor];
 
        return polygonView;
    }
 
    return nil;
}

The update to the delegate method is as straightforward as before. You create an MKOverlayView as an instance of MKPolygonRenderer, and set the stroke color to magenta.

Run the app to see your new boundary in action!

Boundary

That takes care of polylines and polygons. The last drawing method to cover is drawing circles as an overlay, which is neatly handled by MKCircle.

Circle In The Sand – MKCircle

MKCircle is again very similar to MKPolyline and MKPolygon, except that it draws a circle, given a coordinate point as the center of the circle, and a radius that determines the size of the circle.

You can easily imagine that users would like to mark on the map where they spotted a character in the park, and have that information communicated to other app users in the park. As well, the radius of the circle representing a character could change, depending on how long it has been since that character was last spotted.

You won’t go quite that far in this tutorial, but at the very least, you can load up some sample character coordinate data from a file and draw some circles on the map to simulate the location of those characters.

The MKCircle overlay is a very easy way to implement this functionality .

The resources for this tutorial contains the character location files (character-locations.zip), so make sure they’re added to your project.

Each file is an array of a few coordinates where characters have been spotted:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
        <string>{34.42481,-118.596914}</string>
        <string>{34.423383,-118.596101}</string>
        <string>{34.423628,-118.595197}</string>
        <string>{34.421832,-118.595404}</string>
</array>
</plist>

Characters will be represented by a class named PVCharacter. So create a new class under the Models group called PVCharacter as a subclass of MKCircle.

Then replace PVCharacter.h with the following:

#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
 
@interface PVCharacter : MKCircle <MKOverlay>
 
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) UIColor *color;
 
@end

The new class that you just added conforms to the MKOverlay protocol, and defines two properties: name and color.

The implementation is extremely simple — nothing additional is required.

#import "PVCharacter.h"
 
@implementation PVCharacter
 
@end

Import PVCharacter.h in PVParkMapViewController.m as in the code below:

#import "PVCharacter.h"

Now you need a method to add the character based on the data in the plist file. Add the following code to PVParkMapViewController.m:

- (void)addCharacterLocation {
    NSString *batmanFilePath = [[NSBundle mainBundle] pathForResource:@"BatmanLocations" ofType:@"plist"];
    NSArray *batmanLocations = [NSArray arrayWithContentsOfFile:batmanFilePath];
    CGPoint batmanPoint = CGPointFromString(batmanLocations[rand()%4]);
    PVCharacter *batman = (PVCharacter *)[PVCharacter circleWithCenterCoordinate:CLLocationCoordinate2DMake(batmanPoint.x, batmanPoint.y)
                                                                       radius:MAX(5, rand()%40)];
    batman.color = [UIColor blueColor];
 
    NSString *tazFilePath = [[NSBundle mainBundle] pathForResource:@"TazLocations" ofType:@"plist"];
    NSArray *tazLocations = [NSArray arrayWithContentsOfFile:tazFilePath];
    CGPoint tazPoint = CGPointFromString(tazLocations[rand()%4]);
    PVCharacter *taz = (PVCharacter *)[PVCharacter circleWithCenterCoordinate:CLLocationCoordinate2DMake(tazPoint.x, tazPoint.y)
                                                                       radius:MAX(5, rand()%40)];
    taz.color = [UIColor orangeColor];
 
    NSString *tweetyFilePath = [[NSBundle mainBundle] pathForResource:@"TweetyBirdLocations" ofType:@"plist"];
    NSArray *tweetyLocations = [NSArray arrayWithContentsOfFile:tweetyFilePath];
    CGPoint tweetyPoint = CGPointFromString(tweetyLocations[rand()%4]);
    PVCharacter *tweety = (PVCharacter *)[PVCharacter circleWithCenterCoordinate:CLLocationCoordinate2DMake(tweetyPoint.x, tweetyPoint.y)
                                                                       radius:MAX(5, rand()%40)];
    tweety.color = [UIColor yellowColor];
 
    [self.mapView addOverlay:batman];
    [self.mapView addOverlay:taz];
    [self.mapView addOverlay:tweety];
}

The method above performs pretty much the same operations for each character. First, it reads in the data from the plist file and selects a random location from the four locations in the file. Next, it creates an instance of PVCharacter at the previously chosen random location, and sets the radius to a random value to simulate the time variance.

Finally, each character is assigned a color and added to the map as an overlay.

You’re almost done — can you recall what the last few steps should be?

Right — you still need to provide the map view with an MKOverlayView, which is done through the delegate method.

Update the delegate delegate method in PVParkMapViewController.m to reflect the following:

- (MKPolylineRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay {
    if ([overlay isKindOfClass:PVParkMapOverlay.class]) {
        UIImage *magicMountainImage = [UIImage imageNamed:@"overlay_park"];
        PVParkMapOverlayView *overlayView = [[PVParkMapOverlayView alloc] initWithOverlay:overlay overlayImage:magicMountainImage];
 
        return overlayView;
    } else if ([overlay isKindOfClass:MKPolyline.class]) {
        MKPolylineRenderer *lineView = [[MKPolylineRenderer alloc] initWithOverlay:overlay];
        lineView.strokeColor = [UIColor greenColor];
 
        return lineView;
    } else if ([overlay isKindOfClass:MKPolygon.class]) {
        MKPolygonRenderer *polygonView = [[MKPolygonRenderer alloc] initWithOverlay:overlay];
        polygonView.strokeColor = [UIColor magentaColor];
 
        return polygonView;
    } else if ([overlay isKindOfClass:PVCharacter.class]) {
        MKCircleRenderer *circleView = [[MKCircleRenderer alloc] initWithOverlay:overlay];
        circleView.strokeColor = [(PVCharacter *)overlay color];
 
        return circleView;
    }
 
    return nil;
}

And finally, update loadSelectedOptions to give the user an option to turn the character locations on or off.

Update loadSelectedOptions (still in PVParkMapViewController.m) to reflect the following changes:

- (void)loadSelectedOptions {
    [self.mapView removeAnnotations:self.mapView.annotations];
    [self.mapView removeOverlays:self.mapView.overlays];
    for (NSNumber *option in self.selectedOptions) {
        switch ([option integerValue]) {
            case PVMapOverlay:
                [self addOverlay];
                break;
            case PVMapPins:
                [self addAttractionPins];
                break;
            case PVMapRoute:
                [self addRoute];
                break;
            case PVMapBoundary:
                [self addBoundary];
                break;
            case PVMapCharacterLocation:
                [self addCharacterLocation];
                break;
            default:
                break;
        }
    }
}

Build and run the app, and turn on the character overlay to see where everyone is hiding out!

Characters

Where to Go From Here?

That’s it for this tutorial – at this point you know all about making custom Overlay Images and Overlay Views with MapKit maps.

Here’s the final example project that you developed in the tutorial.

Congratulations – you’ve worked with some of the most important functionality that MapKit provides. With a few basic functions, you’ve implemented a full-blown and practical mapping application complete with annotations, satellite view, and custom overlays!

One direction to take this app is to investigate other ways of creating map overlays.

There are many different ways to generate overlays that range from very easy, to the very complex. The approach in this tutorial that was taken for the overlay_park image provided in this tutorial was the easy — yet tedious — route.

To generate the overlay, a screenshot of a satellite view of the park was used as a bottom layer in a graphics tool. The roller coasters, building locations, trees, parking lot, and other details were traced onto a new layer.

When the satellite screenshot was taken, notes were made of the latitude and longitude values of the four corners of the rectangular screenshot. These coordinates were then used to create the park’s property list which is used to position the overlay on the map view.

There are much more advanced — and perhaps more efficient — methods to create overlays. A few alternate methods are to use KML files, MapBox tiles, or other 3rd party provided resources.

This tutorial didn’t delve into these overlay types in order to remain focused on the task of demonstrating the MapKit framework and APIs. But if you are serious about developing mapping apps, then you would do well to investigate these other options, and discover how to hook them into your apps!

I hope you enjoyed this tutorial, and I hope to see you use MapKit overlays in your own apps. If you have any questions or comments, please join the forum discussion below!

Cesare Rocchi

Cesare Rocchi is a speaker, writer, UX designer and developer specializing in web and mobile applications. He began working on interactive applications while he was a researcher in the academia. He runs Studio Magnolia, an interactive studio that creates compelling web and mobile applications. He blogs at upbeat.it. You can find him on Twitter or app.net.

When off duty he enjoys snowboard and beach tennis. Now he is busy working on Neater.

User Comments

38 Comments

[ 1 , 2 , 3 ]
  • Hi and thank you for your comment, I was wondering if you could explain how to remove an array of annotations? I have 4 arrays of annotations and I would like the ability to be able to switch each one on or off via a Switch but Im not managing to find a solution despite trying various things.

    Any answer very gratefully received!!

    Cheers
    Jon
    JonMillar
  • Hey Chris Thanks For this wonderful tutorial. I am working on iPhone app where i am using MKMapView and MKOverlay i need to overlay traffic information, I tried it but the only solution i found that i have to use UIWebView and then load map, It'll open native map app and i can show traffic information. But I don't want to use webView My question - Is there any way to add traffic info using MKMapView ?
    Thanks in advance.
    TanejaArun
  • Is there updated version of tutorial for iOS7 available.

    I am finding issues with MKOverlayRenderer methods not getting called and messing up overlay functionality..
    asethi123
  • goodnight like to ask you some help on a project I'm developing, you can help me?
    alefranco.af
  • asethi123 wrote:Is there updated version of tutorial for iOS7 available.

    I am finding issues with MKOverlayRenderer methods not getting called and messing up overlay functionality..


    Hi Asethi123,

    can you provide a bit more details? Which method in particular does not get called?
    funkyboy
  • Im having the same problem as asethi123

    after adding this method:
    - (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id)overlay
    and running the project... its just never get called.
    Can't proceed the tutorial... spent few hours and stuck now -_-
    Need to find a working tutorial and start over.
    Rotems
  • Ok, its not mentioned but you should click the menu on the top right corner and then select the Map Overlay option.
    Rotems
  • Hi, that's great tutorial!
    Can I ask you some question?
    I want to make an offline map for my company. I work for a company to grow rubber tree and collect rubber latex. My company grow rubber tree in the forest, in that place don't have internet. So I want to make an offline map have some function bellow:
    I have an image about all this area under cultivation and location detail collect by GPS machine. I want to use this image to overlay map and when I use this map, I can know where am I on the map and when I move that point move to, because I have detail location each portion. The image to overlay designed detail to know road middle 2 portion, main road....Can I do this and technology support that. Thank you very much!
    tuandatgl
[ 1 , 2 , 3 ]

Other Items of Interest

Ray's Monthly Newsletter

Sign up to receive a monthly newsletter with my favorite dev links, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

Vote for Our Next Tutorial!

Every week, we alternate between Gaming and Non-Gaming tutorial votes. This week: Non-Gaming!

    Loading ... Loading ...

Last week's winner: How to Make a Simple 2D Game with Metal.

Suggest a Tutorial - Past Results

Hang Out With Us!

Every month, we have a free live Tech Talk - come hang out with us!


Coming up in October: Xcode 6 Tips and Tricks!

Sign Up - October

Our Books

Our Team

Tutorial Team

  • Dani Arnaout
  • Tim Mitra

... 53 total!

Update Team

... 14 total!

Editorial Team

  • Alexis Gallagher

... 22 total!

Code Team

  • Orta Therox

... 3 total!

Subject Matter Experts

  • Richard Casey

... 4 total!