"How to" for the iOS SDK

Prerequisites

  • Xcode version: Xcode 6 or above (Xcode 7 or above for Swift projects)
  • Architectures: armv7, arm64
  • Deployment Target: iOS 8.0. If your app runs on iOS 7, download the static library from here.
  • Memory management: ARC (Automatic Reference Counting)

Integrating the SDK

The downloadable zip archive contains:

  • Objective-C/Swift Demo with SDK as module
  • [alternative to the Objective-C demo] Swift Demo with SDK as module
  • map resources bundle
  • audio advisor resources bundle
  • a demo project illustrating different features
  • SDKTools, an open-source component for navigation UI and offline packages download.
  • SKOneBoxSearch and SKOSearchLib, open-source components providing a nice way of using our one line search functionality with support for third party search providers
  • one box (one line) resources bundle

Adding the SDK to the project as binary

The steps for integrating the SDK as binary:

Common steps:

  • Drag the SKMaps.framework module to your project. When prompted, select Copy items into destination group's folder.
  • Go to the General settings tab of your target settings and add the framework to the Embedded Binaries section.
  • Drag the SKMapsResources/SKMaps.bundle to your project. When prompted, select Copy items into destination group's folder. Optionally, drag the SKMapsResources/SKAdvisorResources.bundle to the project, if audio advices for turn by turn navigation are needed.
  • For iOS9, please set the NSAllowsArbitraryLoads key to YES under NSAppTransportSecurity dictionary in your .plist file.
  • Import the module using @import SKMaps in order to use the library.

If you are integrating the SDK as a static library, the following steps are required:

  • Select the project and choose your target. Open the Build Phases tab and add the following frameworks in the Link Binary with Libraries section:
    • UIKit.framework
    • Foundation.framework
    • CoreLocation.framework
    • OpenGLES.framework
    • QuartzCore.framework
    • CoreGraphics.framework
    • CoreMotion.framework
    • libz.dylib
    • libc++.dylib
  • Select the project and choose your target. Choose the Build settings tab and in the Other linker flags option introduce -ObjC.

Adding the SDK to the project via Cocoapods

  • Install Cocoapods dependency manager (instructions are available at http://cocoapods.org/).
  • Add a line containing pod 'ScoutMaps-iOS-SDK' to your Podfile.
  • Make sure that use_frameworks! flag is added to your Podfile
  • Run pod install in the Terminal.
  • Import the module using @import SKMaps in order to use the library.

Setting the API Key & initializing the library

SKMapsService thumbnail

Note: the mobile SDK API key can be visualised/requested here.

The SKMaps Framework must be initialized in AppDelegate's, application:didFinishLaunchingWithOptions: method, using initializeSKMapsWithAPIKey:settings: method from the SKMapsService class.

Objective-C

[[SKMapsService sharedInstance] initializeSKMapsWithAPIKey:@"API_KEY" settings:settings];

Swift

SKMapsService.sharedInstance().initializeSKMapsWithAPIKey("API_KEY", settings: initSettings)

The initialization settings provide the flexibility to set custom caches path, startup connectivity mode, proxy settings and others.

If the application doesn't call the initialization method before using other framework API, it will crash throwing the following exception message: “Missing API key.The API key wasn't set!

In order to receive GPS locations, the following key must be added in the project's plist file: NSLocationWhenInUseUsageDescription. The value of the key will be displayed to the user when he accepts/declines location updates for the application.If the key and the value are not added to the plist file, the user will not be asked for permission and the current position will not be available in the SDK.

Please see https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html for more information.

Using the MapCreator settings file

The Map Creator feature can be used for configuring map’s basic visualization settings (labels internationalization, bicycle lanes, one way arrows, etc.) through a configuration JSON file generated using the web interface. After the JSON file is generated and saved, it should be added to the application bundle. The following code snippet configures the map using a configuration file from the application’s bundle:

Objective-C

NSString *mapCreatorFilePath = [[NSBundle mainBundle] pathForResource:@"mapCreatorFile" ofType:@"JSON"];
[mapView applySettingsFromFileAtPath:mapCreatorFilePath];

Swift:

let mapCreatorFilePath = Bundle.main.path(forResource: "mapCreatorFile", ofType: "JSON")
mapView .applySettingsFromFile(mapCreatorFilePath)

Displaying a map

SKMapView thumbnail

Adding a map to an existing UIViewController consists of two steps: creating a SKMapView instance and adding it as a subview to an existing UIView:

Objective-C

#import <SKMaps/SKMaps.h>
@implementation MapDisplayViewController
- (void)viewDidLoad{
    [super viewDidLoad];
    SKMapView *mapView = [[SKMapView alloc] initWithFrame:CGRectMake( 0.0f, 0.0f,  CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame) )];
    [self.view addSubview:mapView];
}

Swift

import SKMaps
class MapDisplayViewController : UIViewController {
    override func viewDidLoad()
    super.viewDidLoad()
    let mapView = SKMapView(frame: CGRectMake(0.0, 0.0, CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame)))
    self.view.addSubview(mapView)
}
}

Map settings

The SKMapView instances can be configured through the settings property. The following code disables the rotation and sets the follower mode. For further details about the SKMapView’s settings see the SKMapSettings class.

Objective-C

mapView.settings.rotationEnabled = NO;
mapView.settings.followUserPosition = YES;
mapView.settings.headingMode = SKHeadingModeRotatingMap;

Swift

mapView.settings.rotationEnabled = false;
mapView.settings.followUserPosition = true;
mapView.settings.headingMode = SKHeadingModeRotatingMap;

Configuring visual elements

The SKMapView has two additional visual elements: the compass and the scale views.

The compass is used for indicating the bearing of the map. By default it is hidden. When it becomes visible, it appears in the top right corner of the map view. Using the compassOffset property of the SKMapView’s settings, the compass’ position can be changed.

The scale view of the map displays the ratio of a distance on a map to the corresponding distance on the ground.

Objective-C

#import <SKMaps/SKMaps.h>
@implementation MapDisplayViewController
- (void)viewDidLoad{
    [super viewDidLoad];
    SKMapView *mapView =  [[SKMapView alloc] initWithFrame:CGRectMake( 0.0f, 0.0f,  CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame) )];
    mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    [self.view addSubview:mapView];
    //show the compass
    mapView.settings.showCompass = YES;
    //hide the map scale
    mapView.mapScaleView.hidden = NO;
}

Swift

import SKMaps
class MapDisplayViewController : UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad();
        let mapView = SKMapView(frame: CGRectMake(0.0, 0.0, CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame)))
        mapView.autoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight
        self.view.addSubview(mapView)
        //show the compass
        mapView.settings.showCompass = true
        //hide the map scale
        mapView.mapScaleView.hidden = false
    }
}

Map internationalization settings

The map internationalization feature is used to customize the labeling of the map. First the primary option will be applied for displaying the name of the map elements (country, city, street names) on the first row. If it doesn't exist then the second option will be applied. For displaying both rows, the showBothOptions property should be set to YES/true.

Using the example below the labels of the map will appear in local format. If some of the map elements don't have local format then the secondary option will be applied for them, they will appear in English. If these map elements don't exist in English they will appear in German. By specifying that both options should be displayed, map elements having both representation will appear on the map in two rows.

The map view supports several internationalization languages: English, German, French, Italian, Spanish, Russian, Turkish and local.

Objective-C

SKMapInternationalizationSettings *internationalizationSettings = [SKMapInternationalizationSettings mapInternationalization];
internationalizationSettings.primaryOption = SKMapInternationalizationOptionLocal;
internationalizationSettings.fallbackOption = SKMapInternationalizationOptionInternational;
internationalizationSettings.primaryInternationalLanguage = SKMapLanguageEN;
internationalizationSettings.fallbackInternationalLanguage = SKMapLanguageDE;
internationalizationSettings.showBothOptions = YES;
mapView.settings.mapInternationalization = internationalizationSettings;

Swift

let internationalizationSettings = SKMapInternationalizationSettings.mapInternationalization()
internationalizationSettings.primaryOption = SKMapInternationalizationOption.Local
internationalizationSettings.fallbackOption = SKMapInternationalizationOption.International
internationalizationSettings.primaryInternationalLanguage = SKMapLanguage.EN
internationalizationSettings.fallbackInternationalLanguage = SKMapLanguage.DE
internationalizationSettings.showBothOptions = true
mapView.settings.mapInternationalization = internationalizationSettings

Observing map events

The SKMapViewDelegate protocol is responsible for sending map related events to the client application, such as visible region changes, gesture events, etc.

Check the documentation of the SKMapViewDelegate for further information.

Objective-C

#import <SKMaps/SKMaps.h>
@implementation MapDisplayViewController
- (void)viewDidLoad{
    [super viewDidLoad];
    SKMapView *mapView = [[SKMapView alloc]initWithFrame:[[SKMapView alloc] initWithFrame:CGRectMake( 0.0f, 0.0f,  CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame) )];
    mapView.delegate = self;
    [self.view addSubview:mapView];
}
- (void)mapView:(SKMapView*)mapView didChangeToRegion:(SKCoordinateRegion)region{
}
- (void)mapView:(SKMapView*)mapView didTapAtCoordinate:(CLLocationCoordinate2D)coordinate{
}
- (void)mapView:(SKMapView*)mapView didRotateWithAngle:(float)angle{
}

Swift

import SKMaps
class MapDisplayViewController : UIViewController, SKMapViewDelegate {
override func viewDidLoad() {
    super.viewDidLoad();
    let mapView = SKMapView(frame: CGRectMake(0.0, 0.0, CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame)))
    mapView.delegate = self
    self.view.addSubview(mapView)
}
func mapView(mapView: SKMapView!, didChangeToRegion region:SKCoordinateRegion) {
}
func mapView(mapView: SKMapView!, didTapAtCoordinate coordinate:CLLocationCoordinate2D) {
}
func mapView(mapView: SKMapView!, didRotateWithAngle angle:float) {
}
}

Adding annotations

Map annotations can be used to mark specific places. The SKAnnotation class provides the interface for configuring any annotation before it will be added to the map view. The SKAnnotation supports predefined types, PNG images or native UIViews. Use the addAnnotation:withAnimationSettings: method of the SKMapView to add an annotation on the map.

Objective-C

#import <SKMaps/SKMaps.h>
@implementation MapDisplayViewController
- (void)viewDidLoad{
    [super viewDidLoad];

    mapView = [[SKMapView alloc] initWithFrame:CGRectMake( 0.0f, 0.0f,  CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame) )];
    [self.view addSubview: mapView];
    //set the map region
    SKCoordinateRegion region;
    region.center = CLLocationCoordinate2DMake(52.5233, 13.4127);
    region.zoomLevel = 17;
    mapView.visibleRegion = region;

    //Annotation with type
    SKAnnotation *annotation = [SKAnnotation annotation];
    annotation.identifier = 10;
    annotation.annotationType = SKAnnotationTypePurple;
    annotation.location = CLLocationCoordinate2DMake(52.5237, 13.4137);
    SKAnimationSettings *animationSettings = [SKAnimationSettings animationSettings];
    [mapView addAnnotation:annotation withAnimationSettings:animationSettings];

    //Annotation with view
    //create our view
    UIImageView *coloredView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, 128.0, 128.0)];
    coloredView.image = [UIImage imageNamed:@"picture.png"];

    //create the SKAnnotationView
    SKAnnotationView *view = [[SKAnnotationView alloc] initWithView:coloredView reuseIdentifier:@"viewID"];

    //create the annotation
    SKAnnotation *viewAnnotation = [SKAnnotation annotation];
    //set the custom view
    viewAnnotation.annotationView = view;
    viewAnnotation.identifier = 100;
    viewAnnotation.location = CLLocationCoordinate2DMake(52.5240, 13.4107);
    [mapView addAnnotation:viewAnnotation withAnimationSettings:animationSettings];
}
-(void)mapView:(SKMapView *)mapView didSelectAnnotation:(SKAnnotation *)annotation{
    mapView.calloutView.titleLabel.text = @"Annotation";
    [mapView showCalloutForAnnotation:annotation withOffset:CGPointMake(0, 42) animated:YES];
}

