Build a station finder, part 1

This guides takes a deeper dive into iOS and the Mapbox iOS SDK to build an interactive mapping application. If you’re new to the Mapbox iOS SDK then you might want to check out the API Overview first. If you’re new to iOS development, you might want to check out some beginner tutorials such as Apple’s Start Developing iOS Apps Today or the free tutorials at Ray Wenderlich’s Site.

This guide is the first in a four-part series.

Introduction

We’re going to create a rail station locator app for the Washington, DC Metro. All stations will be shown on the map with an option to filter stations by line color. We’ll also customize the tooltips that display when you tap a marker to show the lines for that station and a button to launch a web page showing the realtime arrivals.

Here’s what the app will look like:

Screenshot

Getting started

There are a few resources that you’ll need:

When working in Xcode, you may receive warnings or errors next to your code. It’s helpful to understand what the issues mean and how to debug them. Before you continue read about viewing issues in Xcode.

Setting up in Xcode

Open Xcode and create a new project. Choose the Single View Application template.

Name the Product Station Finder. Select Objective-C as the language and iPhone as the device.

This process will create a local folder to store all your files. Remember where you saved it as we’ll use it in the next step.

Installing the Mapbox SDK

We recommend that you install the Mapbox iOS SDK via CocoaPods. CocoaPods is a dependency manager for Objective-C applications that makes using third-party code very easy. You can install CocoaPods by running sudo gem install cocoapods in the command line.

If you don’t want to use CocoaPods and have some familiarity with using third-party frameworks in Xcode, you can follow the directions for installing the statically-linked framework binary or from source.

Creating a Podfile

Once you have CocoaPods installed, create a file called Podfile in the root of your project. (The root is the folder that contains the .xcodeproj file.)

Screenshot

Put the following in your Podfile to indicate which CocoaPods repository to fetch libraries from, what OS version you’re targeting, and to use the Mapbox SDK:

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
pod 'Mapbox-iOS-SDK'

Note: While the Mapbox iOS SDK is backwards-compatible to iOS 5, we’ll just stick with iOS 8 for now.

From your root folder, run the terminal command pod install. You should see the Mapbox iOS SDK install successfully along with its dependencies.

Open the Station Finder.xcworkspace file (not .xcodeproj) and we’re ready to start coding!

Putting a map on screen

Let’s begin by adding a simple map to our view using the RMMapView class. Open your ViewController.m file and replace it with the following code. This will import the Mapbox header and create a tile source and map view in our -viewDidLoad method:

#import "ViewController.h"
#import "Mapbox.h"

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

		[[RMConfiguration sharedInstance] setAccessToken:@"your-access-token"];

    RMMapboxSource *tileSource = [[RMMapboxSource alloc]
        initWithMapID:@"mapbox.emerald"];

    RMMapView *mapView = [[RMMapView alloc] initWithFrame:self.view.bounds
        andTilesource:tileSource];

    [self.view addSubview:mapView];
}

@end

Be sure to replace your-access-token with your own Mapbox API token. You can also change the map ID found in initWithMapID:@"mapbox.emerald"]; with your own.

This function is the default place to do view setup in an iOS app, so we have overridden it to customize our app, as well as called Apple’s superclass version to perform the regular setup that happens in every iOS app.

Build and run by clicking the button in the top left of Xcode’s window and you should see a fullscreen map in the iOS Simulator:

Screenshot

Setting up the navigation controller

Let’s make the default view controller appear inside of a navigation controller. This will give us a navigation bar to display the the title of the app and a filter button.

Open Main.storyboard and click on the root view controller (the only view controller in the storyboard so far). From the toolbar choose Editor → Embed In → Navigation Controller to put our view controller inside of a navigation controller.

Click on the view controller and give it a title of “DC Metro Stations”.

You should end up with something like this:

Xcode

Specifying center and zoom level

In the code above, we created our RMMapView with the -initWithFrame:andTileSource: method (another word for function in Objective-C) for simplicity. By doing so, we centered and zoomed the map using the values declared in our map ID. You can edit these values online in the same place you made and edited the map in the first place. You can also override these settings using an alternate initializer and specifying the values that you want to use.

