Overlay Images and Overlay Views with MapKit Tutorial

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

Leave a rating/review
Save for later
Share
You are currently viewing page 2 of 6 of this article. Click here to view the first page.

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!

Contributors

Over 300 content creators. Join our team.