Swift

import SKMaps
class MapDisplayViewController : UIViewController {
    var mapView: SKMapView!
    override func viewDidLoad() {
        super.viewDidLoad()
        mapView = SKMapView(frame: CGRectMake(0.0, 0.0, CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame)))
        self.view.addSubview(mapView)
        //set the map region
        var region = SKCoordinateRegion(center: CLLocationCoordinate2DMake(52.5233, 13.4127), zoomLevel: 17)
        mapView.visibleRegion = region

        //Annotation with type
        let annotation = SKAnnotation()
        annotation.identifier = 10
        annotation.annotationType = SKAnnotationType.Purple
        annotation.location = CLLocationCoordinate2DMake(52.5237, 13.4137)
        let animationSettings = SKAnimationSettings()
        mapView.addAnnotation(annotation, withAnimationSettings: animationSettings)

        //Annotation with view
        //create our view
        let coloredView = UIImageView(frame: CGRectMake(0.0, 0.0, 128.0, 128.0))
        coloredView.image = UIImage(named: "picture.png")

        //create the SKAnnotationView
        let view = SKAnnotationView(view: coloredView, reuseIdentifier: "viewID")

        //create the annotation
        let viewAnnotation = SKAnnotation()
        //set the custom view
        viewAnnotation.annotationView = view
        viewAnnotation.identifier = 100
        viewAnnotation.location = CLLocationCoordinate2DMake(52.5240, 13.4107)
        mapView.addAnnotation(viewAnnotation, withAnimationSettings: animationSettings)
    }
    func mapView(mapView:SKMapView!, didSelectAnnotation annotation:SKAnnotation!) {
        mapView.calloutView.titleLabel.text = "Annotation";
        mapView.showCalloutForAnnotation(annotation, withOffset: CGPointMake(0, 42), animated: true);
    }
}

Adding overlays

Overlays can be used to mark specific areas from the map. The overlay types that can be added are polylines, polygons or circles. For customizing these elements, use the SKPolyline, SKPolygon and SKCircle classes.

Objective-C

#import <SKMaps/SKMaps.h>
@implementation MapDisplayViewController
- (void)viewDidLoad{
    [super viewDidLoad];
    SKMapView *mapView = [[SKMapView alloc] initWithFrame:CGRectMake( 0.0f, 0.0f,  CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame) )];
    [self.view addSubview:mapView];

    //set the map region
    SKCoordinateRegion region;
    region.center = CLLocationCoordinate2DMake(52.5233, 13.4127);
    region.zoomLevel = 17;
    mapView.visibleRegion = region;

    //add a circle overlay
    SKCircle *circle = [SKCircle circle];
    circle.identifier = 1;
    circle.centerCoordinate = CLLocationCoordinate2DMake(52.5263, 13.4087);
    circle.radius = 100.0f;
    circle.fillColor = [UIColor redColor];
    circle.strokeColor = [UIColor blueColor];
    circle.isMask = NO;
    [mapView addCircle:circle];

    //add a rhombus overlay with dotted border
    CLLocation *rhombusVertex1 = [[CLLocation alloc]initWithLatitude:52.5253 longitude:13.4092];
    CLLocation *rhombusVertex2 = [[CLLocation alloc]initWithLatitude:52.5233 longitude:13.4077];
    CLLocation *rhombusVertex3 = [[CLLocation alloc]initWithLatitude:52.5213 longitude:13.4092];
    CLLocation *rhombusVertex4 = [[CLLocation alloc]initWithLatitude:52.5233 longitude:13.4117];
    SKPolygon *rhombus = [SKPolygon polygon];
    rhombus.identifier = 2;
    rhombus.coordinates = @[rhombusVertex1, rhombusVertex2, rhombusVertex3, rhombusVertex4];
    rhombus.fillColor = [UIColor blueColor];
    rhombus.strokeColor = [UIColor greenColor];
    rhombus.borderWidth = 5;
    rhombus.borderDotsSize = 20;
    rhombus.borderDotsSpacingSize = 5;
    rhombus.isMask = NO;
    [mapView addPolygon:rhombus];

    //adding a polyline with the same coordinates as the polygon
    SKPolyline *polyline = [SKPolyline polyline];
    polyline.identifier = 3;
    polyline.coordinates = @[rhombusVertex1, rhombusVertex2, rhombusVertex3, rhombusVertex4];
    polyline.fillColor = [UIColor redColor];
    polyline.lineWidth = 10;
    polyline.backgroundLineWidth = 2;
    polyline.borderDotsSize = 20;
    polyline.borderDotsSpacingSize = 5;
    [mapView addPolyline:polyline];
}

Swift

import SKMaps
class MapDisplayViewController : UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        mapView = SKMapView(frame: CGRectMake(0.0, 0.0, CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame)))
        self.view.addSubview(mapView)

        //set the map region
        var region = SKCoordinateRegion(center: CLLocationCoordinate2DMake(52.5233, 13.4127), zoomLevel: 17)
        mapView.visibleRegion = region

        //add a circle overlay
        let circle = SKCircle()
        circle.identifier = 1
        circle.centerCoordinate = CLLocationCoordinate2DMake(52.5263, 13.4087)
        circle.radius = 100.0
        circle.fillColor = UIColor.redColor()
        circle.strokeColor = UIColor.blueColor()
        circle.isMask = false
        mapView.addCircle(circle)

        //add a rhombus overlay with dotted border
        let rhombusVertex1 = CLLocation(latitude: 52.5253, longitude: 13.4092)
        let rhombusVertex2 = CLLocation(latitude: 52.5233, longitude: 13.4077)
        let rhombusVertex3 = CLLocation(latitude: 52.5213, longitude: 13.4092)
        let rhombusVertex4 = CLLocation(latitude: 52.5233, longitude: 13.4117)
        let rhombus = SKPolygon()
        rhombus.identifier = 2
        rhombus.coordinates = [rhombusVertex1, rhombusVertex2, rhombusVertex3, rhombusVertex4]
        rhombus.fillColor = UIColor.blueColor()
        rhombus.strokeColor = UIColor.greenColor()
        rhombus.borderWidth = 5
        rhombus.borderDotsSize = 20
        rhombus.borderDotsSpacingSize = 5;
        rhombus.isMask = false
        mapView.addPolygon(rhombus)

        //adding a polyline with the same coordinates as the polygon
        let polyline = SKPolyline()
        polyline.identifier = 3
        polyline.coordinates = [rhombusVertex1, rhombusVertex2, rhombusVertex3, rhombusVertex4]
        polyline.fillColor = UIColor.redColor()
        polyline.lineWidth = 10
        polyline.backgroundLineWidth = 2
        polyline.borderDotsSize = 20
        polyline.borderDotsSpacingSize = 5
        mapView.addPolyline(polyline)
    }
}

Changing the map style

The map style represents a set of predefined colors and display rules. It consists of a JSON file and a set of graphic resources.

The SKMaps.bundle contains four basic styles that can be applied to the map and also custom styles can be defined. The SKMapView+Style category provides the interface for managing map styles.

Objective-C

#import <SKMaps/SKMaps.h>
@implementation MapDisplayViewController
- (void)viewDidLoad{
    [super viewDidLoad];
    SKMapView *mapView =[[SKMapView alloc] initWithFrame:CGRectMake( 0.0f, 0.0f,  CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame) )];
    [self.view addSubview:mapView];
    SKMapViewStyle *mapViewStyle = [SKMapViewStyle mapViewStyle];
    mapViewStyle.resourcesFolderName = @"NightStyle";
    mapViewStyle.styleFileName = @"nightstyle.json";
    [SKMapView setMapStyle:mapViewStyle];
}

Swift

import SKMaps
 class MapDisplayViewController : UIViewController {
     override func viewDidLoad () {
         super.viewDidLoad()
         let mapView = SKMapView(frame: CGRectMake( 0.0, 0.0, CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame)))
         self.view.addSubview(mapView)
         let mapViewStyle = SKMapViewStyle()
         mapViewStyle.resourcesFolderName = "NightStyle"
         mapViewStyle.styleFileName = "nightstyle.json"
         SKMapView.setMapStyle(mapViewStyle)
     }
 }

For customizing and creating your own style you can use the StyleEditor tool (contact us if you want to fully configure the style of the map based on your brand).

Check our StyleEditor & Styling documentation to experience the full power of custom map styles

Using RealReach

The SKMapView class contains APIs for calculating and rendering the RealReach layer on the map, in the SKMapView+RealReach category. RealReach is used to provide visual information about the reachable area around a specified position, considering the transport mode (pedestrian, bike, car) and the measurement unit (time, distance, energy).

Objective-C

#import <SKMaps/SKMaps.h>
@implementation MapDisplayViewController
- (void)viewDidLoad{
    [super viewDidLoad];

    SKMapView *mapView = [[SKMapView alloc] initWithFrame:CGRectMake( 0.0f, 0.0f,  CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame) )];
    [self.view addSubview:mapView];

    //Set the map region
    SKCoordinateRegion region;
    region.center = CLLocationCoordinate2DMake(52.5233, 13.4127);
    region.zoomLevel = 13;
    mapView.visibleRegion = region;
    //Display the real reach layer
    SKRealReachSettings *realReachSettings = [SKRealReachSettings realReachSettings];
    realReachSettings.centerLocation = CLLocationCoordinate2DMake(52.5233, 13.4127);
    realReachSettings.transportMode = SKTransportCar;
    realReachSettings.unit = SKRealReachUnitSecond;
    realReachSettings.connectionMode = SKRouteConnectionOffline;
    realReachSettings.range = 300; //5 min
    [mapView displayRealReachWithSettings:realReachSettings];
}

Swift

import SKMaps
class MapDisplayViewController : UIViewController {
    override func viewDidLoad () {
        super.viewDidLoad()
        let mapView = SKMapView(frame: CGRectMake( 0.0, 0.0, CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame)))
        self.view.addSubview(mapView)

        //Set the map region
        let region = SKCoordinateRegion(center: CLLocationCoordinate2DMake(52.5233, 13.4127), zoomLevel: 13)
        mapView.visibleRegion = region
        //Display the real reach layer
        let realReachSettings = SKRealReachSettings()
        realReachSettings.centerLocation = CLLocationCoordinate2DMake(52.5233, 13.4127)
        realReachSettings.transportMode = SKTransportMode.Car
        realReachSettings.unit = SKRealReachUnit.Second
        realReachSettings.connectionMode = SKRouteConnectionMode.Offline
        realReachSettings.range = 300 //5 min
        mapView.displayRealReachWithSettings(realReachSettings)
    }
}

Calculating & displaying a route

SKRoutingService thumbnail

The route calculations are managed by the SKRoutingService singleton class. The SKRoutingDelegate protocol provides information about the result of a route calculation through its delegate methods. The SKRouteSettings model class stores various settings that will influence the result of the route calculation.

Objective-C

#import <SKMaps/SKMaps.h>
@implementation RoutingViewController<SKRoutingDelegate>
- (void)viewDidLoad{
    [super viewDidLoad];
    SKMapView *mapView =[[SKMapView alloc] initWithFrame:CGRectMake( 0.0f, 0.0f,  CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame) )];
    [SKRoutingService sharedInstance].routingDelegate = self; // set for receiving routing callbacks
    [SKRoutingService sharedInstance].mapView = mapView; // use the map view instance for route rendering
    SKRouteSettings* route = [[SKRouteSettings alloc]init];
    route.startCoordinate=CLLocationCoordinate2DMake(37.447692, -122.166016);
    route.destinationCoordinate=CLLocationCoordinate2DMake(37.437964, -122.141147);
    route.shouldBeRendered = YES; // If NO, the route will not be rendered.
    route.routeMode = SKRouteCarFastest;
    route.maximumReturnedRoutes = 1;
    SKRouteRestrictions routeRestrictions = route.routeRestrictions;
    routeRestrictions.avoidHighways = YES;
    route.routeRestrictions = routeRestrictions;
    [[SKRoutingService sharedInstance] calculateRoute:route];
}
- (void)routingService:(SKRoutingService *)routingService didFinishRouteCalculationWithInfo:(SKRouteInformation*)routeInformation{
    NSLog(@"Route is calculated.");
    [routingService zoomToRouteWithInsets:UIEdgeInsetsZero duration:1]; // zoom to current route
}
- (void)routingServiceDidFailRouteCalculation:(SKRoutingService *)routingService{
    NSLog(@"Route calculation failed.");
}
@end

