Cocoapods
Carthage compatible

MapwizeSDK on iOS

The Mapwize iOS SDK allows you to add indoor maps and wayfinding in your iOS apps. After your map has been created in Mapwize Studio, you can use this SDK to display the map in your app and let the user interact with it.

This SDK only comes with the core MapView and API and is intended for data visualization, asset tracking or advanced Mapwize integrations. For a first integration of Mapwize in your project for wayfinding purposes, you might want to consider Mapwize UI.

Open-source Mapwize UI

If you are looking for a fully featured and ready to use View to add Mapwize Indoor Maps and Navigation in your iOS app, have a look at Mapwize UI. This open-source project enriches the core SDK with interfaces for search and directions, that you can use as is or modify to your taste.

We are ourselves using Mapwize UI to build the Mapwize app available on the App Store.

External IDs

Using external IDs is the best way to reference objects in your code. External IDs can be defined in Mapwize Studio for Venues and Places. With external IDs, you can easily change map content on the Studio side while avoiding any change in your code, for example promoting a beta venue to production. You can also specify multiple external IDs for a single object.

All methods that use an "ID" can take either an external ID or a Mapwize ID. But please note that Mapwize IDs are randomly generated and that the IDs will change in case of object copy or clone.

Mapwize initialization

External IDs can be used in centerOnVenueId, centerOnPlaceId, restrictContentToVenueId, restrictContentToVenueIds, restrictContentToOrganizationId options.

// Restrict content to organization
let mapwizeConfiguration = MWZMapwizeConfiguration(apiKey: "YOUR_API_KEY")
let options = MWZOptions()
options.restrictContentToOrganizationId = "MyOrganization"
mapwizeView = MWZMapView(frame: self.view.frame, options: options, mapwizeConfiguration: mapwizeConfiguration)
mapwizeView?.delegate = self
mapwizeView?.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.view.addSubview(mapwizeView!)
// Restrict content to multiple venue and center on a Venue
let mapwizeConfiguration = MWZMapwizeConfiguration(apiKey: "YOUR_API_KEY")
let options = MWZOptions()
options.restrictContentToVenueIds = ["MyOrganization>>MyVenue","MyOrganization>>MyOtherVenue"]
options.centerOnVenueId = "MyOrganization>>MyVenue"
mapwizeView = MWZMapView(frame: self.view.frame, options: options, mapwizeConfiguration: mapwizeConfiguration)
mapwizeView?.delegate = self
mapwizeView?.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.view.addSubview(mapwizeView!)
// Restrict content to one Venue and center on a Place
let mapwizeConfiguration = MWZMapwizeConfiguration(apiKey: "YOUR_API_KEY")
let options = MWZOptions()
options.restrictContentToVenueId = "MyOrganization>>MyVenue"
options.centerOnPlaceId = "MyOrganization>>MyVenue>>MyPlace"
mapwizeView = MWZMapView(frame: self.view.frame, options: options, mapwizeConfiguration: mapwizeConfiguration)
mapwizeView?.delegate = self
mapwizeView?.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.view.addSubview(mapwizeView!)

Retrieving information

The MapwizeApi methods getPlaceDetailsWithIdentifier and getVenueDetailsWithIdentifier can take an external ID as parameter. It allows you to retrieve information about Venues and Places

- (void)getVenueDetailsWithIdentifier:(NSString *_Nonnull)identifier
                              success:(void (^_Nonnull)(MWZVenueDetails *_Nonnull venue))success
                              failure:(void (^_Nonnull)(NSError *_Nonnull error))failure
NS_SWIFT_NAME(getVenueDetails(identifier:success:failure:));

- (void)getPlaceDetailsWithPlaceIdentifier:(NSString *_Nonnull)identifier
                    success:(void (^_Nonnull)(MWZPlaceDetails *_Nonnull details))success
                    failure:(void (^_Nonnull)(NSError *_Nonnull error))failure
NS_SWIFT_NAME(getPlaceDetails(identifier:success:failure:));

Setting places style

The method setPlaceStylesWithIds can take external IDs as key.

