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.
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.
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.
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!)
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:));
The method setPlaceStylesWithIds
can take external IDs as key.
- (void)setPlaceStylesWithIds:(NSDictionary<NSString *, MWZStyle *> *)styleByPlace;
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.
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.
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.
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.
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'
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
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.
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.
Mapwize SDK is using the Mapbox SDK to render the outdoor map. There are 2 options regarding the outdoor:
styleUrl
in your MWZMapwizeConfiguration.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")
}
}
The map can be loaded with the following options available in the MWZOptions class:
floor
to set the default floor when entering a venue. Floors are NSNumbers and can be decimal values.language
to set the default language for venues. It is a string with the 2 letter code for the language. Example: "fr" or "en".universeId
to set the default universe.restrictContentToVenueId
to restrict the displayed content to a venue.restrictContentToVenueIds
to restrict the displayed content to a list of venues.restrictContentToOrganizationId
to restrict the displayed content to an organization.centerOnVenueId
to center on a venue at start.centerOnPlaceId
to center on a place at start.mainColor
If set, this color is used to display markers, directions and UI.mapwizeLogoClickable
If true, the Mapwize logo will display legal information on click. Default is YES.The Mapwize delegate provides callback for the events taking place on the map.
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
}
}
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.
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:
MWZFollowUserMode.NONE
the map does not move if the location changesMWZFollowUserMode.FOLLOW_USER
the map moves laterally in order to keep the user position at the center. Zoom and rotation do not change.MWZFollowUserMode.FOLLOW_USER_AND_HEADING
the map moves and rotates in order to keep the user position in the center and the heading towards the top of the device.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.
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.
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;
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.
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;
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];
}
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.
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 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.
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;
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.
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
markerUrl
the url to an image to use for the marker (NSString)fillColor
the color of the inside of the polygon. Only used if the place is a polygon. (NSString with format "#000000")strokeColor
the color of the border of the polygon. Only used if the place is a polygon. (NSString with format "#000000")fillOpacity
the opacity of the inside of the polygon. Only used if the place is a polygon. (NSNumber between 0 and 1)strokeOpacity
the opacity of the border of the polygon. Only used if the place is a polygon. (NSNumber between 0 and 1)strokeWidth
the width of the border of the polygon. Only used if the place is a polygon. (NSNumber positive)isMarkerDisplay
defines if the marker of the place is displayed or not. (Boolean default true)isShapeDisplay
defines if the shape of the place is displayed or not. (Boolean default true)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.
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;
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.
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/
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 :
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)
}
....
For any question or request, please contact us at support@mapwize.io.