Introduction to MapKit in iOS 6 Tutorial

Matt Galloway Matt Galloway
Plot Baltimore crime data using MapKit!

Plot Baltimore arrests data using MapKit!

Note from Ray: This is the sixth iOS 6 tutorial in the iOS 6 Feast! In this tutorial, we’re updating one of our older tutorials to iOS 6 so it’s fully up-to-date with the latest features like the new Apple Maps in iOS 6 and related new iOS 6 APIs.

Parts of this tutorial come from Matt Galloway‘s “What’s New with MapKit chapter” in our new book iOS 6 by Tutorials, although the book is about a different app (a subway route finder!) and takes things MUCH further than this simple example, such as registering your app as a routing provider. Enjoy!

Update 9/9/12: Fully updated for iOS 6 by Matt Galloway.

Update 3/19/12: Fully updated for iOS 5 by Malek Trabelsi.

3/23/11: Original post by Ray Wenderlich.

MapKit is a really neat API available on the iPhone that makes it easy to display maps, jump to coordinates, plot locations, and even draw routes and other shapes on top.

I’m writing this tutorial because about a month ago I attended a neat event in Baltimore called Civic Hack Day where I played around with MapKit to display some public data such as place locations, crime data, arrests and bus routes. It was kind of fun so thought others might be interested to learn how it works – and maybe use a similar technique with data from your own hometown!

In this tutorial, we’re going to make an app that zooms into a particularly cool location in Baltimore. The app will then query a Baltimore web service to pull back the recent arrests around that area, and plot them on the map.

In the process, you’ll learn how to add a MapKit map to your app, zoom to a particular location, query and retrieve government data available via the Socrata API, create custom map annotations, and more!

This tutorial assumes some familiarity with Objective-C and iOS programming. If you are a complete beginner, you may wish to check out some of the other tutorials on this site.

This tutorial uses Xcode 4.5 with iOS 6 features such as Objective-C literals and the new mapping engine developed directly by Apple. It also uses iOS 5 features such as Storyboard, ARC. You really are living at the cutting edge with us!

Without further ado, let’s get mapping!

Getting Started

In Xcode 4.5, go to File\New\New Project, select iOS\Application\Single View Application, and click Next. Then type ArrestsPlotter as the project name. Make sure Use Storyboard and Use Automatic Reference Counting options are checked. Also make sure iPhone is selected for the Devices option. Then click Next, and choose a directory to save your project in to finish.

Click on MainStoryboard.storyboard to bring up Interface Builder. Then bring up the Object library by selecting the third tab in the View toolbar (Utilities) and selecting the third tab in the library toolbar (Object library), as shown in the screenshot below.

Object Library in Xcode 4.2

From the Object library, drag a toolbar to the bottom of the screen, and a Map View to the middle of the screen, and rename the toolbar button to read “Refresh”, as shown in the screenshot below. It’s important to add the toolbar first and then the map, because if you do it that way round you’ll notice that the map automagically takes up the remaining space. It’s as if Xcode is reading your mind!

View Layout with MapKit

Next, click on the map view you added to the middle of the screen, and click on the fourth tab in the inspector toolbar to switch to the Attributes inspector. Click the checkbox for Shows User Location, as shown below.

Show User Location on Map View

Almost done, but before you can run your code you need to add the MapKit framework to your project (or else it will crash on startup!)

To do this in Xcode 4.5, click on the name of your project in the Groups & Files tree, select the ArrestsPlotter target, and switch to the Build Phases tab. Open the Link Binary With Libraries section, click the Plus button, find the entry for MapKit.framework, and click Add.

At this point your screen should look like the screenshot below:

Adding a Framework with Xcode 4

Now you should be able to compile and run your project, and have a fully zoomable-and-pannable map showing your current location (or Cupertino if you’re using the Simulator), using Google Maps!

So far so good, eh? But we don’t want the map to start looking at the entire world – we want to take a look at a particular area!

As an aside, you might want to at this point experiment with Xcode’s simulate location feature. You’ll notice that the app currently thinks you’re in Cupertino. That’s all well and good, but maybe you want to pretend you’re somewhere else far far away from 1 Infinite Loop. Although I’m not sure why you wouldn’t want to pretend you were there… Anyway, you can simulate your location by clicking the location indicator icon at the bottom of the main Xcode panel whilst the app is running. From there you can select a location. See the screenshot below for an example.