Swift

import SKMaps
class RoutingViewController : UIViewController, SKRoutingDelegate {
    override func viewDidLoad () {
        super.viewDidLoad()
        let mapView = SKMapView(frame: CGRectMake( 0.0, 0.0, CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame)))
        self.view.addSubview(mapView)
        SKRoutingService.sharedInstance().routingDelegate = self // set for receiving routing callbacks
        SKRoutingService.sharedInstance().mapView = mapView // use the map view instance for route rendering

        let route = SKRouteSettings()
        route.startCoordinate = CLLocationCoordinate2DMake(37.447692, -122.166016)
        route.destinationCoordinate = CLLocationCoordinate2DMake(37.437964, -122.141147)
        route.shouldBeRendered = true // If false, the route will not be rendered.
        route.routeMode = SKRouteMode.CarFastest
        route.maximumReturnedRoutes = 1
        route.routeRestrictions.avoidHighways = true
        SKRoutingService.sharedInstance().calculateRoute(route)
    }
    func routingService(routingService: SKRoutingService!, didFinishRouteCalculationWithInfo routeInformation: SKRouteInformation!) {
        NSLog("Route is calculated")
        routingService .zoomToRouteWithInsets(UIEdgeInsetsZero, duration)
    }

    func routingService(routingService: SKRoutingService!, didFailWithErrorCode errorCode: SKRoutingErrorCode) {
        NSLog("Route calculation failed")
    }
}

Dealing with alternative routes

The SKRoutingService class also provides support to calculate multiple routes (alternatives). It can be enabled using the maximumReturnedRoutes property of SKRouteSettings. If calculated routes will be too similar, only the relevant ones will be provided.

Objective-C

#import <SKMaps/SKMaps.h>
@implementation RoutingViewController<SKRoutingDelegate>
- (void)viewDidLoad{
    [super viewDidLoad];
    SKMapView *mapView = [[SKMapView alloc] initWithFrame:CGRectMake( 0.0f, 0.0f,  CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame) )];

    [SKRoutingService sharedInstance].routingDelegate = self; // set for receiving routing callbacks
    [SKRoutingService sharedInstance].mapView = mapView; // use the map view for route rendering

    SKRouteSettings* route = [[SKRouteSettings alloc]init];
    route.startCoordinate=CLLocationCoordinate2DMake(37.447692, -122.166016);
    route.destinationCoordinate=CLLocationCoordinate2DMake(37.437964, -122.141147);
    route.maximumReturnedRoutes = 3; // setting the maximum number of routes
    [[SKRoutingService sharedInstance] calculateRoute:route];
}
- (void)routingService:(SKRoutingService *)routingService didFinishRouteCalculationWithInfo:(SKRouteInformation*)routeInformation{
    NSLog(@"Route %d is calculated.", routeInformation.routeID); // unique identifier for a route
    [routingService zoomToRouteWithInsets:UIEdgeInsetsZero]; // zooming to currrent route
}
- (void)routingServiceDidFailRouteCalculation:(SKRoutingService *)routingService{
    NSLog(@"Route calculation failed.");
}
- (void)routingServiceDidCalculateAllRoutes:(SKRoutingService *)routingService{
    NSLog(@"All routes have been calculated.");
}
@end

Swift

import SKMaps
class RoutingViewController : UIViewController, SKRoutingDelegate {
    override func viewDidLoad() {
        super.viewDidLoad()

        let mapView = SKMapView(frame: CGRectMake( 0.0, 0.0, CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame)))
        self.view.addSubview(mapView)

        SKRoutingService.sharedInstance().routingDelegate = self; // set for receiving routing callbacks
        SKRoutingService.sharedInstance().mapView = mapView; // use the map view instance for route rendering

        let route = SKRouteSettings()
        route.startCoordinate = CLLocationCoordinate2DMake(37.447692, -122.166016);
        route.destinationCoordinate = CLLocationCoordinate2DMake(37.437964, -122.141147)
        route.shouldBeRendered = true // If false, the route will not be rendered.
        route.routeMode = SKRouteMode.CarFastest
        route.maximumReturnedRoutes = 3
        SKRoutingService.sharedInstance().calculateRoute(route)
    }
    func routingService(routingService: SKRoutingService!, didFinishRouteCalculationWithInfo routeInformation: SKRouteInformation!) {
        NSLog("Route is calculated")
        routingService.zoomToRouteWithInsets(UIEdgeInsetsZero,duration)
    }
    func routingService(routingService: SKRoutingService!, didFailWithErrorCode errorCode: SKRoutingErrorCode) {
        NSLog("Route calculation failed")
    }
    func routingServiceDidCalculateAllRoutes(routingService: SKRoutingService!) {
        NSLog("All routes have been calculated.")
    }
}

Getting route directions

After a route was calculated, if the route is intended for navigation or guidance, the route advices are also available using the SKRoutingService. The SKRouteAdvice class stores information about a route advice.

Objective-C

#import <SKMaps/SKMaps.h>
@implementation RoutingViewController<SKRoutingDelegate>
- (void)viewDidLoad{
    [super viewDidLoad];
    SKMapView *mapView = [[SKMapView alloc] initWithFrame:CGRectMake( 0.0f, 0.0f,  CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame) )];

    [SKRoutingService sharedInstance].routingDelegate = self; // set for receiving routing callbacks
    [SKRoutingService sharedInstance].mapView = mapView; // use the map view for route rendering

    SKRouteSettings* route = [[SKRouteSettings alloc]init];
    route.startCoordinate=CLLocationCoordinate2DMake(37.447692, -122.166016);
    route.destinationCoordinate=CLLocationCoordinate2DMake(37.437964, -122.141147);
    route.requestAdvices = YES;
    [[SKRoutingService sharedInstance] calculateRoute:route];
}
- (void)routingService:(SKRoutingService *)routingService didFinishRouteCalculationWithInfo:(SKRouteInformation*)routeInformation{
    NSLog(@"Route is calculated.");
    [routingService zoomToRouteWithInsets:UIEdgeInsetsZero duration:1]; // zoom to currrent route
    NSArray *adviceList = [[SKRoutingService sharedInstance] routeAdviceListWithDistanceFormat:SKDistanceFormatMetric]; // array of SKRouteAdvice
} 

Swift

import SKMaps
class RoutingViewController : UIViewController, SKRoutingDelegate {
    override func viewDidLoad() {
        super.viewDidLoad()

        let mapView = SKMapView(frame: CGRectMake( 0.0, 0.0, CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame)))
        self.view.addSubview(mapView)

        SKRoutingService.sharedInstance().routingDelegate = self
        SKRoutingService.sharedInstance().mapView = mapView

        let route = SKRouteSettings()
        route.startCoordinate = CLLocationCoordinate2DMake(37.447692, -122.166016)
        route.destinationCoordinate=CLLocationCoordinate2DMake(37.437964, -122.141147)
        route.requestAdvices = true
        SKRoutingService.sharedInstance().calculateRoute(route)
    }
    func routingService(routingService: SKRoutingService!, didFinishRouteCalculationWithInfo routeInformation: SKRouteInformation!) {
        NSLog("Route is calculated")
        let advices = SKRoutingService.sharedInstance().routeAdviceListWithDistanceFormat(SKDistanceFormat.Metric) // array of SKRouteAdvice
        NSLog("%@", advices)
    }
}

Navigating on a route

To start a turn by turn navigation on a previously calculated route or just a new free drive session, use the startNavigationWithSettings: method of the SKRoutingService class. The navigation session can be configured using the SKNavigationSettings class.

Objective-C

#import <SKMaps/SKMaps.h>
#import <SKMaps/SKRouteInformation.h>
@implementation RoutingViewController<SKRoutingDelegate,SKNavigationDelegate>
- (void)viewDidLoad{
    [super viewDidLoad];

    SKMapView *mapView = [[SKMapView alloc] initWithFrame:CGRectMake( 0.0f, 0.0f,  CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame) )];

    [SKRoutingService sharedInstance].routingDelegate = self; // set for receiving routing callbacks
    [SKRoutingService sharedInstance].navigationDelegate = self;// set for receiving navigation callbacks
    [SKRoutingService sharedInstance].mapView = mapView; // use the map view for route rendering

    SKRouteSettings* route = [[SKRouteSettings alloc]init];
    route.startCoordinate=CLLocationCoordinate2DMake(37.447692, -122.166016);
    route.destinationCoordinate=CLLocationCoordinate2DMake(37.437964, -122.141147);

    [[SKRoutingService sharedInstance] calculateRoute:route];
}
- (void)routingService:(SKRoutingService *)routingService didFinishRouteCalculationWithInfo:(SKRouteInformation*)routeInformation{
    NSLog(@"Route is calculated.");
    [routingService zoomToRouteWithInsets:UIEdgeInsetsZero duration:1]; // zooming to currrent route

    SKNavigationSettings* navSettings = [SKNavigationSettings navigationSettings];
    navSettings.navigationType=SKNavigationTypeSimulation;
    navSettings.distanceFormat=SKDistanceFormatMilesFeet;
    navSettings.showStreetNamePopUpsOnRoute=YES;
    [SKRoutingService sharedInstance].mapView.settings.displayMode = SKMapDisplayMode3D;
    [[SKRoutingService sharedInstance]startNavigationWithSettings:navSettings];
} 

Swift

import SKMaps
class RoutingViewController : UIViewController, SKRoutingDelegate, SKNavigationDelegate {
    override func viewDidLoad() {
        super.viewDidLoad()

        let mapView = SKMapView(frame: CGRectMake( 0.0, 0.0, CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame)))
        self.view.addSubview(mapView)

        SKRoutingService.sharedInstance().routingDelegate = self
        SKRoutingService.sharedInstance().navigationDelegate = self
        SKRoutingService.sharedInstance().mapView = mapView

        let route = SKRouteSettings()
        route.startCoordinate = CLLocationCoordinate2DMake(37.447692, -122.166016)
        route.destinationCoordinate=CLLocationCoordinate2DMake(37.437964, -122.141147)
        route.shouldBeRendered = true
        SKRoutingService.sharedInstance().calculateRoute(route)
    }
    func routingService(routingService: SKRoutingService!, didFinishRouteCalculationWithInfo routeInformation: SKRouteInformation!) {
        routingService.zoomToRouteWithInsets(UIEdgeInsetsZero)

        let navSettings = SKNavigationSettings()
        navSettings.navigationType = SKNavigationType.Simulation
        navSettings.distanceFormat = SKDistanceFormat.MilesFeet
        navSettings.showStreetNamePopUpsOnRoute = true
        SKRoutingService.sharedInstance().mapView.settings.displayMode = SKMapDisplayMode.Mode3D
        SKRoutingService.sharedInstance().startNavigationWithSettings(navSettings)
    }
} 

Handling navigation events

For an ongoing turn by turn navigation adopt the SKNavigationDelegate protocol to receive callbacks about:

  • distance to destination
  • estimated time to destination
  • current street name
  • next street name
  • types of the streets
  • first & secondary visual advise image and distance
  • audio advices
  • current speed, speed limit and speed warnings
  • re-routings
  • destination reached

Objective-C