- (void)setPlaceStylesWithIds:(NSDictionary<NSString *, MWZStyle *> *)styleByPlace;

OfflineManager


The download methods handles the Mapbox download automatically. The skipMapboxDownload argument has been removed.

When a download succeeds, it now returns an OfflineRegion object (instead of nothing before). This OfflineRegion object can be retrieved later using the getOfflineRegions methods and contains information about the donwloaded content (venueId, universeId, minZoom and maxZoom).

The OfflineRegion replaces the venue, universe pair that was used before. The methods

+ (BOOL) isOfflineForVenue:(MWZVenue*) venue universe:(MWZUniverse*) universe;
- (NSArray<MWZVenue*>*) getOfflineVenues;
- (NSArray<MWZUniverse*>*) getOfflineUniversesForVenue:(MWZVenue*) venue;


have been removed.

The offline manager provides methods to check if an update is available for an OfflineRegion, update its data or remove the OfflineRegion. Those operations cannot be done using a venue, universe pair anymore.

DirectionMode


In the previous versions, you could use the isAccessible parameter in the direction API to search for an accessible direction. isAccessible was conveniant but limiting. We are now adding a more general concept in order to provide more customizable direction modes in your venue.

DirectionMode are configured on Mapwize Studio for each venue/universe. Because of that, the SDK does not know what direction modes are available before entering in a venue.

There are two way to retrieve the available direction modes:

When you have your direction modes, you can use one of them to make a direction request.

Direction modes have a type. The accessible directions you already has configured in the Studio have been automatically migrated to a mode with type ACCESSIBLE.

For convenience, the old direction api method with the isAccessible parameter have been kept but we encourage you to make the change as soon as you can. The offline direction engine no longer supports the isAccessible parameter so the request will always be sent to the server.

Mapbox

The Mapwize iOS SDK is built on top of Mapbox GL native for iOS.

The Mapbox map is used as base map to display the outdoor. It is an amazingly powerful SDK allowing you to do a lot of cool stuff with the map. If you want to move the map, rotate it, overlay your own data or more, you can do it directly by controlling the Mapbox map. Adding Mapwize does not remove any capability from Mapbox, it just adds more. Have a look at the Mapbox documentation to see all features.

With the Mapwize SDK, we are extending Mapbox adding the possibility of getting inside buildings. As you zoom on the map, you will automatically enter in buildings, see the different floors and be able to navigate inside. If you want to change floors, see different universes (views) of the building, draw directions inside or display the user's indoor location, then you'll interact with the Mapwize plugin.

Compatibility

Mapwize iOS SDK is compatible with iOS 10 and higher.

The SDK is written in Objective-C for maximum compatibility and performance. However, it is working and extensively tested in both Objective-C and Swift apps.

Adding Mapwize to your project

Cocoapod

Mapwize is available through CocoaPod. Using pod, all dependencies, including Mapbox, will be added automatically. If you do not want to use pod, you will need to manually download the MapwizeSDK framework as we as all dependencies.

Simply add the following line in your Podfile

pod 'MapwizeSDK'

Carthage

Mapwize is available through Carthage since 1.7.0.

Add the following lines to you Cartfile

github "IndoorLocation/indoor-location-ios" ~> 1.0.5
github "Mapwize/SSZipArchive" == 2.2.0
binary "https://www.mapbox.com/ios-sdk/Mapbox-iOS-SDK.json" ~> 5.9.0
binary "https://sdk.mapwize.io/mapwize-sdk-ios/Carthage/MapwizeSDK.json" ~> 3.5.1

Mapwize API Key

You'll need a Mapwize API key to load the plugin and allow API requests. Simply add MWZMapwizeApiKey with the key in your info.plist.

To get your own Mapwize API key, sign up for a free account at mapwize.io. Then within the Mapwize Studio, navigate to "API Keys" on the side menu.

Mapwize demo keys are available for testing in the demo projects. Please note they cannot be used in production.

Changing api key at runtime