Setting Visible Area

First, we need to connect the Map View you created in Interface Builder to an instance variable in your view controller. We could do this the old fashioned way (create an instance variable and property, and connect with Interface Builder by right clicking and drawing lines from the outlet to the map view), but there’s an even easier way in Xcode!

To do this, select MainStoryboard.storyboard again, and make sure that the assistant editor is selected (the second tab in the Editor tab group). I like to have mine show up at the bottom – you can set the position with View\Assistant Layout\Assistant Editors at Bottom.

In the toolbar at the top of the Assistant Editor, make sure Automatic is selected, and that it is set to display ViewController.h. If it’s not, click on Automatic, on the drop down list, choose Manual and look for ViewController.h like shown in the screenshot below.

Show File With Assistant Editor

Now, control-drag from the Map View down to your header file, right in the middle of the @interface declaration, and before the @end.

A popup will appear. Set the connection type to Outlet, the name to mapView, keep the Type as MKMapView, the Storage as Weak, and click Connect. It will automatically make a property for your map view and hook it up for you!

This calls for a celebration – 3 w00ts (and one rage) for Xcode 4!

Xcode 4 Rage Comic

(If you don’t get this, check out the Introduction to In-App Purchases tutorial for more details on rage comics :])

Ahem – back to work! Open ViewController.m, and add the following underneath the #imports and before the @implementation:

#define METERS_PER_MILE 1609.344

This simply adds a constant for meters per mile which you’ll be using later. Now implement viewWillAppear to zoom in to an initial location on startup:

- (void)viewWillAppear:(BOOL)animated {  
    // 1
    CLLocationCoordinate2D zoomLocation;
    zoomLocation.latitude = 39.281516;
    zoomLocation.longitude= -76.580806;
 
    // 2
    MKCoordinateRegion viewRegion = MKCoordinateRegionMakeWithDistance(zoomLocation, 0.5*METERS_PER_MILE, 0.5*METERS_PER_MILE);
 
    // 3
    [_mapView setRegion:viewRegion animated:YES];
}

There’s a lot of new stuff here, so let’s go over it bit by bit.

  1. Picks out the location to zoom in. Here we choose the location in Baltimore where I initially wrote this app, which is a good choice for the BPD Arrests API we’ll be using later on in this tutorial.
  2. When you are trying to tell the map what to display, you can’t just give a lat/long – you need to specify the box (or region) to display. So this uses a helper function to make a region around a center point (the user’s location) and a given width/height. We use half a mile here, because that works well for plotting arrests data.
  3. Finally, tells the mapView to display the region. The map view automatically transitions the current view to the desired region with a neat zoom animation with no extra code required!

Compile and run the app, and now it should zoom in to Baltimore area :]

Obtaining Arrests Data: The Plan

The next step is to plot some interesting arrests data around our current location. But where in the heck can we get such stuff??

Well, it depends on your current location. Here in Baltimore, we are quite lucky because the city is working quite hard to making all city data available online, through the OpenBaltimore initiative.

We will be using this dataset for the purposes of this tutorial. After you finish this tutorial, maybe look around to see if your city has an alternate dataset you can use?

Anyway, the Baltimore city data is made available through a company named Socrata, who has an API you can use to access the data. The Socrata API documentation is available online, so we aren’t going to go into the gory details here, except to explain the high level plan of attack:

  1. The specific dataset we’re interested in is the BPD Arrests. Using this link, you can take a peek at the raw data, and if you click Export\API, you can see the API access endpoint we’ll be using.
  2. To query the API, you basically issue a POST to the given Socrata URL, and pass in a query as JSON. The results will come back as JSON as well. You can learn more about the command and response formats in the Socrata API documentation, but you don’t really need to know the details for this tutorial.
  3. The query we need to use is on the largish end, so we’ll store it in a text file to make it a bit easier to read and edit, and do some subtitutions in the code.
  4. To save time, we’ll use ASIHTTPRequest to assist with sending/receiving data to the web service.

Ok – so we’ve got a plan, but before we can begin, we need to quickly add the ASIHTTPRequest library to our project.

Adding the Libraries