#import <SKMaps/SKMaps.h>
#import <SKMaps/SKRouteInformation.h>
@implementation RoutingViewController<SKRoutingDelegate,SKNavigationDelegate>
- (void)viewDidLoad{
    [super viewDidLoad];
    SKMapView *mapView = [[SKMapView alloc] initWithFrame:CGRectMake( 0.0f, 0.0f,  CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame) )];

    [SKRoutingService sharedInstance].routingDelegate = self; // set for receiving routing callbacks
    [SKRoutingService sharedInstance].navigationDelegate = self;// set for receiving navigation callbacks
    [SKRoutingService sharedInstance].mapView = mapView; // using the map view for route rendering

    SKRouteSettings* route = [[SKRouteSettings alloc]init];
    route.startCoordinate=CLLocationCoordinate2DMake(37.447692, -122.166016);
    route.destinationCoordinate=CLLocationCoordinate2DMake(37.437964, -122.141147);

    [[SKRoutingService sharedInstance] calculateRoute:route];
}

- (void)routingService:(SKRoutingService *)routingService didFinishRouteCalculationWithInfo:(SKRouteInformation*)routeInformation{
    NSLog(@"Route is calculated.");
    [routingService zoomToRouteWithInsets:UIEdgeInsetsZero]; // zooming to currrent route
    SKNavigationSettings* navSettings = [SKNavigationSettings navigationSettings];
    navSettings.navigationType=SKNavigationTypeSimulation;
    navSettings.distanceFormat=SKDistanceFormatMilesFeet;
    navSettings.showStreetNamePopUpsOnRoute=YES;
    [SKRoutingService sharedInstance].mapView.settings.displayMode = SKMapDisplayMode3D;
    [[SKRoutingService sharedInstance]startNavigationWithSettings:navSettings];
}
- (void)routingService:(SKRoutingService*)routingService didChangeDistanceToDestination:(int)distance withFormattedDistance:(NSString*)formattedDistance{
}
- (void)routingService:(SKRoutingService*)routingService didChangeEstimatedTimeToDestination:(int)time{
}
- (void)routingService:(SKRoutingService*)routingService didChangeCurrentSpeed:(double)speed{
} 

Swift

import SKMaps
class RoutingViewController : UIViewController, SKRoutingDelegate, SKNavigationDelegate {
    override func viewDidLoad() {
        super.viewDidLoad()

        let mapView = SKMapView(frame: CGRectMake( 0.0, 0.0, CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame)))
        self.view.addSubview(mapView)

        SKRoutingService.sharedInstance().routingDelegate = self
        SKRoutingService.sharedInstance().navigationDelegate = self
        SKRoutingService.sharedInstance().mapView = mapView

        let route = SKRouteSettings()
        route.startCoordinate = CLLocationCoordinate2DMake(37.447692, -122.166016)
        route.destinationCoordinate=CLLocationCoordinate2DMake(37.437964, -122.141147)
        route.shouldBeRendered = true
        SKRoutingService.sharedInstance().calculateRoute(route)
    }
    func routingService(routingService: SKRoutingService!, didFinishRouteCalculationWithInfo routeInformation: SKRouteInformation!) {
        routingService.zoomToRouteWithInsets(UIEdgeInsetsZero)

        let navSettings = SKNavigationSettings()
        navSettings.navigationType = SKNavigationType.Simulation
        navSettings.distanceFormat = SKDistanceFormat.MilesFeet
        navSettings.showStreetNamePopUpsOnRoute = true
        SKRoutingService.sharedInstance().mapView.settings.displayMode = SKMapDisplayMode.Mode3D
        SKRoutingService.sharedInstance().startNavigationWithSettings(navSettings)
    }
    func routingService(routingService: SKRoutingService!, didChangeDistanceToDestination distance: Int32, withFormattedDistance formattedDistance: String!) {
    }
    func routingService(routingService: SKRoutingService!, didChangeEstimatedTimeToDestination time: Int32) {
    }
    func routingService(routingService: SKRoutingService!, didChangeCurrentSpeed speed: Double) {
    }
} 

Configuring the audio advisor

For audio advises you have two options: use pre-recorded mp3 audio files or using the phone’s text-to-speech engine.

Using prerecorded audio files

Changing the audio advisor settings used during navigation can be done using the advisorSettings property of SKRoutingService. The SKAdvisorSettings class provides the interface for changing the language of the audio advisor and to change the path to the advisor’s configuration and resource files.

Objective-C

#import <SKMaps/SKMaps.h>
@implementation RoutingViewController<SKRoutingDelegate,SKNavigationDelegate>
- (void)viewDidLoad{
    [super viewDidLoad];
    SKMapView *mapView =[[SKMapView alloc] initWithFrame:CGRectMake( 0.0f, 0.0f,  CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame) )];

    [SKRoutingService sharedInstance].routingDelegate = self; // set for receiving routing callbacks
    [SKRoutingService sharedInstance].navigationDelegate = self;// set for receiving navigation callbacks
    [SKRoutingService sharedInstance].mapView = mapView; // use the map view for route rendering

    SKAdvisorSettings *settings = [SKAdvisorSettings advisorSettings];
    settings.advisorVoice =  @"en_us";
    settings.language  = SKAdvisorLanguageEN_US;
    [SKRoutingService sharedInstance].advisorConfigurationSettings = settings;

    SKRouteSettings* route = [[SKRouteSettings alloc]init];
    route.startCoordinate=CLLocationCoordinate2DMake(37.447692, -122.166016);
    route.destinationCoordinate=CLLocationCoordinate2DMake(37.437964, -122.141147);

    [[SKRoutingService sharedInstance] calculateRoute:route];
}

- (void)routingService:(SKRoutingService *)routingService didFinishRouteCalculationWithInfo:(SKRouteInformation*)routeInformation{
    NSLog(@"Route is calculated.");
    [routingService zoomToRouteWithInsets:UIEdgeInsetsZero]; // zoom to currrent route
    SKNavigationSettings* navSettings = [SKNavigationSettings navigationSettings];
    navSettings.navigationType=SKNavigationTypeSimulation;
    navSettings.distanceFormat=SKDistanceFormatMilesFeet;
    navSettings.showStreetNamePopUpsOnRoute=YES;
    [SKRoutingService sharedInstance].mapView.settings.displayMode = SKMapDisplayMode3D;
    [[SKRoutingService sharedInstance]startNavigationWithSettings:navSettings];
} 

Swift

import SKMaps
class RoutingViewController : UIViewController, SKRoutingDelegate, SKNavigationDelegate {
    override func viewDidLoad() {
        super.viewDidLoad()

        let mapView = SKMapView(frame: CGRectMake( 0.0, 0.0, CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame)))
        self.view.addSubview(mapView)

        SKRoutingService.sharedInstance().routingDelegate = self
        SKRoutingService.sharedInstance().navigationDelegate = self
        SKRoutingService.sharedInstance().mapView = mapView

        let settings = SKAdvisorSettings()
        settings.advisorVoice =  "en_us"
        settings.language  = SKAdvisorLanguageEN_US
        SKRoutingService.sharedInstance().advisorConfigurationSettings = settings

        let route = SKRouteSettings()
        route.startCoordinate = CLLocationCoordinate2DMake(37.447692, -122.166016)
        route.destinationCoordinate=CLLocationCoordinate2DMake(37.437964, -122.141147)
        route.shouldBeRendered = true
        SKRoutingService.sharedInstance().calculateRoute(route)
    }
    func routingService(routingService: SKRoutingService!, didFinishRouteCalculationWithInfo routeInformation: SKRouteInformation!) {
        routingService.zoomToRouteWithInsets(UIEdgeInsetsZero, duration)

        let navSettings = SKNavigationSettings()
        navSettings.navigationType = SKNavigationType.Simulation
        navSettings.distanceFormat = SKDistanceFormat.MilesFeet
        navSettings.showStreetNamePopUpsOnRoute = true
        SKRoutingService.sharedInstance().mapView.settings.displayMode = SKMapDisplayMode.Mode3D
        SKRoutingService.sharedInstance().startNavigationWithSettings(navSettings)
    }
}

Using the Text-To-Speech engine

SKTTSPlayer thumbnail

The SDK supports playing the advices using the TTS API available in iOS. To enable this the SKAdvisorSettings needs to be configured accordingly. Furthermore, the TTS playback can be customized.

For iOS 9 (using Xcode 6.4), use lower values for the rate.

Objective-C

@implementation TTSViewController
- (void)viewDidLoad {
    [super viewDidLoad];

    //enable TTS
    SKAdvisorSettings *advisorSettings = [SKAdvisorSettings advisorSettings];
    advisorSettings.advisorVoice = @"en_us";
    advisorSettings.language = SKAdvisorLanguageEN_US;
    advisorSettings.advisorType = SKAdvisorTypeTextToSpeech;
    [SKRoutingService sharedInstance].advisorConfigurationSettings = advisorSettings;

    //change TTS playback options
    SKAdvisorTTSSettings *ttsSettings = [SKAdvisorTTSSettings advisorTTSSettings];
    ttsSettings.rate = 0.2;
    ttsSettings.pitchMultiplier = 0.8;
    ttsSettings.volume = 0.5;
    ttsSettings.preUtteranceDelay = 0.3;
    ttsSettings.postUtteranceDelay = 0.4;
    [SKTTSPlayer sharedInstance].textToSpeechConfig = ttsSettings;

    [SKTTSPlayer sharedInstance].delegate = self;
}
//you can also pause, resume and cancel the playback
- (void)pauseButtonClicked {
    [[SKTTSPlayer sharedInstance] pausePlayingInstructions];
}
- (void)resumeButtonClicked {
    [[SKTTSPlayer sharedInstance] pausePlayingInstructions];
}
- (void)cancelButtonClicked {
    [[SKTTSPlayer sharedInstance] pausePlayingInstructions];
}
//you can also receive a callback before playing an utterance starts
- (void)TTSPlayer:(SKTTSPlayer *)TTSPlayer willPlayUtterance:(AVSpeechUtterance *)utterance {
    NSLog(@"will play utterance %@", utterance);
}
@end

Swift

import SKMaps
class TTSViewController : UIViewController, SKTTSPlayerDelegate {
override func viewDidLoad {
    super.viewDidLoad()

    //enable TTS
    let advisorSettings = SKAdvisorSettings()
    advisorSettings.advisorVoice = "en_us"
    advisorSettings.language = SKAdvisorLanguageEN_US
    advisorSettings.advisorType = SKAdvisorType.TextToSpeech
    SKRoutingService.sharedInstance().advisorConfigurationSettings = advisorSettings

    //change TTS playback options
    let ttsSettings = SKAdvisorTTSSettings()
    ttsSettings.rate = 0.2
    ttsSettings.pitchMultiplier = 0.8
    ttsSettings.volume = 0.5
    ttsSettings.preUtteranceDelay = 0.3
    ttsSettings.postUtteranceDelay = 0.4
    SKTTSPlayer.sharedInstance().textToSpeechConfig = ttsSettings

    SKTTSPlayer.sharedInstance().delegate = self;
}
//you can also pause, resume and cancel the playback
func pauseButtonClicked {
    SKTTSPlayer.sharedInstance().pausePlayingInstructions()
}
- (void)resumeButtonClicked {
    SKTTSPlayer.sharedInstance().resumePlayingInstructions()
}
- (void)cancelButtonClicked {
    SKTTSPlayer.sharedInstance().cancelPlayingInstructions()
}
//you can also receive a callback before playing an utterance starts
func TTSPlayer(TTSPlayer : SKTTSPlayer!, willPlayUtterance utterance : AVSpeechUtterance) {
    NSLog("will play utterance %@", utterance)
}
}

Tracking POIs

SKPOITracker thumbnail

This feature can be used to detect Points Of Interest (POIs) close to the user. A possible use case could be to detect speed cameras in front of the use. After the SDK is initialized, the POI tracker can be configured with the desired detection rules and then initiated. For further details check the documentation of the SKPOITracker.

Objective-C

