DEV Community

Steven Rockarts
Steven Rockarts

Posted on

4 1

Using Your Custom Mapbox Map and Adding Navigation Using Swift

In the last post we created a custom map using Mapbox Studio, in this blog post we will use that custom made map in an iOS app written in Swift and add some Navigation to it.

If you would prefer to watch a screencast about this instead you can view it below.

First we need to create a new project in XCode. Go to File->New->Project (Single View App) and name your project ExploreOutdoors. Now in the directory you saved the project in we will need to create a PodFile so that we can install the Mapbox and Mapbox Navigation apis. My directory layout looks like this:

Directory layout

Create a file named PodFile and add the following code.

source 'https://github.com/CocoaPods/Specs.git'
project 'ExploreOutdoors.xcodeproj'
platform :ios, '11'
workspace 'ExploreOutdoors'
use_frameworks!
target 'ExploreOutdoors' do
pod 'Mapbox-iOS-SDK'
pod 'MapboxNavigation', '~> 0.18.1'
end
view raw gistfile1.txt hosted with ❤ by GitHub

Open up a terminal and run pod install to install the Mapbox and Navigation API.

Pod install

Now, make sure to close XCode and open up ExploreOutdoors.xcworkspace and not ExploreOutdoors.xcodeproj make sure you can build and run your app with the Mapbox pods installed.

If everything is working, in ViewController.swift add the following code:

import UIKit
import Mapbox
import MapboxNavigation
import MapboxCoreNavigation
import MapboxDirections
class ViewController: UIViewController {
var mapView: MGLMapView!
override func viewDidLoad() {
super.viewDidLoad()
mapView = MGLMapView(frame: view.bounds)
mapView.showsUserLocation = true
mapView.setUserTrackingMode(.follow, animated: true)
//PASTE YOUR STYLE URL IN THE STRING!
mapView.styleURL = URL(string:"")
view.addSubview(mapView)
}
}
view raw gistfile1.txt hosted with ❤ by GitHub

If you build and run your app right now it won't work, we need to add some entries to our Info.plist file. Lets add our mapbox key, you can get this by visiting the following URL: https://www.mapbox.com/install/ios/cocoapods-permission/ they even have a handy animated gif on how to add it.

Next while you are in the Info.plist we need to add 2 more entries. "Required background modes" and for the value type in audio. Also add in "Privacy - Location When In Use Usage Description" and tell the user that we need access to their location so we can show them on the map.

Infoplist entries

Now for the cool part! We will add our custom built map from the last blog post. Go to https://www.mapbox.com/studio/ and select the share and use button. Select iOS on the side and copy your style URL. Paste it underneath the comment that says //PASTE YOUR STYLE URL IN THE STRING!

Now you should be able to launch your app and see your custom maps being displayed! Check out my custom map of rock climbs in Jasper National Park!

rock climbs

Add Some Navigation

Now let's add navigation to the app. We are going to make it so the user can tap on the screen to add navigation points. For that add these two lines above your viewDidLoad() method:

var button: UIButton!
var points = CLLocationCoordinate2D

Now we will add a gesture recognizer to the map to recognize when the user taps on the map. Add the following code to your ViewController.