To add ASIHTTPRequest, first download it. Once you have it downloaded, right click your ArrestsPlotter project entry in groups and files, select New Group, and name the new group ASIHTTPRequest. Then drag all of the files (but not the folders) from within the ASIHTTPRequest\Classes directory (ASIAuthenticationDialog.h and several others) into the new ASIHTTPRequest group. Make sure “Copy items into destination group’s folder (if needed)” and “Add to targets -> ArrestsPlotter” are selected, and click Finish.

Also repeat this for the two files (Reachability.h and Reachability.m) in ASIHTTPRequest\External\Reachability, as these are dependencies of the project.

To add MBProgressHUD, first download it. Once you have it downloaded, right click your ArrestsPlotter project entry in groups and files, select New Group, and name the new group MBProgressHUD. Then drag MBProgressHUD.h and MBProgressHUD.m into the new MBProgressHUD group. Make sure “Copy items into destination group’s folder (if needed)” and “Add to targets -> ArrestsPlotter” are selected, and click Finish.

The last step is you need to link your project against a few required frameworks. To do this, click on your ArrestsPlotter project entry in Groups & Files, click the ArrestsPlotter target, and choose the Build Phases tab. Click the plus button, and choose CFNetwork.framework. Then repeat this for SystemConfiguration.framework, MobileCoreServices.framework, and libz.dylib.

Framework dependencies in Xcode 4.2

Note that, so far, if you compile the app you will run several error messages like – autorelease is unavailable, retain is unavailable and ARC forbids explicit message send of ‘release’ and many others around the ARC feature.

Actually, projects with Automatic Reference Counting (ARC) enabled can use ASIHTTPRequest. However, since ASIHTTPRequest’s codebase does not use ARC, you will need to add compiler flags to get everything working. This is pretty easy. In Xcode, go to your active target and select the “Build Phases” tab. In the “Compiler Flags” column, set -fno-objc-arc for each of the ASIHTTPRequest source files (including Reachability.m).

Compile your project just to make sure you’re good so far, and now we’re back to the fun stuff! You will notice a few warnings coming from ASIHTTPRequest and MBProgressHUD which you can simply ignore. They are just there because it hasn’t been fully updated yet to support the new iOS 6 SDK.

Obtaining Arrests Data: The Implementation

First, download this resource file which contains a template for the query string we need to send to the Socrata API web service to get the arrests near a particular location. When you get the file, unzip it and drag command.json into your ArrestsPlotter\Supporting Files group, make sure “Copy items into destination group’s folder (if needed)” and “Add to targets -> ArrestsPlotter” are selected, and click Finish.

Next, you need to set up the “Refresh” button on the toolbar to call a method, so you know when it’s tapped and can search for the arrests data around the current location. Again, you could do this the old way (make an IBAction outlet and connect with Interface Builder), but you might as well use the new super-duper, automagic way!

To do this, click MainStoryboard.storyboard, select the Refresh button, and control drag from the button to ViewController.h, to the line right after the mapView outlet. Change the Connection type to Action, the Name to refreshTapped, keep the Type as id, and click Connect. Xcode will automatically create the method for you in both the header and implementation, and connect it too!

Then switch over to ViewController.m and make the following changes:

// At top of file
#import "ASIHTTPRequest.h"
 
// Replace refreshTapped as follows
- (IBAction)refreshTapped:(id)sender {
     // 1
    MKCoordinateRegion mapRegion = [_mapView region];
    CLLocationCoordinate2D centerLocation = mapRegion.center;
 
    // 2
    NSString *jsonFile = [[NSBundle mainBundle] pathForResource:@"command" ofType:@"json"];
    NSString *formatString = [NSString stringWithContentsOfFile:jsonFile encoding:NSUTF8StringEncoding error:nil];
    NSString *json = [NSString stringWithFormat:formatString, 
                      centerLocation.latitude, centerLocation.longitude, 0.5*METERS_PER_MILE];
 
    // 3
    NSURL *url = [NSURL URLWithString:@"http://data.baltimorecity.gov/api/views/INLINE/rows.json?method=index"];
 
    // 4
    ASIHTTPRequest *_request = [ASIHTTPRequest requestWithURL:url];
    __weak ASIHTTPRequest *request = _request;
 
    request.requestMethod = @"POST";    
    [request addRequestHeader:@"Content-Type" value:@"application/json"];
    [request appendPostData:[json dataUsingEncoding:NSUTF8StringEncoding]];
    // 5
    [request setDelegate:self];
    [request setCompletionBlock:^{         
        NSString *responseString = [request responseString];
        NSLog(@"Response: %@", responseString);
    }];
    [request setFailedBlock:^{
        NSError *error = [request error];
        NSLog(@"Error: %@", error.localizedDescription);
    }];
 
    // 6
    [request startAsynchronous];
}