-(void)startPOITracking{
    SKTrackablePOIRule *rule1 = [SKTrackablePOIRule trackablePOIRule];
    rule1.routeDistance = 1500;
    rule1.aerialDistance = 3000;

    SKTrackablePOIRule *rule2 = [SKTrackablePOIRule trackablePOIRule];
    rule2.routeDistance = 1500;
    rule2.aerialDistance = 3000;

    self.poiTracker = [SKPOITracker sharedInstance];
    self.poiTracker.dataSource = self;
    self.poiTracker.delegate = self;
    [self.poiTracker setRule:rule1 forPOIType:0];
    [self.poiTracker setRule:rule2 forPOIType:1];
    [poiTracker startPOITrackerWithRadius:5000 refreshMargin:0.1 forPOITypes:@[@1]];
}
-(NSArray*)poiTracker:(SKPOITracker *)poiTracker trackablePOIsAroundLocation:(CLLocationCoordinate2D)location inRadius:(int)radius{
    return self.trackablePOIs;
}
- (void)poiTracker:(SKPOITracker *)poiTracker didDectectPOIs:(NSArray *)detectedPOIs{
    [detectedPOIs enumerateObjectsUsingBlock:^(SKDetectedPOI *detectedPOI, NSUInteger index, BOOL *stop){
    NSLog(@"Detected: %@",[detectedPOI description]);
    }];
}

Swift

func startPOITracking() {
    let rule1 = SKTrackablePOIRule()
    rule1.routeDistance = 1500
    rule1.aerialDistance = 3000

    let rule2 = SKTrackablePOIRule()
    rule2.routeDistance = 1500
    rule2.aerialDistance = 3000

    self.poiTracker = SKPOITracker.sharedInstance()
    self.poiTracker.dataSource = self
    self.poiTracker.delegate = self
    self.poiTracker.setRule(rule1, forPOIType: 0)
    self.poiTracker.setRule(rule2, forPOIType: 1)
    self.poiTracker.startPOITrackerWithRadius(5000, refreshMargin: 0.1, forPOITypes: [Int(1)])
}

func poiTracker(poiTracker: SKPOITracker!, trackablePOIsAroundLocation location: CLLocationCoordinate2D, inRadius radius: Int32, withType poiType: Int32) -> [AnyObject]! {
    return self.trackablePOIs
}

func poiTracker(poiTracker: SKPOITracker!, didDectectPOIs detectedPOIs: [AnyObject]!, withType type: Int32) {
    for poi in detectedPOIs {
    NSLog("Detected: %@", poi.description)
}
}

One Line Search

OneBox search provides a cleaner, easier way of searching for cities, addresses or POIs inside one search field, instead of using one for country, city, street and number. To find out more about the internals of the OneBox search tool (the OpenStreetMap based implementation) please see the dedicated page.

The SKOneLineSearchSettings will be used as an input for the one line search method. The results will be passed through the SKSearchServiceDelegate method searchService:didRetrieveOneLineSearchResults:withSearchMode. For more information regarding the use of this feature and also an entry point for multiple search providers, please check out the SKOSearchLib and SKOneBoxSearch projects, where you can also find more details regarding that implementation and also a provided user interface solution.

Objective-C

#import 

@implementation OneBoxSearchViewController
- (void)viewDidLoad {
    [super viewDidLoad];

    [SKSearchService sharedInstance].searchServiceDelegate = self;
    SKOneLineSearchSettings *settings = [SKOneLineSearchSettings oneLineSearchSettings];
    settings.countryCode = @"DE";
    settings.searchTerm = @"restaurant";
    settings.coordinate = [SKPositionerService sharedInstance].currentCoordinate;
    [[SKSearchService sharedInstance] startOneLineSearch:settings];
}

//Delegate methods for one line search
- (void)searchService:(SKSearchService *)searchService didRetrieveOneLineSearchResults:(NSArray *)searchResults withSearchMode:(SKSearchMode)searchMode {
    //manage search results
}
- (void)searchServiceDidFailToRetrieveOneLineSearchResults:(SKSearchService *)searchService withSearchMode:(SKSearchMode)searchMode {
}
}

Swift

import SKMaps
class OneBoxSearchViewController: UIViewController, SKSearchServiceDelegate {

    override func viewDidLoad() {
       super.viewDidLoad()

       SKSearchService.sharedInstance().searchServiceDelegate = self
       let settings = SKOneLineSearchSettings()
       settings.countryCode = "DE"
       settings.searchTerm = "restaurant"
       settings.coordinate = SKPositionerService.sharedInstance().currentCoordinate
       SKSearchService.sharedInstance().startOneLineSearch(settings)
    }

    //Delegate methods for one line search
    func searchService(searchService: SKSearchService!, didRetrieveOneLineSearchResults searchResults: [AnyObject]!, withSearchMode: SKSearchMode) {
       //manage search results
    }

    func searchServiceDidFailToRetrieveOneLineSearchResults(searchService: SKSearchService!, withSearchMode: SKSearchMode) {

    }
}

Extending the OneBox (OneLine) search component

SKOSearchLib project

SKOSearchLib is a search aggregator library that contains the base structure for any kind of search service. It supports custom search service that will be aggregated in the results list.

SKOneBoxSearch project

The SKOneBoxSearch project is a user interface built on top of SKOSearchLib to display all the search results in a simple and clear way. The search results are displayed separately for each provider, for legal reasons.

Integrating 3rd party providers

A custom search provider can be added following these steps:

  • Define your own search object (should inherit from SKOBaseSearchObject class)

Objective-C

@interface MapsSearchObject : SKOBaseSearchObject

/** The code of the country where the search is executed.
*/
@property(nonatomic, strong) NSString *countryCode;

/** The search term is used to filter the results. It should be empty for all the results.
*/
@property(nonatomic, strong) NSString *searchTerm;

/** The center location of the searched area. This is an optional parameter but it can help to return better search results
*/
@property(nonatomic, assign) CLLocationCoordinate2D coordinate;

/** Specifies the search categories for the POI's.
*/
@property(nonatomic, strong) NSArray *searchCategories;

/** How many items to be displayed per page (should be set for searches that support pagination). Can be Nil.
*/
@property(nonatomic, strong) NSNumber *itemsPerPage;

/** Search radius in meters (should be set for searches that support radius). Can be Nil.
*/
@property(nonatomic, strong) NSNumber *radius;

/** A newly initialized MapsSearchObject.
*/
+ (instancetype)searchObject;

@end

Swift

class MapsSearchObject: SKOBaseSearchObject {

    /** The code of the country where the search is executed.
    */
    var countryCode: String?

    /** The search term is used to filter the results. It should be empty for all the results.
    */
    var searchTerm: String?

    /** The center location of the searched area. This is an optional parameter but it can help to return better search results
    */
    var coordinate: CLLocationCoordinate2D?

    /** Specifies the search categories for the POI's.
    */
    var searchCategories: [Int]?

    /** How many items to be displayed per page (should be set for searches that support pagination). Can be Nil.
    */
    var itemsPerPage: Int?

    /** Search radius in meters (should be set for searches that support radius). Can be Nil.
    */
    var radius: NSNumber?

}
  • Define your own Search Service class; must implement a search and cancel functionality.

Objective-C

/** MapsSearchService class provides services for searching POIs and addresses using ScoutSDK oneline search.
*/
@interface MapsSearchService : NSObject

/** Returns the singleton search service.
*/
+ (instancetype)sharedInstance;

/** The caller objects should adopt the MapsSearchServiceDelegate to receive callbacks.
*/
@property (atomic, weak) id searchServiceDelegate;

/** Supports local search.
@param searchObject - stores the input parameters for the ScoutSDK oneline search.
*/
- (void)search:(MapsSearchObject *)searchObject;

/** Cancels an ongoing search request.
*/
- (void)cancelSearch;

@end

Swift

/** MapsSearchService class provides services for searching POIs and addresses using ScoutSDK oneline search.
*/
class MapsSearchService : NSObject {

    /** Returns the singleton search service.
    */
    class let sharedInstance: <>

    /** The caller objects should adopt the MapsSearchServiceDelegate to receive callbacks.
    */
    weak var searchServiceDelegate: MapsSearchServiceDelegate

    /** Supports local search.
    @param searchObject - stores the input parameters for the ScoutSDK oneline search.
    */
    func search(searchObject: MapsSearchObject) -> <>

    /** Cancels an ongoing search request.
    */
    func cancelSearch()
}
  • Define a protocol for your Search Service.

Objective-C

@protocol MapsSearchServiceDelegate 
- (void)searchService:(MapsSearchService *)searchService didRetrieveResults:(NSArray *)searchResults;
- (void)searchServiceDidFailToRetrieveSearchResults:(MapsSearchService *)searchService;
@end

Swift

protocol MapsSearchServiceDelegate : class {
   func searchService(searchService: MapsSearchService, didRetrieveResults searchResults: [AnyObject])
   func searchServiceDidFailToRetrieveSearchResults(searchService: MapsSearchService)
}
  • Define your search provider, which should inherit from the SKSearchBaseProvider class and implement your search service protocol. In order to configure this to work as you want and for the One Box component to display the results as needed, you can set different properties from the SKSearchProviderProtocol, which is implemented inside the base provider search class. The search and cancel methods should also be implemented.

Objective-C

@implementation MapsSearchProvider
- (BOOL)shouldAppearInDefaultList {
   return YES;
}
- (BOOL)shouldShowSectionHeaderDefaultList {
   return YES;
}
- (NSInteger)numberOfCategoriesToShowDefaultList {
   return 4;
}
- (BOOL)shouldShowCategories {
   return YES;
}
- (NSString *)localizedProviderName {
   return @"OpenStreetMap";
}

- (void)search:(SKOneBoxSearchObject *)searchObject {
   Class searchServiceClass = NSClassFromString(@"MapsSearchService");

   if (searchServiceClass) {
       MapsSearchObject *mapsSearchObject = [MapsSearchObject searchObject];

       mapsSearchObject.searchMode = self.searchMode;

       if (![self isConnectedToInternet]) {
           mapsSearchObject.searchMode = SKOSearchOffline; //go offline if not connected to internet
       }

       mapsSearchObject.searchTerm = searchObject.searchTerm;
       mapsSearchObject.coordinate = searchObject.coordinate;
       mapsSearchObject.radius = searchObject.radius;
       mapsSearchObject.searchLanguage = searchObject.searchLanguage;
       mapsSearchObject.itemsPerPage = searchObject.itemsPerPage;

       if (searchObject.searchCategory) {
           mapsSearchObject.searchCategories = @[searchObject.searchCategory];
       }

       mapsSearchObject.apiKey = self.apiKey;
       mapsSearchObject.apiSecret = self.apiSecret;

       [[searchServiceClass sharedInstance] setSearchServiceDelegate:self];
       [[searchServiceClass sharedInstance] search:mapsSearchObject];
   }
}

- (void)cancelSearch {
   Class searchServiceClass = NSClassFromString(@"MapsSearchService");

   if (searchServiceClass) {
       [[searchServiceClass sharedInstance] cancelSearch];
   }
}

Swift

class MapsSearchProvider: SKSearchBaseProvider, MapsSearchServiceDelegate {

    override var shouldAppearInDefaultList: Bool {
       get {
           return true
       }
       set {
           self.shouldAppearInDefaultList = newValue
       }
    }
    override var shouldShowSectionHeaderDefaultList: Bool {
       get {
           return true
       }
       set {
           self.shouldShowSectionHeaderDefaultList = newValue
       }
    }
    override var numberOfCategoriesToShowDefaultList: Int {
       get {
           return 4
       }
       set {
           self.numberOfCategoriesToShowDefaultList = newValue
       }
    }
    override var shouldShowCategories: Bool {
       get {
           return true
       }
       set {
           self.shouldShowCategories = newValue
       }
    }
    override var localizedProviderName: String! {
       get {
           return "OpenStreetMap"
       }
       set {
           self.localizedProviderName = newValue
       }
    }

    override func search(searchObject: SKOneBoxSearchObject!) {
       let mapsSearchObject = MapsSearchObject()
       mapsSearchObject.searchMode = self.searchMode()

       if self.isConnectedToInternet() {
           mapsSearchObject.searchMode = .Offline
       }
       mapsSearchObject.searchTerm = searchObject.searchTerm
       mapsSearchObject.coordinate = searchObject.coordinate
       mapsSearchObject.radius = searchObject.radius
       mapsSearchObject.searchLanguage = searchObject.searchLanguage
       mapsSearchObject.itemsPerPage = searchObject.itemsPerPage

       if let category = searchObject.searchCategory {
           mapsSearchObject.searchCategories = [category]
       }
       mapsSearchObject.apiKey = self.apiKey
       mapsSearchObject.apiSecret = self.apiSecret

       MapsSearchService.sharedInstance.searchServiceDelegate = self
       MapsSearchService.sharedInstance.search(mapsSearchObject)
    }