In some case, you may want to display a map using a different api that the one used for initialization. For example, you displayed a first map for an organization A and you want to display another map for an organization B. The MWZMapwizeConfiguration is there for that.

Configuring the outdoor map

Mapwize SDK is using the Mapbox SDK to render the outdoor map. There are 2 options regarding the outdoor:

Adding your first map

Here is the most simple demo ViewController in Swift:

import Foundation
import UIKit
import MapwizeSDK

class CreateMapViewController: UIViewController {
    private var mapwizeView: MWZMapView?
    private var mapwizeApi: MWZMapwizeApi?

    override func viewDidLoad() {
        super.viewDidLoad()
        let mapwizeConfiguration = MWZMapwizeConfiguration(apiKey: "YOUR_API_KEY")
        mapwizeApi = MWZMapwizeApiFactory.getApi(mapwizeConfiguration:mapwizeConfiguration)
        let options = MWZOptions()
        mapwizeView = MWZMapView(frame: self.view.frame, options: options, mapwizeConfiguration: mapwizeConfiguration)
        mapwizeView?.delegate = self
        mapwizeView?.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        self.view.addSubview(mapwizeView!)
    }
}
extension CreateMapViewController: MWZMapViewDelegate {
    func mapViewDidLoad(_ mapView: MWZMapView) {
        print("Mapwize is ready to be used")
    }
}

Mapwize options

The map can be loaded with the following options available in the MWZOptions class:

Delegate

The Mapwize delegate provides callback for the events taking place on the map.

MWZClickEvent

In order to react to click events on the map, Mapwize provides an object that helps you know on which object the user clicked. It can be a venue, a place or just the map.

It is represented with 3 event types :

typedef enum MWZClickEventType: NSUInteger {
MAP_CLICK,
PLACE_CLICK,
VENUE_CLICK
}

The following attributes can be retrieved :

MWZClickEventType eventType;    // Enum, see above
MWZLatLngFloor* latLngFloor;    // The latLngFloor at which the click was made. The latitude and longitudes are always set, but the floor can be null. Available for all event types.
MWZPlace* place;                // Not null if eventType == PLACE_CLICK
MWZVenue* venue;                // Not null if eventType == VENUE_CLICK

A typical way to handle those event (in SWIFT):

func plugin(_ plugin: MapwizePlugin!, didTap clickEvent: MWZClickEvent!) {
    switch clickEvent.eventType {
        case VENUE_CLICK:
            let venue = clickEvent.venue
            // Do something with venue
            break
        case PLACE_CLICK:
            let place = clickEvent.place
            // Do something with place
            break;
        default:
            let latLngFloor = clickEvent.latLngFloor
            // Do something with coordinates
    }
}

Indoor Location

Displaying the user's location on the map is done by connecting an IndoorLocationProvider to Mapwize.

There are a lot of IndoorLocation providers already freely available as part of the IndoorLocation open-source framework.

If you are missing your provider, contact us at support@mapwize.io or build your own starting from the base classes.

To set the IndoorLocationProvider on the MapwizePlugin:

- (void) setIndoorLocationProvider:(ILIndoorLocationProvider*) locationProvider;

If you want to manually set the user location, you can use the ManualIndoorLocationProvider

You can retrieve the current user position on the map using ILIndoorLocation* userLocation.

There is no delegate to be notified of any new user location. Subscribe to the IndoorLocationProvider's delegate instead.

The user's heading is also displayed on the map as an arrow from the user position. The heading is retrieved directly from the device. You can read the user heading using NSNumber* userHeading. It is not possible to manually set the user heading at this point.

FollowUserMode

You can use the follow user mode feature to automatically move the map as the user location changes.

3 modes are available on the MWZFollowUserMode class:

You can retrieve the current mode using the FollowUserMode followUserMode property and you can set it using

- (void) setFollowUserMode:(FollowUserMode) followUserMode;

The FollowUserMode is automatically set to NONE when the user manually moves the map or changes floor.

Venues

Indoor maps are defined inside venues. The map will automatically enter in a venue when the zoom is sufficient, and exit the venue when the map is moved away. Only one venue can be open at a time.

