MapQuest Navigation SDK provides a way to easily add turn by turn navigation in any app.
In order to use MapQuest APIs and SDKs you will need a MapQuest Key. We use this key to associate your requests to APIs with your account. You can find your existing Keys or create a new one at Applications page.
If you don't have a MapQuest Developer Account you can sign up for one. Sign Up Here.
You can get these for free from the Mac App Store. Download Xcode Here
You can use your personal devices or Xcode’s iOS Simulator for this guide without an Apple Developer Account. If you want to distribute your app to others, you’ll need to sign up and pay for an account. Sign Up Here
Download the zipped SDK that contains the framework and a resource bundle that need to be included in the project
Use the Navigation SDK within your own Xcode project using CocoaPods.
You need to include the following in the podfile:
source 'https://github.com/MapQuest/podspecs-ios.git'
pod 'MQNavigation', :path => '<path to folder containing MQNavigation.framework>'
To use the MapQuest SDK in your app, you need to place your MapQuest Key inside the app’s Info.plist file:
You will also need to allow arbitrary loads in your app. You can do this by adding NSAppTransportSecurity to your Info.plist with NSAllowsArbitraryLoads set to true. We are actively working on resolving this requirement.
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
Host app developers instantiate MQNavigationManager which provides delegate methods for navigation-related events. Events are triggered when:
The Objective-C and Swift examples here demonstrate how to create a route from New York City to Boston and start a turn-by-turn navigation session.
//include the umbrella header
#import "MQNavigation.h"
//declare properties for MQNavigationManager and MQRouteService
@property (nonatomic, strong) MQNavigationManager *navigationManager;
@property (nonatomic, strong) MQRouteService *routeService;
//instantiate MQNavigationManager and assign delegates.
self.navigationManager = [[MQNavigationManager alloc] init];
self.navigationManager.delegate = self;
self.navigationManager.promptDelegate = self;
//instantiate MQRouteService
self.routeService = [[MQRouteService alloc] init];
//set up start and destination for the route
CLLocation *nyc = [[CLLocation alloc] initWithLatitude:40.7326808 longitude:-73.9843407];
CLLocation *boston = [[CLLocation alloc] initWithLatitude:42.355097 longitude:-71.055464];
//set up route options
MQRouteOptions *options = [MQRouteOptions new];
//get route/s from MQRouteService
[self.routeService requestRoutesWithStartLocation:nyc destinationLocations:@[boston] options:options completion:^(NSArray * _Nullable routes, NSError * _Nullable error) {
if(error) {
NSLog(@"ERROR:%@",error);
} else if(routes) {
if (routes.count > 0) {
//start navigating with the route.
[self.navigationManager startNavigationWithRoute:routes[0]];
}
}
}];
//import the module
import MQNavigation`
//declare properties for MQNavigationManager and MQRouteService
fileprivate lazy var navigationManager: MQNavigationManager = {
let manager = MQNavigationManager()
manager.delegate = self
manager.promptDelegate = self
return manager
}()
fileprivate lazy var routeService = MQRouteService()
//set up start and destination for the route
fileprivate let nyc = CLLocation(latitude:40.7326808, longitude:-73.9843407)
fileprivate let boston = CLLocation(latitude:42.355097, longitude:-71.055464)
//request route
routeService.requestRoutes(withStart: nyc, destinationLocations:[boston], options: routeOptions) { [weak self] (routes, error) in
guard let strongSelf = self else { return }
//handle error
if let error = error {
let alert = UIAlertController(title: "Error", message: error.localizedDescription, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
strongSelf.present(alert, animated: true, completion: nil)
return
}
guard let routes = routes, routes.isEmpty == false else { return }
//start navigation with first route
strongSelf.navigationManager.startNavigation(with: routes[0])
}
MQNavigation provides a simple class for you to set route restrictions, system of measurement, and language locale when requesting a route.
Restrictions may be provided to influence the route path to avoid certain features like tolls, unpaved roads, and others. The following restriction types are available:
Each type can be set to one of: allow, avoid, or disallow.
System of measurement for Prompts may be changed to Metric or United States Customery by setting systemOfMeasurementForDisplayText to one of MQSystemOfMeasurementUnitedStatesCustomary or MQSystemOfMeasurementMetric.
Language allows the navigation prompts, instructions, and maneuvers to be localized in a supported language. Current supported languages include (default) US English en_US
and US Spanish es_US
.
MQRouteOptions *options = [MQRouteOptions new];
options.highways = MQRouteOptionTypeAllow;
options.tolls = MQRouteOptionTypeDisallow;
options.ferries = MQRouteOptionTypeAvoid;
options.unpaved = MQRouteOptionTypeAllow;
options.internationalBorders = MQRouteOptionTypeDisallow;
options.seasonalClosures = MQRouteOptionTypeAvoid;
options.maxRoutes = 3;
options.systemOfMeasurementForDisplayText = MQSystemOfMeasurementUnitedStatesCustomary;
options.language = @"en_US";
//build route options
let options = MQRouteOptions()
options.highways = .allow
options.tolls = .disallow
options.ferries = .avoid
options.unpaved = .allow
options.internationalBorders = .disallow
options.seasonalClosures = .avoid
options.maxRoutes = 3
options.systemOfMeasurementForDisplayText = .unitedStatesCustomary
options.language = "en_US"
As the user progresses along the route, the MQNavigation periodically queries a server to receive updated arrival time and traffic information based on current traffic conditions. Developers may capture these events to obtain and update the displayed traffic information and ETA.
//make your view controller conform to MQNavigationManagerDelegate protocol
@interface ViewController () <MQNavigationManagerDelegate> {
}
//this method is called before the network call is made to get new ETA
- (void)navigationManagerWillUpdateETA:(nonnull MQNavigationManager *)navigationManager {
//TODO: may be a spinner to show that something is happening in the background.
}
//This method will provide updated ETA.
- (void)navigationManagerDidUpdateETA:(nonnull MQNavigationManager *)navigationManager withETAByRouteLegId:(nonnull NSDictionary *)etaByRouteLegId {
//TODO: handle ETA update
}
//This method will provide details of errors if the network call to get new ETA fails for any reason.
- (void)navigationManager:(nonnull MQNavigationManager *)navigationManager failedToUpdateEtaWithError:(nonnull NSError *)error {
//TODO: failed to get an ETA update. Do something.
}
//this method is called before the network call is made to get new Traffic
- (void)navigationManagerWillUpdateTraffic:(MQNavigationManager *)navigationManager {
//TODO: may be a spinner to show that something is happening in the background.
}
//This method will provide updated Traffic for each leg of the route.
- (void)navigationManagerDidUpdateTraffic:(MQNavigationManager *)navigationManager withTrafficByRouteLegId:(NSDictionary *)trafficByRouteLegId {
//TODO: update UI to show new traffic details on the route.
}
//This method is called when a faster route is available that avoids traffic congestions.
- (void)navigationManager:(MQNavigationManager *)navigationManager foundTrafficReroute:(MQRoute *)route {
//TODO: ask the user if they want to follow a different route and start navigation with new route.
}
//This method will provide details of errors if the network call to get new Traffic fails for any reason.
- (void)navigationManager:(MQNavigationManager *)navigationManager failedToUpdateTrafficWithError:(nonnull NSError *)error {
//TODO: handle error
}
//make your view controller conform to MQNavigationManagerDelegate protocol
class ViewController: UIViewController, MQNavigationManagerDelegate{
}
//this method is called before the network call is made to get new ETA
func navigationManagerWillUpdateETA(_ navigationManager: MQNavigationManager) {
//TODO: may be a spinner to show that something is happening in the background.
}
//This method will provide updated ETA.
func navigationManagerDidUpdateETA(_ navigationManager: MQNavigationManager, withETAByRouteLegId etaByRouteLegId: [String : MQEstimatedTimeOfArrival]) {
//TODO: handle ETA update
}
//This method will provide details of errors if the network call to get new ETA fails for any reason.
func navigationManager(_ navigationManager: MQNavigationManager, failedToUpdateEtaWithError error: Error) {
//TODO: failed to get an ETA update. Do something.
}
//this method is called before the network call is made to get new Traffic
func navigationManagerWillUpdateTraffic(_ navigationManager: MQNavigationManager) {
//TODO: may be a spinner to show that something is happening in the background.
}
//This method will provide updated Traffic for each leg of the route.
func navigationManagerDidUpdateTraffic(_ navigationManager: MQNavigationManager, withTrafficByRouteLegId trafficByRouteLegId: [AnyHashable : Any]) {
//TODO: update UI to show new traffic details on the route.
}
//This method is called when a faster route is available that avoids traffic congestions.
func navigationManager(_ navigationManager: MQNavigationManager, foundTrafficReroute route: MQRoute) {
//TODO: ask the user if they want to follow a different route and start navigation with new route.
}
//This method will provide details of errors if the network call to get new Traffic fails for any reason.
func navigationManager(_ navigationManager: MQNavigationManager, failedToUpdateTrafficWithError error: Error) {
//TODO: handle error
}
As the user progresses along the route, Whenever the speed limit changes, a delegate method is invoked specifying the speed limit zones exited and entered. These examples demonstrate how to listen to speed limit changes and update a speed limit display.
//make your view controller conform to MQNavigationManagerDelegate protocol
@interface ViewController () <MQNavigationManagerDelegate> {
}
//Implement the delegate method and handle changes in speed limits.
- (void)navigationManager:(nonnull MQNavigationManager *)navigationManager crossedSpeedLimitBoundariesWithExitedZones:(nullable NSSet <MQSpeedLimit *> *)exitedSpeedLimits enteredZones:(nullable NSSet <MQSpeedLimit *> *)enteredSpeedLimits
{
// update UI to reflect new speeed limits.
}
//make your view controller conform to MQNavigationManagerDelegate protocol
class ViewController: UIViewController, MQNavigationManagerDelegate{
}
////Implement the delegate method and handle changes in speed limits.
func navigationManager(_ navigationManager: MQNavigationManager, crossedSpeedLimitBoundariesWithExitedZones exitedSpeedLimits: Set<MQSpeedLimit>?, enteredZones enteredSpeedLimits: Set<MQSpeedLimit>?) {
// update UI to reflect new speeed limits.
}
When MQNavigation detects that user has gone off-route, the host app is informed via an optional delegate method requesting permission to reroute. If the delegate method returns TRUE or does not exist MQNavigation will attempt a reroute request which if successful will invoke a delegate method specifying a new route from the current location to the destination. These examples demonstrate how to listen to off-route reroutes.
//called providing the host app the ability to choose whether a reroute occurs or not
- (BOOL)navigationManagerShouldReroute:(nonnull MQNavigationManager *)navigationManager {
return YES;
}
//called before network call is made to get a new route
-(void)navigationManagerWillReroute:(MQNavigationManager *)navigationManager {
//TODO: update UI to show that user has gone off the route
}
//called after MQNavigation gets a new route.
-(void)navigationManager:(MQNavigationManager *)navigationManager didReroute:(MQRoute *)route {
//TODO: update UI with new route
//TODO: start navigation using the new route
}
//if the user gets back on the route before a new route is acquired, new route is discarded
-(void)navigationManagerDiscardedReroute:(MQNavigationManager *)navigationManager {
//TODO: update UI to show that background network call is completed
}
//called when SDK fails to get a reroute.
-(void)navigationManager:(MQNavigationManager *)navigationManager failedToRerouteWithError:(NSError *)error {
//TODO: note that there was an error
}
//called providing the host app the ability to choose whether a reroute occurs or not
func navigationManagerShouldReroute(_ navigationManager: MQNavigationManager) -> Bool {
return true
}
//called before network call is made to get a new route
func navigationManagerWillReroute(_ navigationManager: MQNavigationManager) {
//TODO: update UI to show that user has gone off the route
}
//called after MQNavigation gets a new route.
func navigationManager(_ navigationManager: MQNavigationManager, didReroute route: MQRoute) {
//TODO: update UI with new route
//TODO: start navigation using the new route
}
//if the user gets back on the route before a new route is acquired, new route is discarded
func navigationManagerDiscardedReroute(_ navigationManager: MQNavigationManager) {
//TODO: update UI to show that backgrond network call is completed
}
//called when SDK fails to get a reroute.
func navigationManager(_ navigationManager: MQNavigationManager, failedToRerouteWithError error: Error) {
//TODO: note that there was an error
}
During navigation, when it is time to alert user of an upcoming maneuver or give an update on the route, a delegate method is invoked specifying Prompt object that is suppose to be spoken. Host app developers are responsible for using a TTS engine to speak the prompt. These examples demonstrate how to listen to Prompt updates.
//make your class conform to MQNavigationManagerPromptDelegate protocol
@interface ViewController () <MQNavigationManagerDelegate> {
}
//this method is called when it is time to speak the prompt.
- (void)navigationManager:(nonnull MQNavigationManager *)navigationManager receivedPrompt:(nonnull MQPrompt *)promptToSpeak userInitiated:(BOOL)userInitiated {
//TODO: use any Text To Speech engine to speak promptToSpeak.speech
//TODO: If the app is in background, post a local notification with promptToSpeak.text
}
//this is called when the current prompt (if there is one spoken) is not valid anymore.
- (void)cancelPromptsForNavigationManager:(nonnull MQNavigationManager *)navigationManager {
//TODO: stop speaking any prompt.
}
//make your class conform to MQNavigationManagerPromptDelegate protocol
class ViewController: UIViewController, MQNavigationManagerPromptDelegate {
}
//this method is called when it is time to speak the prompt.
func navigationManager(_ navigationManager: MQNavigationManager, receivedPrompt promptToSpeak: MQPrompt, userInitiated: Bool) {
//TODO: use any Text To Speech engine to speak promptToSpeak.speech
//TODO: If the app is in background, post a local notification with promptToSpeak.text
}
//this is called when the current prompt (if there is one spoken) is not valid anymore.
func cancelPrompts(for navigationManager: MQNavigationManager) {
//TODO: stop speaking any prompt.
}
Upon reaching a destination MQNavigationManager provides an optional delegate method which provides the destination reached and if it is the final destination. If you implement this delegate method you are required to call the acceptance block to continue to the next destination or finish navigation – otherwise MQNavigationManager will assume the destination has been accepted.
By default navigation automatically stops when reaching the final destination. For intermediate destinations, you may want to pause navigation and update the user-interface to allow the user to resume.
- (void)startNavigation {
//set up start and destination for the route
CLLocation *nyc = [[CLLocation alloc] initWithLatitude:40.7326808 longitude:-73.9843407];
CLLocation *boston = [[CLLocation alloc] initWithLatitude:42.355097 longitude:-71.055464];
CLLocation *paulRevereHouse = [[CLLocation alloc] initWithLatitude:42.3637 longitude:-71.0537];
//set up route options
MQRouteOptions *options = [MQRouteOptions new];
//get route/s from MQRouteService
[self.routeService requestRoutesWithStartLocation:nyc destinationLocations:@[boston, paulRevereHouse] options:options completion:^(NSArray<MQRoute *> * _Nullable routes, NSError * _Nullable error) {
if(error) {
NSLog(@"ERROR:%@",error);
} else if(routes) {
if (routes.count > 0) {
//start navigating with the route.
[self.navigationManager startNavigationWithRoute:routes[0]];
}
}
}];
}
//make your class conform to MQNavigationManagerPromptDelegate protocol
@interface ViewController () <MQNavigationManagerDelegate> {
}
- (void)navigationManager:(nonnull MQNavigationManager *)navigationManager reachedDestinationForRouteLeg:(nonnull MQRouteLeg *)completedRouteLeg isFinalDestination:(BOOL)isFinalDestination confirmArrival:(nonnull void(^)(BOOL didArrive))confirmArrival {
// Handle Destination
confirmArrival(YES);
}
func startNavigation() {
//set up start and destination for the route
fileprivate let nyc = CLLocation(latitude:40.7326808, longitude:-73.9843407)
fileprivate let boston = CLLocation(latitude:42.355097, longitude:-71.055464)
fileprivate let paulRevereHouse = CLLocation(latitude:42.3637, longitude:-71.0537)
//request route
routeService.requestRoutes(withStart: nyc, destinationLocations:[boston, paulRevereHouse], options: routeOptions) { [weak self] (routes, error) in
guard let strongSelf = self else { return }
//handle error
if let error = error {
let alert = UIAlertController(title: "Error", message: error.localizedDescription, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
strongSelf.present(alert, animated: true, completion: nil)
return
}
guard let routes = routes, routes.isEmpty == false else { return }
//start navigation with first route
strongSelf.navigationManager.startNavigation(with: routes[0])
}
}
//make your class conform to MQNavigationManagerPromptDelegate protocol
class ViewController: UIViewController, MQNavigationManagerPromptDelegate {
}
func navigationManager(_ navigationManager: MQNavigationManager, reachedDestinationFor completedRouteLeg: MQRouteLeg, isFinalDestination: Bool, confirmArrival: @escaping (Bool) -> Void) {
// Handle Destination
confirmArrival(true)
}
MQNavigation provides battery savings by letting you know if you've been running in the background, on battery, and without moving for a period of time. The framework will call you and you can decide what to do at that point. Some common options include:
/// Called when the app is in the background and navigating, but has not moved enough in a period of time
- (void)navigationManagerBackgroundTimerExpired:(nonnull MQNavigationManager *)navigationManager {
UNMutableNotificationContent* content = [UNMutableNotificationContent new];
content.title = @"Do you wish to continue navigating?";
content.body = @"You haven't moved in awhile while navigation has been in the background. Please let us know if you wish to continue navigating.";
content.sound = [UNNotificationSound default];
content.categoryIdentifier = @"BackgroundTimer";
UNNotificationRequest* request = [UNNotificationRequest initWithIdentifier: @"BackgroundTimerExpired" content: content trigger:nil];
// Schedule the notification
UNUserNotificationCenter* center = [UNUserNotificationCenter current];
[center removeAllPendingNotificationRequests];
[center addRequest:request withCompletionHandler:nil];
}
/// Called when the app is in the background and navigating, but has not moved enough in a period of time
func navigationManagerBackgroundTimerExpired(_ navigationManager: MQNavigationManager) {
let content = UNMutableNotificationContent()
content.title = "Do you wish to continue navigating?"
content.body = "You haven't moved in awhile while navigation has been in the background. Please let us know if you wish to continue navigating."
content.sound = UNNotificationSound.default()
content.categoryIdentifier = "BackgroundTimer"
let request = UNNotificationRequest(identifier: "BackgroundTimerExpired", content: content, trigger: nil)
// Schedule the notification.
let center = UNUserNotificationCenter.current()
center.removeAllPendingNotificationRequests()
center.add(request, withCompletionHandler: nil)
}
MQNavigationManager provides the NSError domain MQNavigationErrorDomain and a list of potential error codes listed in the MQNavigation SDK Documentation. Furthermore we also provide a set of error delegate methods that provide error information for situations such as:
Refer to the Navigation SDK Reference Sample Application code to see a complete example of how to leverage the MQNavigation iOS SDK to create turn-by-turn navigation experience.