    override func cancelSearch() {
       MapsSearchService.sharedInstance.cancelSearch()
}
  • In the desired ViewController, add a SKOneBoxSearchBar and create your SKOneBoxViewController inside a UINavigationController. The OneBox component is initialized with the search bar and the desired search providers.

Objective-C

- (void)createOneBoxSearch {
   [self createSearchBar];

   self.oneBoxViewController = [[SKOneBoxViewController alloc] initWithSearchBar:self.searchBar searchProviders:[self searchProviders]];
   self.oneBoxViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
   self.oneBoxViewController.delegate = self;

   self.oneBoxBaseViewController = [[UINavigationController alloc] initWithRootViewController:self.oneBoxViewController];
   self.oneBoxBaseViewController.navigationBar.translucent = NO;
}
- (NSArray *)searchProviders {
   MapsSearchProvider *mapsSearchProvider = [[MapsSearchProvider alloc] initWithAPIKey:@"" apiSecret:@""];
   mapsSearchProvider.providerID = @(0);

   AppleSearchProvider *appleSearchProvider = [[AppleSearchProvider alloc] initWithAPIKey:@"" apiSecret:@""];
   appleSearchProvider.providerID = @(1);

   return @[mapsSearchProvider, appleSearchProvider];
}
- (void)oneBoxViewController:(SKOneBoxBaseViewController *)viewController searchBarTextDidBeginEditing:(SKOneBoxSearchBar *)searchBar {
   [self presentViewController:self.oneBoxBaseViewController animated:YES completion:^{
       [searchBar showKeyboard];
   }];
}

Swift

private func createOneBoxSearch() {
   self.createSearchBar()

   self.oneBoxViewController = SKOneBoxViewController(searchBar: self.searchBar!, searchProviders: self.searchProviders())
   self.oneBoxViewController?.modalTransitionStyle = .CrossDissolve
   self.oneBoxViewController?.delegate = self

   self.oneBoxBaseViewController = UINavigationController(rootViewController: self.oneBoxViewController!)
   self.oneBoxBaseViewController?.navigationBar.translucent = false
}
private func searchProviders() -> [SKSearchBaseProvider] {
   let mapsSearchProvider = MapsSearchProvider(APIKey: "", apiSecret: "")
   mapsSearchProvider.providerID = 0

   let appleSearchProvider = AppleSearchProvider(APIKey: "", apiSecret: "")
   appleSearchProvider.providerID = 1

   return [mapsSearchProvider, appleSearchProvider]
}
func oneBoxViewController(viewController: SKOneBoxBaseViewController, searchBarTextDidBeginEditing searchBar:SKOneBoxSearchBar) {
   self.presentViewController(self.oneBoxBaseViewController!, animated: true) {
       searchBar.showKeyboard()
   }
}
  • The search requests can also be location sensitive. If you want location to be used as a parameter in your searches, report the location to the SKOneBoxSearchPositionerService.

Objective-C

#pragma mark SKPositionerServiceDelegate
- (void)positionerService:(SKPositionerService *)positionerService updatedCurrentLocation:(CLLocation *)currentLocation {
   [[SKOneBoxSearchPositionerService sharedInstance] reportLocation:currentLocation];
}

Swift

//MARK: SKPositionerServiceDelegate
func positionerService(positionerService: SKPositionerService!, updatedCurrentLocation currentLocation: CLLocation!) {
   SKOneBoxSearchPositionerService.sharedInstance().reportLocation(currentLocation)
}

Offline geocoding

SKSearchService thumbnail

The multi step offline geocoding can be used to search for a certain address and retrieve different map information related to it. The SKMultiStepSearchSettings will be used as an input for a top-down hierarchical search in an offline package. This search can be used for any offline package (country or city). The listLevel indicates the depth level of the geocoding and has to be used incrementally.

Objective-C

#import <SKMaps/SKMaps.h>
static SKListLevel listLevel;
@implementation MultiStepSearchViewController<SKSearchServiceDelegate>
- (void)viewDidLoad{
    [super viewDidLoad];

    [SKSearchService sharedInstance].searchServiceDelegate = self;
    [SKSearchService sharedInstance].searchResultsNumber = 500;
    [SKMapsService sharedInstance].connectivityMode = SKConnectivityModeOffline;

    listLevel = SKCountryList;

    SKMultiStepSearchSettings *multiStepSearchObject = [SKMultiStepSearchSettings multiStepSearchSettings];
    multiStepSearchObject.listLevel = listLevel;
    multiStepSearchObject.offlinePackageCode = @"FR"; // France package has to be downloaded
    multiStepSearchObject.searchTerm = @"";
    multiStepSearchObject.parentIndex = -1;
    [[SKSearchService sharedInstance]startMultiStepSearchWithSettings:multiStepSearchObject];
}

-(void)searchService:(SKSearchService *)searchService didRetrieveMultiStepSearchResults:(NSArray *)searchResults{
    if ([searchResults count] != 0 && listLevel < SKInvalidListLevel )  {
        if(listLevel == SKCountryList ){  // only US has states
            listLevel = SKCityList;
        }
        else{
            listLevel++;
        }
        SKSearchResult *searchResult = searchResults[0]; // the first result will be used for next level search

        SKMultiStepSearchSettings *multiStepSearchObject = [SKMultiStepSearchSettings multiStepSearchSettings];
        multiStepSearchObject.listLevel = listLevel++;
        multiStepSearchObject.offlinePackageCode = searchResult.offlinePackageCode; // the package in which map   data is stored
        multiStepSearchObject.searchTerm = @"";
        multiStepSearchObject.parentIndex = searchResult.identifier; // used for searching children of the selected result
        [[SKSearchService sharedInstance]startMultiStepSearchWithSettings:multiStepSearchObject];
    }
}
-(void)searchServiceDidFailToRetrieveMultiStepSearchResults:(SKSearchService *)searchService{
    NSLog(@”Search failed”);
}

Swift

import SKMaps
static SKListLevel listLevel;
class MultiStepSearchViewController : UIViewController, SKSearchServiceDelegate {
var listLevel : SKListLevel!

override func viewDidLoad() {
    super.viewDidLoad()

    SKSearchService.sharedInstance().searchServiceDelegate = self
    SKSearchService.sharedInstance().searchResultsNumber = 500
    SKMapsService.sharedInstance().connectivityMode = SKConnectivityMode.Offline;

    self.listLevel = SKListLevel.CountryList

    let multiStepSearchObject = SKMultiStepSearchSettings()
    multiStepSearchObject.listLevel = listLevel
    multiStepSearchObject.offlinePackageCode = "FR" // France package has to be downloaded
    multiStepSearchObject.searchTerm = ""
    multiStepSearchObject.parentIndex = 0
    SKSearchService.sharedInstance().startMultiStepSearchWithSettings(multiStepSearchObject)
}

func searchService(searchService: SKSearchService!, didRetrieveMultiStepSearchResults searchResults: [AnyObject]!) {
    if (searchResults.count != 0 && self.listLevel.toRaw() < SKListLevel.InvalidListLevel.toRaw()) {
        if (self.listLevel == SKListLevel.CountryList) {
            listLevel = SKListLevel.CityList
        } else {
            self.listLevel = SKListLevel.fromRaw(self.listLevel.toRaw() + 1)
        }

        let searchResult = searchResults[0] as SKSearchResult; // the first result will be used for next level search

        let multiStepSearchObject = SKMultiStepSearchSettings()
        multiStepSearchObject.listLevel = SKListLevel.fromRaw(self.listLevel.toRaw() + 1)!
        multiStepSearchObject.offlinePackageCode = searchResult.offlinePackageCode // the package in which map   data is stored
        multiStepSearchObject.searchTerm = ""
        multiStepSearchObject.parentIndex = searchResult.identifier // used for searching children of the selected result
        SKSearchService.sharedInstance().startMultiStepSearchWithSettings(multiStepSearchObject)
    }
}

func searchService(searchService: SKSearchService!, didFailToRetrieveNearbySearchResultsWithSearchMode searchMode: SKSearchMode) {
    NSLog("Search failed")
}
}

Offline/online POI Search

The SKSearchService provides support for POI search around a specified location. SKNearbySearchSettings can be used as an input parameter for customizing the POI search and filtering the results. This functionality can be used both online and offline.

Objective-C

#import <SKMaps/SKMaps.h>
@implementation CategorySearchViewController<SKSearchServiceDelegate>
- (void)viewDidLoad{
    [super viewDidLoad];

    [SKSearchService sharedInstance].searchServiceDelegate = self;
    [SKMapsService sharedInstance].connectivityMode = SKConnectivityModeOffline;

    SKNearbySearchSettings* searchObject = [SKNearbySearchSettings nearbySearchSettings];
    searchObject.coordinate = CLLocationCoordinate2DMake(52.5233, 13.4127);
    searchObject.searchTerm=@""; //search for all POIs
    searchObject.radius=radius;
    searchObject.searchMode=SKSearchOffline;
    searchObject.searchResultSortType=SKProximitySort;
    searchObject.searchCategories = @[@(SKPOICategoryAirport), @(SKPOICategoryAtm), @(SKPOICategoryAccessoires), @(SKPOICategoryCar), @(SKPOICategoryUniversity), @(SKPOICategorySupermarket)];
    [[SKSearchService sharedInstance] setSearchResultsNumber:10000];
    [[SKSearchService sharedInstance]startNearbySearchWithSettings:searchObject];
}
-(void)searchService:(SKSearchService *)searchService didRetrieveNearbySearchResults:(NSArray *)searchResults withSearchMode:(SKSearchMode)searchMode{
    NSLog(@”Search results retrieved”);
}
-(void)searchService:(SKSearchService *)searchService didFailToRetrieveNearbySearchResultsWithSearchMode:(SKSearchMode)searchMode{
    NSLog(@”Search failed”);
}

Swift

import SKMaps
class CategorySearchViewController : UIViewController, SKSearchServiceDelegate {
    override func viewDidLoad() {
        super.viewDidLoad()

        SKSearchService.sharedInstance().searchServiceDelegate = self
        SKMapsService.sharedInstance().connectivityMode = SKConnectivityMode.Online

        let searchObject = SKNearbySearchSettings()
        searchObject.coordinate = CLLocationCoordinate2DMake(52.5233, 13.4127);
        searchObject.searchTerm = "" //search for all POIs
        searchObject.radius = 2000
        searchObject.searchMode = SKSearchMode.Offline
        searchObject.searchResultSortType = SKSearchResultSortType.ProximitySort
        let a = [SKPOICategory.Airport]
        searchObject.searchCategories = [SKPOICategory.Airport.toRaw(), SKPOICategory.Atm.toRaw(), SKPOICategory.Accessoires.toRaw(), SKPOICategory.Car.toRaw(), SKPOICategory.University.toRaw(), SKPOICategory.Supermarket.toRaw()]
        SKSearchService.sharedInstance().searchResultsNumber = 10000
        SKSearchService.sharedInstance().startNearbySearchWithSettings(searchObject)
    }

    func searchService(searchService: SKSearchService!, didRetrieveNearbySearchResults searchResults: [AnyObject]!, withSearchMode searchMode: SKSearchMode) {
        NSLog("Search results retrieved: %@", searchResults)
    }

    func searchService(searchService: SKSearchService!, didFailToRetrieveNearbySearchResultsWithSearchMode searchMode: SKSearchMode) {
        NSLog("Search failed")
    }
}

Offline reverse geocoding

The SKOfflineReverseGeocoderService provides functionality for offline reverse geocoding. In order to work properly, either the tiles have to be downloaded in that area (cached) or an offline package needs to be installed.

Objective-C

#import <SKMaps/SKMaps.h>
@implementation ReverseGeocodeViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    CLLocationCoordinate2D location = CLLocationCoordinate2DMake(37.447692, -122.166016);
    SKSearchResult *searchObject =  [[SKReverseGeocoderService sharedInstance] reverseGeocodeLocation: location];
    NSLog(@”%@”,searchObject.name);
}