Let’s review this section by section.

  1. Gets the lat/long for the center of the map.
  2. Reads in the command file template that you downloaded from this site, which is the query string you need to send to the Socrata API to get the arrests within a radius of a particular location. It also has a hardcoded date restriction in there to keep the data set managable. The command file is set up to be a query string, so you can substitute the lat/long and radius in there as parameters. It has a hardcoded radius here (0.5 miles) to again keep the returned data managable.
  3. Creates a URL for the web service endpoint to query.
  4. Creates a ASIHTTPRequest request, and sets it up as a POST, passing in the JSON string as data.
  5. Sets up two blocks for the completion and failure. So far on this site we’ve been using callback methods (instead of blocks) with ASIHTTPRequest, but I wanted to show you the block method here because it’s kinda cool and convenient. Right now, these do nothing but log the results.
  6. Finally, starts the request going asynchronously. When it completes, either the completion or error block will be executed.

Compile and run your code, and if all works well you should see some data in your console when you click refresh, similar to the following:

Arrests Data Web Service Results

Plotting the Data

Ok so now that we have interesting data points to show, all we have to do is add them to the map!

With MapKit, you can do this with something called a “map annotation”. Think of map annotations as the little pins that show up in the Maps app. They don’t necessarily have to be pins – we are gonna make them look something different!

To use annotations there are three steps:

  1. Create a class that implements the MKAnnotation protocol. This means it needs to return a title, subtitle, and coordinate. You can store other information on there if you want too.
  2. For every location you want marked on the map, create one of these classes and add it to the mapView with the addAnnotation method.
  3. Mark the view controller as the map view’s delegate, and for each annotation you added it will call a method on the view controller called mapView:viewForAnnotation:. Your job in this method is to return an instance of MKAnnotationView to present as a visual indicator of the annotation. We’ll be using the base class, MKAnnotationView, in this tutorial but there is a concrete subclass called MKPinAnnotationView which can be used if you want the standard pin you see in the Maps app.

Ok, so let’s start with step 1. Select your ArrestsPlotter group, go to File\New\New File, choose iOS\Cocoa Touch\Objective-C class, and click Next. Enter MyLocation for the class, make it a subclass of NSObject, and finish creating the file.

Replace MyLocation.h with the following:

#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
 
@interface MyLocation : NSObject <MKAnnotation>
 
- (id)initWithName:(NSString*)name address:(NSString*)address coordinate:(CLLocationCoordinate2D)coordinate;
- (MKMapItem*)mapItem;
 
@end

This is a plain old NSObject with a special initializer and a method to return an MKMapItem (more on that in a minute). Note it marks itself as implementing the MKAnnotation protocol. This means that the coordinate property is required, and so are the title and subtitle methods (which we’ll be adding next).

Now replace MyLocation.m with the following:

#import "MyLocation.h"
#import <AddressBook/AddressBook.h>
 
@interface MyLocation ()
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *address;
@property (nonatomic, assign) CLLocationCoordinate2D theCoordinate;
@end
 
@implementation MyLocation
 
- (id)initWithName:(NSString*)name address:(NSString*)address coordinate:(CLLocationCoordinate2D)coordinate {
    if ((self = [super init])) {
        if ([name isKindOfClass:[NSString class]]) {
            self.name = name;
        } else {
            self.name = @"Unknown charge";
        }
        self.address = address;
        self.theCoordinate = coordinate;
    }
    return self;
}
 
- (NSString *)title {
    return _name;
}
 
- (NSString *)subtitle {
    return _address;
}
 
- (CLLocationCoordinate2D)coordinate {
    return _theCoordinate;
}
 