The currently displayed venue can be retrieved using the getVenue method

- (MWZVenue*) getVenue;

The method returns null if no venue is displayed on the map. The willEnterVenue, didEnterVenue and didExitVenue events from the delegate can be used.

Universes

Venues can have multiple universes. Universes are like views. In Mapwize Studio, it is possible to define which elements are displayed in each universe and define security policies for each of them.

When a venue is displayed, the method (MWZUniverse*) getUniverse will return the currently displayed universe for that venue, and (void) setUniverse:(MWZUniverse*) universe will change the universe.

You can set and get the universe for each venue using

- (void) setUniverse:(MWZUniverse*) universe forVenue:(MWZVenue*) venue;
- (MWZUniverse*) getUniverseForVenue:(MWZVenue*) venue;

Floors

Venues usually have multiple floors. Floors are identified by a number which can be decimal.

When a venue is displayed, the method (NSNumber*) getFloor will return the currently displayed floor for that venue and (NSArray<NSNumber*>*) getFloors will return the list of active floors for that venue. A floor is considered active if the geometry of one layer of that floor is intersecting with the visible region on the screen.

You can change floor using

- (void) setFloor:(NSNumber*) floor;
- (void) setFloor:(NSNumber*) floor forVenue:(MWZVenue*) venue;

The floorDidChange event is fired when the floor changes.
The floorsDidChange event is fired when the list of active floors changes, which may or may not happen at venue enter, venue exit, universe change or camera move.

Languages

The title of the places that are displayed on the map can be translated in multiple languages and you can control what language is used.

Firstly, you can set the preferered language of the user. By default, titles will be displayed in the preferred language if available in the venue. Otherwise, the main language of the venue is used.

- (void) setPreferredLanguage:(NSString*) language;
- (NSString*) getPreferredLanguage;

It is also possible to specify what language to use for a specific venue

- (void) setLanguage:(NSString*) language;
- (void) setLanguage:(NSString*) language forVenue:(MWZVenue*) venue;
- (NSString*) getLanguageForVenue:(MWZVenue*) venue;
- (NSString*) getLanguage;

Moving the map

The Mapbox map offers lots of options to move the camera viewing the map. Some specific methods are available on the MapwizePlugin to move the map towards Mapwize objects:

- (void) centerOnVenue:(MWZVenue*) venue forceEntering:(BOOL) force;
- (void) centerOnPlace:(MWZPlace*) place;
- (void) centerOnUser;

Please note that the centerOnVenue takes the forceEntering parameter. If set to false, the bounds of the maps are fit so that the entire venue is visible. For large campuses, this may mean that the zoom level is not high enough to enter the venue. If forceEntering is set to true, the map will be zoomed in to be sure that the venue is displayed, possibly not showing the entire campus.

If you want more flexibility with the centerOnVenue and centerOnPlace methods, you can use the center attribute of the MWZPlace and MWZVenue objects to build your own MGLCamera object and use the Mapbox methods to move the camera. Here is an exemple :

MGLMapCamera* camera = [[MGLMapCamera alloc] init];
camera.centerCoordinate = [place center];

// You can add more properties to the camera object if you want
// camera.altitude
// camera.pitch
// camera.bearing
// ...

[mapboxMapView setCamera:camera];
// Note that mapbox offers different setCamera methods allowing to control the animation

// If it is a place, don't forget to change the floor
if (place.floor) {
    [self setFloor:place.floor];
}

Adding markers

Markers is a convenient way to add simple pins on the map.

Mapwize Markers are positioned on a specific floor and therefore only displayed if that floor is selected. However, they are not attached to a specific venue and will be displayed even if the venue is not visible.

If you want to add more complex annotations on the map, you have all the power of Mapbox at your disposal. Have a look at their documentation

By default, the image used for the pin is the standard Mapwize pin. However, you can specify another image and different options using the MWZMarkerOptions.

Promoting places (Deprecated: Promoted Places will be remove in the future and are replaced by the place select)

The title of places are displayed based on the zoom level. The objective is to display as many as possible without having collisions. By default, the order specified in Mapwize Studio is used to define which place is displayed first.