Swift

import SKMaps
class ReverseGeocodeViewController : UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        let location = CLLocationCoordinate2D(latitude: 37.447692, longitude: -122.166016)
        let searchObject = SKReverseGeocoderService.sharedInstance().reverseGeocodeLocation(location)
    }
}

Considerations when using offline maps

The SDK requires some meta files to be present in order to be able to render the map (in offline).

You may choose to distribute the meta files prebundled in your app or you can download them, on-demand, from our servers.

Prebundling the meta files

The archived meta files can be found in the demo project, in the /SKMapResources folder. Depending on which maps you are using (full details maps or light maps) choose the corresponding archive file.

The unpacked content must be added to SkMaps.bundle/PreinstalledMaps/v1/ folder, similar to the below screenshot:

Downloading on demand the meta files

When initialising the SDK, the meta files will also be downloaded (if needed). The SKMapVersioningDelegate provides a callback that tells you when these meta files are ready. Only after receiving this callback you may safely switch to offline mode (as now the maps can be successfully rendered).

Objective-C

-(void)mapsVersioningManagerLoadedMetadata:(SKMapsVersioningManager *)versioningManager {
    NSLog(@"Loaded metadata");
}	 
	 

Swift

func mapsVersioningManagerLoadedMetadata(versioningManager: SKMapsVersioningManager!) {
     println("Loaded metadata");
 }

Checking for the status of the meta files

The meta file download & presence status can also be queried by reading the value of the metaDataDownloadedStatus property of the SKMapsVersioningManager.

Structure of an offline map package

Information about offline packages can be retrieved using the SKOfflinePackagesManager. The mapsJSONURLForVersion can be used to retrieve a JSON containing information about all available offline packages. After deciding which package to download, information about a package can be retrieved by using the downloadInfoForPackageWithCode: method.

A map package consists of three components:

  • .skm file - the actual map file, with tile information.
  • name browser files: .ngi, .ngi.dat (zipped in one file) - an archive containing two files used for POI and address searching in the package. This has to be unarchived before installing the package.
  • .txg file - file containing textures for high zoom levels.

After all components are downloaded, the addOfflineMapPackageNamed:inContainingFolderPath: method will install the package to the SDK.

Objective-C

#import <SKMaps/SKMaps.h>
@implementation OfflinePackagesViewController
- (void)viewDidLoad{
    [super viewDidLoad];
    [self downloadMapsJSON];
}

- (void) downloadMapsJSON{
    NSURL *jsonURL = [[NSURL alloc] initWithString:[[SKMapsService sharedInstance].packagesManager mapsJSONURLForVersion:nil]]; // get the JSON for the latest map version
    // … Download the JSON from the URL … //
    // … When download is finished call [self downloadPackages] … //
}

-(void) downloadPackages{
    SKMapPackageDownloadInfo *packageDownloadInfo = [[SKMapsService sharedInstance].packagesManager downloadInfoForPackageWithCode:@”DE”];
    // … Download the files from the URLs … //
    // … The package contains an .skm, a .txg and .zip file (which has to be unarchived before adding the package) … //
    // … When download is finished call [self addPackageFromPath: … ] … //
}

-(void) addPackageFromPath:(NSString*)path{
    [[SKMapsService sharedInstance].packagesManager addOfflineMapPackageNamed:@”DE” inContainingFolderPath:path]; // installs the offline package
}

Swift

import SKMaps
class OfflinePackagesViewController : UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        self.downloadMapsJSON()
    }

    func downloadMapsJSON() {
        let jsonURL = NSURL(string: SKMapsService.sharedInstance().packagesManager.mapsJSONURLForVersion(nil)) // get the JSON for the latest map version
        // … Download the JSON from the URL … //
        // … When download is finished call self.downloadPackages() … //
    }

    func downloadPackages() {
        let packageDownloadInfo = SKMapsService.sharedInstance().packagesManager.downloadInfoForPackageWithCode("DE")

        // … Download the files from the URLs … //
        // … The package contains an .skm, a .txg and .zip file (which has to be unarchived before adding the package) … //
        // … When download is finished call self.addPackageFromPath(…)
    }

    func addPackageFromPath(path : String!) {
        SKMapsService.sharedInstance().packagesManager.addOfflineMapPackageNamed("DE", inContainingFolderPath: path) // installs the offline package
    }
}

Managing downloaded map packages

The SKOfflinePackagesManager also provides information about previously installed offline packages. Based on that information, packages can be uninstalled. The uninstallation of a package includes the deletion of the stored files.

Objective-C

#import <SKMaps/SKMaps.h>
@implementation OfflinePackagesViewController
- (void)viewDidLoad{
    [super viewDidLoad];
    NSArray* packages =   [[SKMapsService sharedInstance].packagesManager installedOfflineMapPackages] ; // all installed packages for all versions
    SKMapPackage* package = packages[0];

    [[[SKMapsService sharedInstance] packagesManager] deleteOfflineMapPackageNamed:package.name]; // uninstall ( also delete the package from the disk )
}

Swift

import SKMaps
class OfflinePackagesViewController : UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        let packages = SKMapsService.sharedInstance().packagesManager.installedOfflineMapPackages
        let package = packages[0] as SKMapPackage
        SKMapsService.sharedInstance().packagesManager.deleteOfflineMapPackageNamed(package.name) // uninstall ( also delete the package from the disk )
    }
}

Managing pre-bundled map packages

Any pre-bundled map package must be stored in the SKMaps.bundle, PreinstalledMaps\v1 folder.

The v1 folder should contain the map version folder (e.g. 20140320). That folder should contain the package folder, where the package files are stored (e.g PreinstalledMaps\v1\20140320\package\DE.skm).

Meta files

In case the iOS SDK is used in offline mode only, the meta files must be pre-bundled as well. These files should be stored on the same level with the map packages.

The meta files are used for rendering shapes, roads, POIs on the map (e.g PreinstalledMaps\v1\20140320\meta\attributes). The meta files are specific to a map version.

In the Demo project folder, there is a folder named 'MetaFiles' where the meta files for the latest version are stored. In order to use the library exclusively offline, the version folder (e.g. 20140320) must be copied in 'PreinstalledMaps\v1' in the SKMaps.bundle.

Screen Shot 2014-05-12 at 3.18.01 PM.png

Preparing a map package for pre-bundling

The Demo project provides the support to download the map packages for a specific country or city. Once the map package is downloaded you can find it on the iPhone simulators folder at: "Library/Caches/maps/v1/20140320/package"

Note: the mapsXMLURLForVersion contains information about the association between city name (e.g. Berlin) and maps file name (DEBE.skm file).

From the above location grab the .skm file (optionally the .ngi, .ngi.dat) and move them into the "PreinstalledMaps" folder in the SKMaps.bundle.

Using live traffic information

The traffic component provides live information regarding incidents and traffic flow. Besides visualising the rendered traffic on the map, it supports routing and guidance that avoids heavy traffic areas and road incidents, based on live data.

Traffic map rendering

The SKMapView class contains APIs for rendering the traffic layer on the map, in the SKMapView+Traffic category. The trafficMode property controls which type of traffic data is rendered.

Objective-C

#import  <SKMaps/SKMapView+Traffic.h>
@implementation MapDisplayViewController
- (void)viewDidLoad{
    [super viewDidLoad];
    SKMapView *mapView = [[SKMapView alloc] initWithFrame:CGRectMake( 0.0f, 0.0f,  CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame) )];
    [self.view addSubview:mapView];

    //Configure traffic
    mapView.trafficMode = SKTrafficModeTrafficAndIncidents;
    mapView.clusterIncidents = YES;
    mapView.minimumIncidentsZoomLevel = 11.0;
}

Swift

import SKMaps/SKMapView+Traffic
class MapDisplayViewController : UIViewController {
    override func viewDidLoad () {
        super.viewDidLoad()
        let mapView = SKMapView(frame: CGRectMake( 0.0, 0.0, CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame)))
        self.view.addSubview(mapView)

        //Configure traffic
        mapView.trafficMode = SKTrafficMode.TrafficAndIncidents
        mapView.clusterIncidents = YES
        mapView.minimumIncidentsZoomLevel = 11.0
    }
}

Traffic routing

The route calculations are managed by the SKRoutingService singleton class. The SKRoutingDelegate protocol provides information about the result of a route calculation through its delegate methods.

The trafficRoutingMode and useLiveTraffic properties of SKRouteSettings control the usage of live traffic date when calculating the routes.

Objective-C

#import <SKMaps/SKMaps.h>
@implementation RoutingViewController<SKRoutingDelegate>
- (void)viewDidLoad{
    [super viewDidLoad];
    SKMapView *mapView =[[SKMapView alloc] initWithFrame:CGRectMake( 0.0f, 0.0f,  CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame) )];
    [SKRoutingService sharedInstance].routingDelegate = self; // set for receiving routing callbacks
    [SKRoutingService sharedInstance].mapView = mapView; // use the map view instance for route rendering
    SKRouteSettings* route = [[SKRouteSettings alloc]init];

    route.startCoordinate=CLLocationCoordinate2DMake(37.447692, -122.166016);
    route.destinationCoordinate=CLLocationCoordinate2DMake(37.437964, -122.141147);
    route.shouldBeRendered = YES; // If NO, the route will not be rendered.
    route.routeMode = SKRouteCarFastest;
    route.maximumReturnedRoutes = 3;
    route.useLiveTraffic = YES;
    route.trafficRoutingMode = SKTrafficModeTrafficAndIncidents;
    SKRouteRestrictions routeRestrictions = route.routeRestrictions;
    routeRestrictions.avoidHighways = YES;
    route.routeRestrictions = routeRestrictions;
    [[SKRoutingService sharedInstance] calculateRoute:route];
}

- (void)routingService:(SKRoutingService *)routingService didFinishRouteCalculationWithInfo:(SKRouteInformation*)routeInformation{
    NSLog(@"Route is calculated.");
    [routingService zoomToRouteWithInsets:UIEdgeInsetsZero duration:1]; // zoom to current route
}

- (void)routingServiceDidFailRouteCalculation:(SKRoutingService *)routingService{
    NSLog(@"Route calculation failed.");
}
@end

Swift

import SKMaps
class RoutingViewController : UIViewController, SKRoutingDelegate {
    override func viewDidLoad () {
        super.viewDidLoad()
        let mapView = SKMapView(frame: CGRectMake( 0.0, 0.0, CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame)))
        self.view.addSubview(mapView)
        SKRoutingService.sharedInstance().routingDelegate = self // set for receiving routing callbacks
        SKRoutingService.sharedInstance().mapView = mapView // use the map view instance for route rendering

        let route = SKRouteSettings()
        route.startCoordinate = CLLocationCoordinate2DMake(37.447692, -122.166016)
        route.destinationCoordinate = CLLocationCoordinate2DMake(37.437964, -122.141147)
        route.shouldBeRendered = true // If false, the route will not be rendered.
        route.routeMode = SKRouteMode.CarFastest
        route.maximumReturnedRoutes = 3
        route.useLiveTraffic = YES;
        route.trafficRoutingMode = SKTrafficMode.TrafficAndIncidents;
        route.routeRestrictions.avoidHighways = true
        SKRoutingService.sharedInstance().calculateRoute(route)
    }

    func routingService(routingService: SKRoutingService!, didFinishRouteCalculationWithInfo routeInformation: SKRouteInformation!) {
        NSLog("Route is calculated")
        routingService .zoomToRouteWithInsets(UIEdgeInsetsZero)
    }

    func routingService(routingService: SKRoutingService!, didFailWithErrorCode errorCode: SKRoutingErrorCode) {
        NSLog("Route calculation failed")
    }
}

Traffic updates during navigation