- (MKMapItem*)mapItem {
    NSDictionary *addressDict = @{(NSString*)kABPersonAddressStreetKey : _address};
 
    MKPlacemark *placemark = [[MKPlacemark alloc]
                              initWithCoordinate:self.coordinate
                              addressDictionary:addressDict];
 
    MKMapItem *mapItem = [[MKMapItem alloc] initWithPlacemark:placemark];
    mapItem.name = self.title;
 
    return mapItem;
}
 
@end

Again a simple implementation here – note the required title method (from MKAnnotation protocol) returns the name, and the required subtitle method returns the address. The mapItem method is more interesting. This is creating an instance of a special class called MKMapItem to represent this location. What this class does is provide a way for you to pass information to the Maps app. You’re going to add the ability in a minute to open the Maps app directly from within your app! It’ll show the location in the standard Maps app. Pretty neat!

One more thing you need to do right now is to add the AddressBook framework. Do this like you did before by selecting the ArrestsPlotter project on the left panel of Xcode. Then select the Build Phases tab within the ArrestsPlotter target, open Link Binary with Libraries panel and click the plus button. Finally select AddressBook.framework and select Add. You need this because you’re using kABPersonAddressStreetKey from that framework in the mapItem method.

Onto step 2 – add an instance of one of these classes for every arrest we wish to plot. Make the following changes to ViewController.m:

// Add to top of file
#import "MyLocation.h"
 
// Add new method above refreshTapped
- (void)plotCrimePositions:(NSData *)responseData {
    for (id<MKAnnotation> annotation in _mapView.annotations) {
        [_mapView removeAnnotation:annotation];
    }
 
    NSDictionary *root = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:nil];
    NSArray *data = [root objectForKey:@"data"];
 
    for (NSArray *row in data) {
        NSNumber * latitude = [[row objectAtIndex:22]objectAtIndex:1];
        NSNumber * longitude = [[row objectAtIndex:22]objectAtIndex:2];
        NSString * crimeDescription = [row objectAtIndex:18];
        NSString * address = [row objectAtIndex:14];
 
        CLLocationCoordinate2D coordinate;
        coordinate.latitude = latitude.doubleValue;
        coordinate.longitude = longitude.doubleValue;
        MyLocation *annotation = [[MyLocation alloc] initWithName:crimeDescription address:address coordinate:coordinate] ;
        [_mapView addAnnotation:annotation];
	}
}
 
// Add new line inside refreshTapped, in the setCompletionBlock, right after logging the response string
[self plotCrimePositions:request.responseData];

The important code here is inside the new method, plotCrimePositions. It first removes any annotations already on the map so you start with a clean slate. Then it parses the response string as JSON, and pulls out the interesting info from it (lat/long, arrest description, etc), given the hardcoded offsets at which this data appears in the JSON results.

Once it’s pulled out the interesting data, it creates a new MyLocation object and adds it as an annotation to the map view.

Ok, onto the third and final step! First select MainStoryboard.storyboard. Then set the View Controller (right at the top of the list) as the delegate of the map view by control clicking on the map view, and dragging a line from the delegate entry to the View Controller.

Then go to ViewController.h, and mark the class as implementing MKMapViewDelegate as follows:

@interface ViewController : UIViewController <MKMapViewDelegate>

Then add a new method to ViewController.m as follows:

- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
    static NSString *identifier = @"MyLocation";   
    if ([annotation isKindOfClass:[MyLocation class]]) {
 
        MKAnnotationView *annotationView = (MKAnnotationView *) [_mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
        if (annotationView == nil) {
            annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
            annotationView.enabled = YES;
            annotationView.canShowCallout = YES;
            annotationView.image = [UIImage imageNamed:@"arrest.png"];//here we use a nice image instead of the default pins
        } else {
            annotationView.annotation = annotation;
        }
 
        return annotationView;
    }
 
    return nil;    
}

This is the method that gets called for every annotation you added to the map (kind of like tableView:cellForRowAtIndexPath:), that needs to return the view for each annotation.

Also similarly to tableView:cellForRowAtIndexPath:, map views are set up to reuse annotation views when some are no longer visible. So the code first checks to see if a reusable annotation view is available before creating a new one.

Update: One extra thing to point out about this, suggested by Kalgar (thanks man!) Note that when you dequeue a reusable annotation, you give it an identifier. If you have multiple styles of annotations, be sure to have a unique identifier for each one, otherwise you might mistakenly dequeue an identifier of a different type, and have unexpected behavior in your app. It’s basically the same idea behind a cell identifier in tableView:cellForRowAtIndexPath.