However, there are situations where you would like to change dynamically the order. For example, if the user clicks on a place and you display informations about it, you might want to make sure that the title of that place is displayed. Basically, you want to promote that place to the first position.

Promoting places make them come first on the rendering. Of course, the collision mechanism still apply so if 2 promoted places collide, then only the first in the promotion list comes first. Also, promoted places only show on their floor when their venue are visible.

Selecting place

Selecting a place allow you to highlight a specific place to make it more visible for the user. Only one place can be selected and selecting another place will deselect the first one.

A selected place will be visible regardless the zoom level of the map while the venue is displayed.

Showing Direction

To display directions on the map, the first step is to compute them. For that, you'll need to use the API methods to get a MWZDirection object as described in the API section.

When you have a MWZDirection object, you can display it on the map using

- (void) setDirection:(MWZDirection*) direction;
- (void) setDirection:(MWZDirection *)direction  options:(MWZDirectionOptions*) options;

The first method will use defaut DirectionOptions.

Once a direction is set, you can retrieve it or remove it.

- (MWZDirection*) getDirection;
- (void) removeDirection;

Showing Navigation (Dynamic direction)

Navigation is used as direction is. The difference is that navigation show the evolution of the user on the direction line.

The navigation should be used only if the starting point of the direction is the user position and if you have a IndoorLocation system working in your venue.

In order to provide an understandable visualization, Mapwize recompute the IndoorLocation to put the user position dot at the most realistic position on the direction path. Below, you will read how to use this information to recompute direction if needed.

Styling places

Do you want to show if a meeting room is available or not, or color the prefered shops of the user? Then dynamic styling of places is what you are looking for.

You can overwrite the style of any place at any time using the setStyle function. Set the style to nil to return to the default style.

The available attributes for the style are

Offline

Maps can be made available offline using the MWZOfflineManager. When offline, the following features are available:

Data is downloaded for specific universes of specific venues.

If a venue/universe has been taken offline, all data for that venue/universe will come from the local database regarless the network availability. Changes made to the venue map on Mapwize Studio will not be available in the app until a new download is done. It is the developer's responsibility to trigger venue re-downloads.

To limit the size of the data downloaded on the device, it is possible to specify a minZoom and a maxZoom. For raster layers, only the tiles in that zoom range will be downloaded. The map can still be seen at any zoom level but pixelated graphics might appear. By default, minZoom = 16 and maxZoom = 21 or 23 depending on the surface of the venue. The exact size of the download depends on too many parameters to be estimated before hand, however, for raster layers, the size of zoom n+1 is usually about 4 times the size of zoom n. When a minZoom and maxZoom have been used for a venue/universe, they cannot be changed. The only option is to delete the offlineRegion and redownload it with different parameters.

MWZOfflineManager

To download content for a specific universe of a venue and make that venue/universe available offline, use downloadDataForVenue:

- (void) downloadDataForVenue:(MWZVenue*)venue
                     universe:(MWZUniverse*)universe
                      success:(void (^)(MWZOfflineRegion* offlineRegion))success
                     progress:(void (^)(int))progress
                      failure:(void (^)(NSError *error))failure;

- (void) downloadDataForVenue:(MWZVenue*)venue
                     universe:(MWZUniverse*)universe
                      minZoom:(NSNumber*)minZoom
                      maxZoom:(NSNumber*)maxZoom
                      success:(void (^)(MWZOfflineRegion* offlineRegion))success
                     progress:(void (^)(int))progress
                      failure:(void (^)(NSError *error))failure;

To update the local data for a venue, the same downloadDataForVenue method can be used again.

You can also use the following method to check if an update is available

- (void) checkForUpdateForOfflineRegion:(MWZOfflineRegion*) offlineRegion
                               callback:(void (^)(BOOL hasChanged))callback;

And this one to update it

- (void) updateDataForOfflineRegion:(MWZOfflineRegion*) offlineRegion
                            success:(void (^)(MWZOfflineRegion* offlineRegion))success
                           progress:(void (^)(int))progress
                            failure:(void (^)(NSError *error))failure