To start a regular turn by turn navigation on a previously calculated route with traffic, use the startNavigationWithSettings: method of the SKRoutingService class. The navigation session can be configured using the SKNavigationSettings class. When the traffic conditions on the current route changed, a callback is sent. If the client application decides to take the faster route, the rerouteWithTrafficInfo method should be called and the navigation will follow the new route.

Objective-C

#import <SKMaps/SKMaps.h>
#import <SKMaps/SKRouteInformation.h>
@implementation RoutingViewController<SKRoutingDelegate,SKNavigationDelegate>
- (void)viewDidLoad{
    [super viewDidLoad];
    SKMapView *mapView = [[SKMapView alloc] initWithFrame:CGRectMake( 0.0f, 0.0f,  CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame) )];
    [SKRoutingService sharedInstance].routingDelegate = self; // set for receiving routing callbacks
    [SKRoutingService sharedInstance].navigationDelegate = self;// set for receiving navigation callbacks
    [SKRoutingService sharedInstance].mapView = mapView; // use the map view for route rendering

    SKRouteSettings* route = [[SKRouteSettings alloc]init];
    route.startCoordinate=CLLocationCoordinate2DMake(37.447692, -122.166016);
    route.destinationCoordinate=CLLocationCoordinate2DMake(37.437964, -122.141147);

    [[SKRoutingService sharedInstance] calculateRoute:route];
}

- (void)routingService:(SKRoutingService *)routingService didFinishRouteCalculationWithInfo:(SKRouteInformation*)routeInformation{
    NSLog(@"Route is calculated.");
    [routingService zoomToRouteWithInsets:UIEdgeInsetsZero]; // zooming to currrent route

    SKNavigationSettings* navSettings = [SKNavigationSettings navigationSettings];
    navSettings.navigationType=SKNavigationTypeSimulation;
    navSettings.distanceFormat=SKDistanceFormatMilesFeet;
    navSettings.showStreetNamePopUpsOnRoute=YES;
    [SKRoutingService sharedInstance].mapView.settings.displayMode = SKMapDisplayMode3D;
    [[SKRoutingService sharedInstance]startNavigationWithSettings:navSettings];
}

- (void)routingService:(SKRoutingService *)routingService didUpdateRouteTraffic:(SKRouteTrafficUpdate *)trafficUpdate {
    NSLog(@"Traffic changed on the current route.");
    //Rerouting can be done after the traffic changed
    [[SKRoutingService sharedInstance]rerouteWithTrafficInfo];
}

Swift

import SKMaps
class RoutingViewController : UIViewController, SKRoutingDelegate, SKNavigationDelegate {
    override func viewDidLoad() {
        super.viewDidLoad()

        let mapView = SKMapView(frame: CGRectMake( 0.0, 0.0, CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame)))
        self.view.addSubview(mapView)

        SKRoutingService.sharedInstance().routingDelegate = self
        SKRoutingService.sharedInstance().navigationDelegate = self
        SKRoutingService.sharedInstance().mapView = mapView

        let route = SKRouteSettings()
        route.startCoordinate = CLLocationCoordinate2DMake(37.447692, -122.166016)
        route.destinationCoordinate=CLLocationCoordinate2DMake(37.437964, -122.141147)
        route.shouldBeRendered = true
        SKRoutingService.sharedInstance().calculateRoute(route)
    }
    func routingService(routingService: SKRoutingService!, didFinishRouteCalculationWithInfo routeInformation: SKRouteInformation!) {
        routingService.zoomToRouteWithInsets(UIEdgeInsetsZero, duration)

        let navSettings = SKNavigationSettings()
        navSettings.navigationType = SKNavigationType.Simulation
        navSettings.distanceFormat = SKDistanceFormat.MilesFeet
        navSettings.showStreetNamePopUpsOnRoute = true
        SKRoutingService.sharedInstance().mapView.settings.displayMode = SKMapDisplayMode.Mode3D
        SKRoutingService.sharedInstance().startNavigationWithSettings(navSettings)
    }
}
func routingService(routingService: SKRoutingService!, didUpdateRouteTraffic trafficUpdate: SKRouteTrafficUpdate!) {
    NSLog("Traffic changed on the current route.")
    //Rerouting can be done after the traffic changed
    SKRoutingService.sharedInstance().rerouteWithTrafficInfo()
}

Map Update

SKPositionerService thumbnail

The Map Update feature allows the client to update the version of the maps to a newer version available on the server. The SKMapsVersioningManager class provides information related to the current map version, the available versions and provides functionality for updating the library to a newer map version. The SKMapsVersioningDelegate protocol provides some methods that will notify the client if a new version is available on the server, when initializing the library. This protocol also provides a method to notify the user if some of the installed offline packages can be updated.

Objective-C

@interface AppDelegate () <SKMapVersioningDelegate, UIAlertViewDelegate>
@end

@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    SKMapsInitSettings* initSettings = [[SKMapsInitSettings alloc]init];
    initSettings.mapDetailLevel = SKMapDetailLevelFull; //To use Full version of maps.
    [[SKMapsService sharedInstance]initializeSKMapsWithAPIKey:API_KEY settings:initSettings];
    [[SKPositionerService sharedInstance]startLocationUpdate];
    [SKMapsService sharedInstance].mapsVersioningManager.delegate = self;

    [self.window makeKeyAndVisible];
    return YES;
}

- (void)mapsVersioningManager:(SKMapsVersioningManager *)versioningManager loadedWithOfflinePackages:(NSArray *)packages updatablePackages:(NSArray *)updatablePackages
{
    NSLog(@"%d updatable packages",updatablePackages.count);
    for (SKMapPackage *package in updatablePackages) {
        NSLog(@"%@",package.name);
    }
}
- (void)mapsVersioningManager:(SKMapsVersioningManager *)versioningManager detectedNewAvailableMapVersion:(NSString *)latestMapVersion currentMapVersion:(NSString *)currentMapVersion {
    NSLog(@"Current map version: %@ \n Latest map version: %@",currentMapVersion, latestMapVersion);
    NSString* message = [NSString stringWithFormat:@"A new map version is available on the server: %@ \n Current map version: %@",latestMapVersion,currentMapVersion];
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"New map version available" message:messagedelegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"Update", nil];
    [alert show];
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
    if (buttonIndex == 1) {
        NSArray *availableVersions = [[SKMapsService sharedInstance].mapsVersioningManager availableMapVersions];
        SKVersionInformation *latestVersion = availableVersions[0];
        [[SKMapsService sharedInstance].mapsVersioningManager updateToVersion:latestVersion.version];
    }
}

Swift

class AppDelegate: NSObject, UIApplicationDelegate, SKMapVersioningDelegate, UIAlertViewDelegate {

     var window: UIWindow!

     func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
         self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
         let initSettings = SKMapsInitSettings()
         initSettings.mapDetailLevel = SKMapDetailLevel.Full //Use Full version of maps.
         //Can be set to a light version.

         SKMapsService.sharedInstance().initializeSKMapsWithAPIKey(API_KEY, settings: initSettings)
         SKPositionerService.sharedInstance().startLocationUpdate()
         SKMapsService.sharedInstance().mapsVersioningManager.delegate = self

         self.window.makeKeyAndVisible()
         return true
     }

     func mapsVersioningManager(versioningManager: SKMapsVersioningManager!, detectedNewAvailableMapVersion latestMapVersion: String!, currentMapVersion: String!) {
         NSLog("Current map version: %@ \n Latest map version: %@", currentMapVersion, latestMapVersion)
         let message = String(format:"A new map version is available on the server: %@ \n Current map version: %@",latestMapVersion,currentMapVersion)
         let alert = UIAlertView(title: "New map version available", message: message, delegate: self, cancelButtonTitle:"Cancel", otherButtonTitles:"Update")
     }

     func alertView(alertView: UIAlertView, clickedButtonAtIndex buttonIndex: Int) {
         if (buttonIndex == 1) {
             let availableVersions = SKMapsService.sharedInstance().mapsVersioningManager.availableMapVersions
             let latestVersion = availableVersions[0] as SKVersionInformation
             SKMapsService.sharedInstance().mapsVersioningManager.updateToVersion(latestVersion.version)
         }
     }
 }

SDK Resources documentation

Beside the framework binary and public header files, the SDK contains two other packages:

SKMaps.bundle

The SKMaps.bundle contains 4 important folders:

  • PreinstalledMaps, used for pre-bundled offline map packages. Detailed at the Managing pre-bundled map packages section.
  • MapViewResources, with resources used by the SKMapView’s additional views, such as the callout view.
  • MapResources, with resources used for styling the map. It contains a few mandatory folders : Common, Routing and Shaders, plus other folders, each containing resources for a map style. As long as a style is not used by the application, the corresponding folder can be deleted, in order to keep the application size smaller.
  • AdvisorConfigs, with audio advisor configuration files.

SKAdvisorResources.bundle

The SKAdvisorResources.bundle is used by the audio advisor component during a turn by turn navigation session, and contains:

  • Languages, a folder with language packages, each containing sound files and grammar rules configuration. Any unnecessary language can be deleted, if it won’t be used by the application.

If audio advices are not required by the client application, it’s ok not to add the SKAdvisorResources.bundle to the project at all.

SDK Update Procedure (2.X to 3.Y)

  • Replace SKMaps.framework
  • Replace SKMaps.bundle
  • Replace SKAdvisorResources.bundle (if you are using this bundle)

Known limitations

Multiple independent SKMapView instances visible at the same time. The SDK supports one visible SKMapView instance at a time. Viewing multiple map instances in the same screen will result in an undefined behaviour.

Workaround: Use the -(UIImage*)lastRenderedFrame method of SKMapView if the above scenario cannot be avoided.

Calculating routes using a list of points or a GPX file

Instead of using real inputs, a route can be calculated by using a list of coordinates or a GPX file.

  • If you want to calculate a route using your own coordinates, you can use the calculateRouteWithSettings:customLocations: method found in the SKRoutingService+GPSFiles.h, which takes as a second argument an array of CLLocation objects that define your custom route.
  • For using a GPX file as an input for your route calculation, use the SKRoutingService method calculateRouteWithSettings:GPSFileElement:. An example of using this can be found in the GPSFilesViewController class from the Demo Project.

Simulating a navigation experience

Simulating a navigation is very useful when you want to test the functionality of your app without having to actually go for a drive. This can be done in two ways:

  • Regular navigation simulation, which starts at the route's start point and simulates the navigation until it reaches the destination.

Objective-C

SKNavigationSettings *navigationSettings = [SKNavigationSettings navigationSettings];
navigationSettings.navigationType = SKNavigationTypeSimulation;
[[SKRoutingService sharedInstance] startNavigationWithSettings:navigationSettings];

Swift

let navigationSettings = SKNavigationSettings()
navigationSettings.navigationType = .Simulation
SKRoutingService.sharedInstance().startNavigationWithSettings(navigationSettings)
  • Navigation simulation from log file, which uses the positions from a log file and simulates a navigation from the first to the last location in the file. The log can be a recording of an actual drive or computer generated. By using this feature you can simulate positions in "Free Drive" / "Free Walk" mode.

Objective-C

SKNavigationSettings *navigationSettings = [SKNavigationSettings navigationSettings];
navigationSettings.navigationType = SKNavigationTypeSimulationFromLogFile;
[SKPositionerService sharedInstance].positionsLogFilePath = logFilePath;
[[SKRoutingService sharedInstance] startNavigationWithSettings:navigationSettings];

Swift

let navigationSettings = SKNavigationSettings()
navigationSettings.navigationType = .SimulationFromLogFile
SKPositionerService.sharedInstance().positionsLogFilePath = logFilePath
SKRoutingService.sharedInstance().startNavigationWithSettings(navigationSettings)

Logging and debugging

If you encounter any issues while using the SDK, please check if the same behaviour is found in the provided Demo application. If it doesn't, please provide as much information as possible in order to be able to reproduce and fix it.

A reported bug should include:

  • steps to reproduce
  • code snippets
  • logs
  • crash logs

The log files (.log) created by the SDK can be found in the documents directory under the "SKMapsLogs" directory.