Here we use the plain vanilla MKAnnotationView. It uses the title and subtitle of our MyLocation class to determine what to show in the callout (the little bubble that pops up when you tap on it). Note we customized the annotation so that it shows an image, for fun.

Speaking of an image, be sure to download this custom image and add it to your project.

And that’s it! Compile and run your code, and now you should be able to zoom around Baltimore searching for arrest!

(Hint: in the land of the Wire, you won’t have to search very far! :])

Opening the Maps app

Wanna try out a cool new feature in iOS 6? You can now easily launch the Maps app right from within your app, with parameters to configure exactly what to show!

Remember that mapItem method you added to MyLocation? Well now you’re about to use it! Open ViewController.m and add the following code:

// Add to mapView:viewForAnnotation: after setting the image on the annotation view
annotationView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
 
// Add the following method
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control {
    MyLocation *location = (MyLocation*)view.annotation;
 
    NSDictionary *launchOptions = @{MKLaunchOptionsDirectionsModeKey : MKLaunchOptionsDirectionsModeDriving};
    [location.mapItem openInMapsWithLaunchOptions:launchOptions];
}

You’ve now made it so that in the callout when a pin is tapped on, there will be a button on the right hand side. When this is tapped, the mapView:annotationView:calloutAccessoryControlTapped: method is called. In this method, you grab the MyLocation object that this tap refers to and then launch the Maps app by calling the openInMapsWithLaunchOptions: method.

It’s as simple as that! You’ll notice that a dictionary is passed in to this method. This allows you to specify a few different options; here the directions key is set to driving. This will make the Maps app try to give driving directions from the current location to this pin. Neat!

I suggest you take a look at the various different options you can pass in the launch options dictionary. Also take a look at the class method on MKMapItem to allow you to pass multiple MKMapItem objects at the same time.

Now build & run the app, perform a refresh and tap on an arrest item. Then tap the blue button on the right and watch it launch the Maps app to show that arrest, with driving directions to it. Very cool :]!

Note: You may have gotten an error when opening the Maps app. If so, it’s likely because it couldn’t get driving directions for some reason. To fix that, simulate your location as being somewhere in the US (e.g. San Francisco) and try again. This time it’ll work and show you directions from downtown San Francisco right to the arrest!

Adding a Progress indicator

The App works fine so far, but it’d be better if we let the user know what’s going on when we pass the HTTP Request, a nice and easy MBProgressHUD will do that so let’s quickly add it in the ViewController.m:

// Add at the top of the file
#import "MBProgressHUD.h"
 
// Add right after [request startAsynchronous] in refreshTapped action method
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.labelText = @"Loading arrests...";
 
// Add at start of setCompletionBlock and setFailedBlock blocks 
[MBProgressHUD hideHUDForView:self.view animated:YES];

Ahh… much more responsive! :]

Where To Go From Here?

Here is a sample project with all of the code we’ve developed in the above tutorial.

Now you know the basics of using Map Kit, but there’s a lot more you can do from here, including geocoding, adding custom map overlays, and more. A great place to go to for additional information is Apple’s Location Awareness Programming Guide.

To take this app further you may want to look into the MKMapItem class method I hinted at for opening the Maps app with multiple items. Perhaps add a button to the toolbar that opens the Maps app with all of the arrests currently shown. Also, why not take a look at the other launch dictionary options to control what happens when Maps opens.

If you want to learn more the new MapKit features in iOS 6, including registering your app as a routing provider, you should check out our new book iOS 6 by Tutorials. The book contains a chapter on MapKit that covers how you can launch Maps with various options, and how you can register your own app as a routing provider to give directions to users!

If you have any questions as you use MapKit in your apps, hints for other MapKit users, or are interested in using government data in your apps, please let me know in the forum discussion below! :]

This is a blog post by iOS Tutorial Team members Matt Galloway, Adam Burkepile, and Ray Wenderlich.

Matt Galloway
Matt Galloway

Matt is the founder of SwipeStack, a mobile development company based in London, UK which create apps for clients and also a few of their own. One client's app was featured by Apple in the best apps of 2011, the App Rewind in the newsstand category.