If a venue/universe does not need to be offline anymore, removeDataForVenue can be used to delete all data from database and filesystem.

- (void) removeOfflineRegion:(MWZOfflineRegion*) offlineRegion callback:(void (^)(void))callback;

Note: After removing or downloading data, displayed maps need to be refreshed using MapwizePlugin#refreshWithVenue:(MWZVenue*) venue universe:(MWZUniverse*) universe. Otherwise, maps might not display properly or be inconsistent.

The following methods allow you yo check which venues and universes are available offline.

- (NSArray<MWZOfflineRegion*>*) getOfflineRegions;

- (MWZOfflineRegion* _Nullable) getOfflineRegionForVenue:(MWZVenue*) venue universe:(MWZUniverse*) universe;

- (MWZOfflineRegion* _Nullable) getOfflineRegionForVenueId:(NSString*) venueId universeId:(NSString*) universeId;

- (BOOL) hasOfflineRegionWithVenue:(MWZVenue*) venue universe:(MWZUniverse*) universe;

- (BOOL) hasOfflineRegionWithVenueId:(NSString*) venueId universeId:(NSString*) universeId;

API

A complete set of functions are available to query raw Mapwize objects. Some API functions are available offline if the venue and universe was downloaded using the MWZOfflineManager. The API documentation is available in the advanced documentation.

Custom Server Url

If you are using a custom instance of Mapwize server, you have to set the api URL with the key MWZServerUrl in your info.plist or use a custom MWZMapwizeConfiguration with serverUrl.

The default value is https://api.mapwize.io/

Telemetry

Mapwize Telemetry reports some details about the map usage in your application so that you can get usage statistics about your maps. Analytics are stored in an anonymous way according to our Privacy Policy. The user location is NEVER logged as part of the Telemetry logged data.

Telemetry is enabled by default but you can deactivate it using MWZMapwizeConfiguration.telemetryEnabled = NO.

Events are stored in local storage and sent in batch to Mapwize servers. At the moment, the following events are logged :

FloorController

The SDK provides a UIView component to help you display a floor selector. To add the floorController to your map, you need to

The floor controller can be created using the method

MWZFloorController(color:)

where color is the main color used for rendering.

It then exposes the following methods :

- (void) mapwizeFloorsDidChange:(NSArray<MWZFloor*>*) floors showController:(BOOL) showController language:(NSString*)language;
- (void) mapwizeFloorWillChange:(MWZFloor* _Nullable) floor;
- (void) mapwizeFloorDidChange:(MWZFloor* _Nullable) floor;

that should be called when the MWZMapViewDelegate calls the floor related method. As well as a delegate

.... MWZFloorControllerDelegate
func floorController(_ floorController: MWZFloorController!, didSelect floor: MWZFloor!)
.... 

This is a complete example on how to add and bind the floorController :

floorController = MWZFloorController(color: .green)
floorController.translatesAutoresizingMaskIntoConstraints = false
floorController.floorControllerDelegate = self
self.view.addSubview(floorController)

// Add some constraints
floorController.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -16).isActive = true
floorController.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 16).isActive = true
floorController.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 16).isActive = true


.... MWZFloorControllerDelegate
func floorController(_ floorController: MWZFloorController!, didSelect floor: MWZFloor!) {
    self.mapwizeView?.setFloor(floor.number)
}
.... 

.... MWZMapViewDelegate
func mapView(_ mapView: MWZMapView, floorsDidChange floors: [MWZFloor]) {
    floorController.mapwizeFloorsDidChange(floors, showController: true, language: mapView.getLanguage() ?? "en")
}

func mapView(_ mapView: MWZMapView, floorWillChange floor: MWZFloor?) {
    floorController.mapwizeFloorWillChange(floor)

}

func mapView(_ mapView: MWZMapView, floorDidChange floor: MWZFloor?) {
    floorController.mapwizeFloorDidChange(floor)
}
....

Evolution and support

For any question or request, please contact us at support@mapwize.io.