At the top of your ViewController.m file, declare a private property for our RMMapView so we can refer to it outside of -viewDidLoad later:

@interface ViewController ()
@property (nonatomic, strong) RMMapView *mapView;
@end

Now let’s change the constructor method we use to instantiate the RMMapView. Update your -viewDidLoad method to look like this:

- (void)viewDidLoad
{
    [super viewDidLoad];

		[[RMConfiguration sharedInstance] setAccessToken:@"your-access-token"];

    RMMapboxSource *tileSource = [[RMMapboxSource alloc]
        initWithMapID:@"mapbox.emerald"];

    CLLocationCoordinate2D center = CLLocationCoordinate2DMake(38.910003, -77.015533);

    self.mapView = [[RMMapView alloc] initWithFrame:self.view.bounds
	    andTilesource:tileSource];

    self.mapView.zoom = 11;
		self.mapView.centerCoordinate = center;

    [self.view addSubview:self.mapView];
}

Since we’ve declared mapView as a property of ViewController, we refer to it as self.mapView in order to properly set and alter that property.

Build and run, and you should see our map centered on Washington, DC and zoomed out so that you can see most of the area:

Handling rotation

We want to make sure that when the device is rotated that the map expands to fill the screen. If you rotate the simulator currently (using the Hardware → Rotate Right menu command), the map view will remain the same width and height that we gave it originally, which was the size of the view controller’s main view. The simplest way to do this is by using view resizing masks.

Set the resizing mask on your map view someplace in the -viewDidLoad method:

self.mapView.autoresizingMask = UIViewAutoresizingFlexibleHeight |
    UIViewAutoresizingFlexibleWidth;

Build and run, then rotate the simulator. This time your map should resize to fit the screen:

Preventing panning outside of DC

Since our map is going to be a Washington, DC Metro station finder, we don’t want to let the user pan the map very far outside of the relevant area. We can restrict panning outside of a set area using the -setConstraintsSouthWest:northEast: method of RMMapView.

At the end of our -viewDidLoad method, add these lines to create an area that the user cannot pan outside of:

CLLocationCoordinate2D southWest = CLLocationCoordinate2DMake(38.560314,
    -77.370506);
CLLocationCoordinate2D northEast = CLLocationCoordinate2DMake(39.357147,
    -76.793182);
[self.mapView setConstraintsSouthWest:southWest northEast:northEast];

We found these coordinates by some quick trial-and-error; making sure all our stations are shown in these boundaries.

Build and run, then zoom out and try to pan the map outside of DC. You’ll notice that it hits the boundaries that we defined and stops.

Continue to part 2

We accomplished a lot in this first part! We set up a map, set the center point, and locked the panning to stay in the DC area.

Here’s what your ViewController.m file should look like at the end of part 1:

#import "ViewController.h"
#import "Mapbox.h"

@interface ViewController ()
@property (nonatomic, strong) RMMapView *mapView;
@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

		[[RMConfiguration sharedInstance] setAccessToken:@"your-access-token"];

    RMMapboxSource *tileSource = [[RMMapboxSource alloc]
        initWithMapID:@"mapbox.emerald"];

    CLLocationCoordinate2D center = CLLocationCoordinate2DMake(38.910003,
        -77.015533);

    self.mapView = [[RMMapView alloc] initWithFrame:self.view.bounds
        andTilesource:tileSource];

    [self.view addSubview:self.mapView];

    self.mapView.autoresizingMask = UIViewAutoresizingFlexibleHeight |
        UIViewAutoresizingFlexibleWidth;

    [self.view addSubview:self.mapView];

    self.mapView.zoom = 11;
    self.mapView.centerCoordinate = center;

    // Prevent panning outside the bounds of DC
    CLLocationCoordinate2D southWest = CLLocationCoordinate2DMake(38.560314,
        -77.370506);
    CLLocationCoordinate2D northEast = CLLocationCoordinate2DMake(39.357147,
        -76.793182);

    [self.mapView setConstraintsSouthWest:southWest northEast:northEast];

}
@end

In part 2, we will add markers to our map by parsing a GeoJSON file.

Additional questions? Ask our support team or learn more about How Mapbox Works.