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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Overlay Images and Overlay Views with MapKit Tutorial
50 mins
- Apple Maps vs Google Maps
- Getting Started
- Do You Know the Way To San Jose? – Adding A MapView
- The Long and Winding Road – Wiring Up Your MapView
- I’ve Been There, You Can’t Get There from Here – Interacting With MKMapView
- I've Been Everywhere, Man - Switching The Map Type
- What a View - All About Overlay Views
- Put Yourself on the Map - Adding Your Own Information
- If You Liked It Then You Should Have Put a Pin On It - Annotations
- I Walk The Line - MKPolyline
- Don't Fence Me In - MKPolygon
- Circle In The Sand - MKCircle
- Where to Go From Here?
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:
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:
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.
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:
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!