BeerMap is one of their own creations, helping beer drinkers find a great pub near them! You can find him on Twitter.

User Comments

70 Comments

[ 1 , 2 , 3 , 4 , 5 ]
  • I didn't see that anyone has posted instructions to make this work with the API changes so here's what I did:

    The URL in refreshed tapped becomes: https://data.baltimorecity.gov/api/view ... thod=index

    The data set has changed so the positions in the data arrays that get pulled out of the results in plotCrimePositions need to change:
    NSNumber *latitude = [[row objectAtIndex:17] objectAtIndex:1];
    NSNumber *longitude = [[row objectAtIndex:17] objectAtIndex:2];
    NSString *crimeDescription = [row objectAtIndex:12];
    NSString *address = [row objectAtIndex:11];

    Also, there is some bad data in the file so you need to check if your latitude or longitude values are NSNull or not before you set them on your coordinate object. Otherwise you'll get an exception when you call "doubleValue" on them.

    The data set is huge. The map is completely covered in annotations. A subset of the data is probably better. I'm sure there's a way to do it with the API, but I haven't looked into it.

    Don't think I'll be moving to Baltimore anytime soon.
    Wasserman
  • #define METERS_PER_MILE 1609.344

    Using a preprocessor #define here seems like a bad practice as it lacks type information. Wouldn't it be better to use a const double here?

    const static double metersPerMile = 1609.344; // for example

    Forgive me for splitting hairs but I know that a lot of http://www.raywenderlich.com readers are new to Obj-C. It's easy to get into the habit of using preprocessor definitions to store data that should really be typed.
    NickMar
  • Question : in MyLocation.m

    - (NSString *)title
    {
    return _name;
    }

    - (NSString *)subtitle
    {
    return _address;
    }

    - (CLLocationCoordinate2D)coordinate
    {
    return _theCoordinate;
    }

    Why are you using _address. Can't we use self.address, self.name.
    Same with the mapView property in the ViewController. You have used _mapView. Is there any issue while using self.mapView.
    I never seem to understand when to use "_ObjName" notation and "self.objName" notation.
    tusharkoul
  • Hi,

    I am having trouble getting the annotations to show up on the map. I followed the tutorial and downloaded the completed code at the end of the article. However the annotation pins do not show up on the map after I press the refresh button. Any ideas?

    Thanks!
    Monica
  • Figured it out, I should have read previous comments as the api has changed since this tutorial was published. I followed what Wasserman said in the comments. It is a lot of data, so I just displayed the first 5 (used a counter and a break statement to get out of the loop in the plotCrimePositions method. Yah, I know, not ideal). There is probably a way to limit the amount of data coming back but I didn't research.
    Some notes:
    - it takes a while for the app to run as there is a lot of data to return.
    - I had to zoom the map out to see the annotations as there were not any in the area I was in. (it looks like the location data is not actually used in the query?)
    Monica
  • Hi,

    I'am a little confuse, I followed the above tutorial and all I get when I run the app is a map. When I click the refresh button nothing happens. I debug in the plotCrimePositions method....
    ... in the following line
    NSDictionary *root = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:nil];
    When I hoover on root I get 2 key/value pairs, which gives me:
    [0] key = @"data", value = @"0 Objects"
    [1] key = @"meta", value = "0x9f33820"

    So I conclude if we have O objects, the next 2 line of code, will not run, therefor nothing will be plotted on the map.
    NSArray *data = [root objectForKey:@"data"];
    for (NSArray *row in data) {

    Could someone help me out, did the above tutorial suffer any changes that I am not aware about?
    Thanks in advance, Raven51
    raven51
  • hi guys,

    i'm trying to use the sample app of this tutorial but there's always a problem. i've downloaded the package from tutorial site but don't found a file and also i add that file e make the changes that some user has written, there's a problem. i click on refresh button but don't show nothing.

    i've found on git a repository about this app. and it seems to be updated. i've downloaded and tried. nothing! don't show nothing. there is someone who have a working version?

    thanks.
    Rufy
  • Hello there, nice tutorial.
    I've downloaded the source code but i was getting the error about the command.json file
    I downloaded the file put it in the folder where the project is , but still nothing...
    More specifically the error i get is error: /Users/user/Downloads/test_crimeplotters/CrimePlotter/CrimePlotter/command.json: No such file or directory
    The foler test_crimeplotters isnt anywhere to be found....can you help what im doing wrong?
    Regards
    yiannis
    yiannis silver
  • Hello there.

    yiannis silver, I have the same problem.
    Downloaded project, get error on compile:
    /Users/user/Downloads/test_crimeplotters/CrimePlotter/CrimePlotter/command.json: No such file or directory

    Had removed command.json from project in navigator panel.
    After that project compiling without errors, but still doesn't work properly on my Xcode 6.0.1, iOS 8.0.
    Argus
  • Error on line:
    NSString *json = [NSString stringWithFormat:formatString,
    centerLocation.latitude, centerLocation.longitude, 0.5*METERS_PER_MILE];

    Output:
    2014-10-22 14:34:44.762 ArrestsPlotter[30888:1455647] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSPlaceholderString initWithFormat:locale:arguments:]: nil argument'
    *** First throw call stack:
    (
    0 CoreFoundation 0x02a8fdf6 __exceptionPreprocess + 182
    1 libobjc.A.dylib 0x021aaa97 objc_exception_throw + 44
    2 CoreFoundation 0x02a8fd1d +[NSException raise:format:] + 141
    3 Foundation 0x01b4d341 -[NSPlaceholderString initWithFormat:locale:arguments:] + 105
    4 Foundation 0x01b50c22 +[NSString stringWithFormat:] + 89
    5 ArrestsPlotter 0x00097e60 -[ViewController refreshTapped:] + 496
    6 libobjc.A.dylib 0x021c07cd -[NSObject performSelector:withObject:withObject:] + 84
    7 UIKit 0x0098279d -[UIApplication sendAction:to:from:forEvent:] + 99
    8 UIKit 0x00cf05c0 -[UIBarButtonItem(UIInternal) _sendAction:withEvent:] + 139
    9 libobjc.A.dylib 0x021c07cd -[NSObject performSelector:withObject:withObject:] + 84
    10 UIKit 0x0098279d -[UIApplication sendAction:to:from:forEvent:] + 99
    11 UIKit 0x0098272f -[UIApplication sendAction:toTarget:fromSender:forEvent:] + 64
    12 UIKit 0x00ab5a16 -[UIControl sendAction:to:forEvent:] + 69
    13 UIKit 0x00ab5e33 -[UIControl _sendActionsForEvents:withEvent:] + 598
    14 UIKit 0x00ab509d -[UIControl touchesEnded:withEvent:] + 660
    15 UIKit 0x009d2aba -[UIWindow _sendTouchesForEvent:] + 874
    16 UIKit 0x009d3595 -[UIWindow sendEvent:] + 791
    17 UIKit 0x00998aa9 -[UIApplication sendEvent:] + 242
    18 UIKit 0x009a88de _UIApplicationHandleEventFromQueueEvent + 20690
    19 UIKit 0x0097d079 _UIApplicationHandleEventQueue + 2206
    20 CoreFoundation 0x029b37bf __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 15
    21 CoreFoundation 0x029a92cd __CFRunLoopDoSources0 + 253
    22 CoreFoundation 0x029a8828 __CFRunLoopRun + 952
    23 CoreFoundation 0x029a81ab CFRunLoopRunSpecific + 443
    24 CoreFoundation 0x029a7fdb CFRunLoopRunInMode + 123
    25 GraphicsServices 0x04e5a24f GSEventRunModal + 192
    26 GraphicsServices 0x04e5a08c GSEventRun + 104
    27 UIKit 0x00980e16 UIApplicationMain + 1526
    28 ArrestsPlotter 0x0009692d main + 141
    29 libdyld.dylib 0x02501ac9 start + 1
    )
    libc++abi.dylib: terminating with uncaught exception of type NSException
    (lldb)
    Argus
[ 1 , 2 , 3 , 4 , 5 ]

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: Gaming!

    Loading ... Loading ...

Last week's winner: Apple TestFlight Tutorial.

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 January: WatchKit.

Sign Up - January

Our Books

Our Team

Tutorial Team

... 60 total!

Update Team

  • Riccardo D'Antoni

... 13 total!

Editorial Team

... 17 total!

Code Team

  • Orta Therox

... 3 total!

Subject Matter Experts

... 4 total!