//
// ViewController.swift
// ExploreOutdoors
//
// Created by Steven Rockarts on 2018-09-01.
// Copyright © 2018 Figure4Software. All rights reserved.
//
import UIKit
import Mapbox
import MapboxNavigation
import MapboxCoreNavigation
import MapboxDirections
class ViewController: UIViewController {
var mapView: MGLMapView!
var button: UIButton!
var points = [CLLocationCoordinate2D]()
override func viewDidLoad() {
super.viewDidLoad()
mapView = MGLMapView(frame: view.bounds)
mapView.showsUserLocation = true
mapView.setUserTrackingMode(.follow, animated: true)
//PASTE YOUR CUSTOM STYLE URL HERE
mapView.styleURL = URL(string:"")
view.addSubview(mapView)
// Setup single tap gesture
let singleTap = UITapGestureRecognizer(target: self, action: #selector(handleMapTap(sender:)))
for recognizer in mapView.gestureRecognizers! where recognizer is UITapGestureRecognizer {
singleTap.require(toFail: recognizer)
}
mapView.addGestureRecognizer(singleTap)
}
// Handle single taps
@objc @IBAction func handleMapTap(sender: UITapGestureRecognizer) {
let point = sender.location(in: sender.view!)
let coordinate = mapView.convert(point, toCoordinateFrom: mapView)
points.append(coordinate)
let annotation = MGLPointAnnotation()
annotation.coordinate = coordinate
mapView.addAnnotation(annotation)
}
}

You should now be able to add annotations to your app by tapping the screen!

Adding annotations

Let's explain the code step by step:

This code sets up a single tap gesture recognizer, binds it to the method handleTap and then adds it to the mapview.

// Setup single tap gesture
let singleTap = UITapGestureRecognizer(target: self, action: #selector(handleMapTap(sender:)))
for recognizer in mapView.gestureRecognizers! where recognizer is UITapGestureRecognizer {
singleTap.require(toFail: recognizer)
}
mapView.addGestureRecognizer(singleTap)
view raw gesture.swift hosted with ❤ by GitHub

Now this code is really cool, it gets the point on the map where the user tapped, converts it from a point into a GPS coordinate and then creates an annotation and adds it to the map!

// Handle single taps
@objc @IBAction func handleMapTap(sender: UITapGestureRecognizer) {
let point = sender.location(in: sender.view!)
let coordinate = mapView.convert(point, toCoordinateFrom: mapView)
points.append(coordinate)
let annotation = MGLPointAnnotation()
annotation.coordinate = coordinate
mapView.addAnnotation(annotation)
}
view raw handletap.swift hosted with ❤ by GitHub

Alright, now once the user has added 2 points to the map we can give them navigation instructions.

Let's add a button to the screen in our viewDidLoad so that the user can trigger starting the navigation.

// Setup navigation button
button = UIButton(frame: CGRect(x: 20, y: 50, width: self.mapView.frame.width - 40, height: 45))
button.setTitle("Tap to add points", for: .disabled)
button.setTitle("Start navigation", for: .normal)
button.backgroundColor = UIColor(red:0.77, green:0.77, blue:0.77, alpha:1.0)
button.addTarget(self, action: #selector(startNavigation(sender:)), for: .touchUpInside)
button.isEnabled = false
mapView.addSubview(button)
view raw button.swift hosted with ❤ by GitHub

One thing to remember when adding buttons to mapviews, is that if you want them to appear above the map, you need to add them to the map subview after it has been added to the parent view.

Now let's handle the tap and fetch the Navigation instructions from Mapbox. Add this code to the ViewController.

// Begin navigation when navigation button is tapped
@objc func startNavigation(sender: UIButton) {
let options = NavigationRouteOptions(coordinates: points)
options.profileIdentifier = .automobile
Directions.shared.calculate(options) { (waypoints, routes, error) in
guard let route = routes?.first, error == nil else {
print(error!.localizedDescription)
return
}
let navigationController = NavigationViewController(for: route)
self.present(navigationController, animated: true, completion: nil)
}
}
view raw gistfile1.txt hosted with ❤ by GitHub

This code looks at our points and sets the options to .automobile for directions, then using those waypoints it calculates the best route and presents the NavigationViewController from Mapbox. One last modification we need to do is enable the navigation button if there are more than 2 points on the map by adding a couple lines to our handleMapTap methond

@objc @IBAction func handleMapTap(sender: UITapGestureRecognizer) {
let point = sender.location(in: sender.view!)
let coordinate = mapView.convert(point, toCoordinateFrom: mapView)
points.append(coordinate)
let annotation = MGLPointAnnotation()
annotation.coordinate = coordinate
mapView.addAnnotation(annotation)
//ADD THESE 2 LINES TO ENABLE THE NAVIGATION BUTTON
if (points.count) > 1 {
button.backgroundColor = UIColor(red:0.35, green:0.84, blue:0.84, alpha:1.0)
button.isEnabled = true
}
}

You should now be able to navigate in your app! I've posted the finished code at the bottom of this post. Let me know if you have any questions!

Adding annotations

Finished Code

import UIKit
import Mapbox
import MapboxNavigation
import MapboxCoreNavigation
import MapboxDirections
class ViewController: UIViewController {
var mapView: MGLMapView!
var button: UIButton!
var points = [CLLocationCoordinate2D]()
override func viewDidLoad() {
super.viewDidLoad()
mapView = MGLMapView(frame: view.bounds)
mapView.showsUserLocation = true
mapView.setUserTrackingMode(.follow, animated: true)
//ADD YOU CUSTOM MAP STYLE HERE
mapView.styleURL = URL(string:"")
view.addSubview(mapView)
// Setup single tap gesture
let singleTap = UITapGestureRecognizer(target: self, action: #selector(handleMapTap(sender:)))
for recognizer in mapView.gestureRecognizers! where recognizer is UITapGestureRecognizer {
singleTap.require(toFail: recognizer)
}
mapView.addGestureRecognizer(singleTap)
// Setup navigation button
button = UIButton(frame: CGRect(x: 20, y: 50, width: self.mapView.frame.width - 40, height: 45))
button.setTitle("Tap to add points", for: .disabled)
button.setTitle("Start navigation", for: .normal)
button.backgroundColor = UIColor(red:0.77, green:0.77, blue:0.77, alpha:1.0)
button.addTarget(self, action: #selector(startNavigation(sender:)), for: .touchUpInside)
button.isEnabled = false
mapView.addSubview(button)
}
// Begin navigation when navigation button is tapped
@objc func startNavigation(sender: UIButton) {
let options = NavigationRouteOptions(coordinates: points)
options.profileIdentifier = .automobile
Directions.shared.calculate(options) { (waypoints, routes, error) in
guard let route = routes?.first, error == nil else {
print(error!.localizedDescription)
return
}
let navigationController = NavigationViewController(for: route)
self.present(navigationController, animated: true, completion: nil)
}
}
// Handle single taps
@objc @IBAction func handleMapTap(sender: UITapGestureRecognizer) {
let point = sender.location(in: sender.view!)
let coordinate = mapView.convert(point, toCoordinateFrom: mapView)
points.append(coordinate)
let annotation = MGLPointAnnotation()
annotation.coordinate = coordinate
mapView.addAnnotation(annotation)
if (points.count) > 1 {
button.backgroundColor = UIColor(red:0.35, green:0.84, blue:0.84, alpha:1.0)
button.isEnabled = true
}
}
}

Top comments (0)

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay