iOS guide



Indoorway lets you find your way indoors. Check it out!


  • Indoor location
  • Navigation
  • Map view
    • Customizable colors, size and fonts
    • Possibility to add view annotations
    • MapKit like interface


IndoorwayKit framework is implemented in Swift and requires system version:

  • iOS 10.0+

If you are using Swift:

  • Xcode 8.1+
  • Swift 3.0.1+

If you are using Objective-C:

  • Xcode 7.3.1+

Location module requires Bluetoooth turned on to obtain location data.



CocoaPods is a dependency manager for Cocoa projects. To build IndoorwayKit, CocoaPods in version 1.0.0+ is required. You can easily install it from Ruby gem by using the following bash command:

$ gem install cocoapods

You can integrate IndoorwayKit to your Xcode project using CocoaPods Podfile like:

source ''

platform :ios, '10.0'

target '<Your Target Name>' do
    pod 'IndoorwayKit', '~> 1.1.0'

And then, run bash command in your project's folder:

$ pod install


Initial configuration

Before any use of the framework you must configure it by using your API key. The best place to apply configuration is your Application Delegete application(_:didFinishLaunchingWithOptions:) method:

import IndoorwayKit

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

    IndoorwayConfiguration.configure(withApiKey: "<Your API key>")

    return true

Visitor configuration

Current user registration is optional. It is only required to aggregate locations, send tracking events and gather information for statistical purposes.

IndoorwayConfiguration.setupVisitor(withName: "John Appleseed", age: 30, sex: .male, group: "Engineers")

If user name or group is not applicable, pass empty strings. If age of the user is also not applicable, pass negative value.

Map displaying

To display map, use IndoorwayMapView object and load map by using it's building UUID and map UUID:

import IndoorwayKit

var mapView = IndoorwayMapView()

let description = IndoorwayMapDescription(
    buildingUUID: "<building UUID>",
    mapUUID: "<map UUID>"

mapView.loadMap(with: description) { [weak self] (completed) in
    self?.mapView.showsUserLocation = completed // To start displaying location if map is properly loaded

It is recommended to implement a map view's delegate protocol and set it as delegate in the map view to receive callbacks, like information that map has finished launching, which can be used to retry if it fails:

mapView.delegate = self
extension ViewController: IndoorwayMapViewDelegate {

    // Map loading
    func mapViewDidFinishLoadingMap(_ mapView: IndoorwayMapView) {
        print("Map view did finish loading")
    func mapViewDidFailLoadingMap(_ mapView: IndoorwayMapView, withError error: Error) {
        print("Map view did fail loading map with error: \(error.localizedDescription)")
    // And many other methods

For more information check other methods in protocol IndoorwayMapViewDelegate.

Custom map rendering

To customize displaying attributes of map view simply implement protocol IndoorwayMapRendererConfiguration:

import IndoorwayKit

class ExampleMapRenderingCongiguration: IndoorwayMapRendererConfiguration {

    struct MapRendererColors {
        static let background = UIColor.white.cgColor
        static let outline =
        static let room = UIColor.gray.cgColor
        static let toilet =
        static let stairs = UIColor.darkGray.cgColor
        static let inaccessible =
        static let unknown =
        static let selected =
        static let text = UIColor.white.cgColor

    struct MapRendererSizes {
        static let objectStrokeWidth = 2.0
        static let outlineStrokeWidth = 4.0
        static let textSize = 12.0

    func backgroundColor() -> CGColor {
        return MapRendererColors.background

    func strokeWidth(forObject object: IndoorwayObjectInfo) -> CGFloat {
        return MapRendererSizes.objectStrokeWidth

    func outlineFillColor() -> CGColor? {
        return MapRendererColors.outline

    func outlineStrokeWidth() -> CGFloat {
        return MapRendererSizes.outlineStrokeWidth

    func outlineStrokeColor() -> CGColor? {
        return MapRendererColors.outline

    func strokeColor(forObject object: IndoorwayObjectInfo) -> CGColor? {
        if object.objectType != IndoorwayObjectType.path {
            return MapRendererColors.outline
        return nil

    func fillColor(forObject object: IndoorwayObjectInfo) -> CGColor? {
        switch (object.objectType, {
        case (.toilet, _):
            return MapRendererColors.toilet
        case (.room, _):
        case (.stairs, _):
            return MapRendererColors.stairs
        case (.inaccessible, _):
            return MapRendererColors.inaccessible
        case (.unknown, _):
            return MapRendererColors.unknown
            return MapRendererColors.unknown

    func textColor() -> CGColor {
        return MapRendererColors.text

    func textStrokeColor() -> CGColor{
        return MapRendererColors.text

    func textSize() -> CGFloat {
        return MapRendererSizes.textSize

    var selectedObjectFillColor: CGColor? {
        return MapRendererColors.selected

    var selectedObjectStrokeColor: CGColor? {
        return MapRendererColors.selected

and apply it to the map view which you want to customize:

// Hold strong reference to object
let renderingConfiguration = ExampleMapRenderingCongiguration()

// Set custom rendering configuration
mapView.renderingConfiguration = renderingConfiguration

Custom map annotations

Map view supports custom annotations. To add annotation implement class conforming to IndoorwayAnnotation protocol:

class ExampleAnnotation: NSObject, IndoorwayAnnotation {
    var coordinate: IndoorwayLatLon
    var title: String?
    var subtitle: String?

    init(coordinate: IndoorwayLatLon) {
        self.coordinate = coordinate

and add the following annotation to the map view:

let annotation = ExampleAnnotation(
    coordinate: IndoorwayLatLon(latitude: latitude, longitude: longitude)

Annotations can be removed as shown in the following example:


Annotations have a default view, but you can customize them by implementing IndoorwayMapViewDelegate method as presented below:

enum ExampleMapViewIdentifiers: String {
    case exampleAnnotation = "example.annotation.view.identifier.example"
    case userLocationAnnotation = "example.annotation.view.identifier.userlocation"

func mapView(_ mapView: IndoorwayMapView, viewForAnnotation annotation: IndoorwayAnnotation) -> IndoorwayAnnotationView? {
    var view: IndoorwayAnnotationView? = nil

    // Example annotation
    if let annotation = annotation as? ExampleAnnotation {
        // Reused annotation view
        if let reusedView = dequeueReusableAnnotationView(withIdentifier: ExampleMapViewIdentifiers.exampleAnnotation.rawValue) {
            reusedView.annotation = annotation
            view = reusedView
        // New annotation view
        else {
            let newView = IndoorwayAnnotationView(annotation: annotation, reuseIdentifier: ExampleMapViewIdentifiers.exampleAnnotation.rawValue)
            newView.frame = CGRect(x: 0, y: 0, width: 15, height: 15)
            newView.backgroundColor =
            newView.layer.cornerRadius = view.bounds.height/2.0
            view = newView

    // User location view
    else if let annotation = annotation as? IndoorwayUserLocation {
        // Reused annotation view
        if let reusedView = dequeueReusableAnnotationView(withIdentifier: ExampleMapViewIdentifiers.userLocationAnnotation.rawValue) {
            reusedView.annotation = annotation
            view = reusedView
        // New annotation view
        else {
            let newView = IndoorwayAnnotationView(annotation: annotation, reuseIdentifier: ExampleMapViewIdentifiers.userLocationAnnotation.rawValue)
            newView.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
            newView.backgroundColor =
            newView.layer.cornerRadius = view.bounds.height/2.0
            view = newView
    return view

IndoorwayMapViewDelegate has also methods that will be called when user did selects or deselects annotation view:

func mapView(_ mapView: IndoorwayMapView, didSelectAnnotationView view: IndoorwayAnnotationView) {
    print("User did select annotation view \(view.description)")
func mapView(_ mapView: IndoorwayMapView, didDeselectAnnotationView view: IndoorwayAnnotationView) {
    print("User did deselect annotation view  \(view.description)")

Indoor objects selection

When user selects or deselects indoor objects, the following methods from the map view's delegate are called:

func mapView(_ mapView: IndoorwayMapView, didSelectIndoorObject indoorObjectInfo: IndoorwayObjectInfo) {
    print("User did select indoor object with identifier: \(indoorObjectInfo.objectId)")
func mapView(_ mapView: IndoorwayMapView, didDeselectIndoorObject indoorObjectInfo: IndoorwayObjectInfo) {
    print("User did deselect indoor object with identifier: \(indoorObjectInfo.objectId)")

You can also specify which of indoor objects can be selected:

func mapView(_ mapView: IndoorwayMapView, shouldSelectIndoorObject indoorObjectInfo: IndoorwayObjectInfo) -> Bool {
    print("Should user select indoorway object with this \(indoorObjectInfo.objectId) identifier or type \(indoorObjectInfo.objectType)?")
    return true

Indoor objects can be also selected programmatically by identifier:

mapView.selectObject(withIndoorwayObjectId: "<indoor object identifier>")

or IndoorwayObjectInfo:

mapView.selectObject(withIndoorwayObject: objectInfo)

Tap detection

When user tap's location is not associated with annotation or indoor object, the following method from the map view's delegate will be called:

func mapView(_ mapView: IndoorwayMapView, didTapLocation location: IndoorwayLatLon) {
    print("User did tap location: \(location.latitude) \(location.longitude)")

Indoor objects list

You can access the list of indoor objects on the loaded map using indoorObjects property of IndoorwayMapView object:


To search for indoor objects that contain specific tag:

let tag = "Sample tag"
let objectsWithTag = mapView.indoorObjects
    .filter {


There are several ways you can display navigation in map view:

  • navigate from current user's location to specific destination location
mapView.navigate(to: location)
  • navigate from specific start location to specific destination location
mapView.navigate(form: startLocation, to: destinationLocation)
  • navigate from current user's location to specific indoor object with identifier
mapView.navigate(toObjectWithId: "<destination indoor object identifier>")
  • navigate from specific indoor object with identifier to specific destination indoor object with identifier
mapView.navigate(fromObjectWithId: "<start indoor object identifier>", toObjectWithId: "<destination indoor object identifier>")
  • navigate from current user's location to specific indoor object
mapView.navigate(to: indoorObject)
  • navigate from specific indoor object to specific destination indoor object
mapView.navigate(from: startObject, to: destinationObject)

To stop displaying navigation call:


Path points

You can access the list of navigation path points on the loaded map using pathPoints property of IndoorwayMapView object:


Indoor location manager

Indoor location manager is a source of information of user's location. To create manager and set it delegate:

var locationManager = IndoorwayLocationManager()
locationManager.delegate = self

start location updates:


and conform to location manager delegate's protocol IndoorwayLocationManagerDelegate:

func indoorwayLocationManager(_ manager: IndoorwayLocationManager, didFailWithError error: Error) {
    print("Location manager did fail with error \(error.localizedDescription)")
func indoorwayLocationManager(_ manager: IndoorwayLocationManager, didUpdateLocation location: IndoorwayLocation) {
    print("Location manager did update location: (latitude:\(location.latitude), longitude:\(location.longitude))")
func indoorwayLocationManager(_ manager: IndoorwayLocationManager, didUpdateHeading heading: IndoorwayHeading) {
    print("Loation manager did update heading: \(heading)")

To stop location updates:


Location ranging

The framework allows you to define custom ranges. When user enters or exits defined region, IndoorwayLocationManagerDelegate provides callbacks for these actions:

func locationManager(_ manager: IndoorwayLocationManager, didEnter region: IndoorwayRegion) {
    let enterData: IndoorwayRegionData? = region.notifications
        .flatMap {
            if case .enter(let data) = $0 { return data }
            else { return nil }
    guard let data = enterData else { return }
    print("Entered: \(data.title) with description: \(data.description)")

func locationManager(_ manager: IndoorwayLocationManager, didExit region: IndoorwayRegion) {
    let exitData: IndoorwayRegionData? = region.notifications
        .flatMap {
            if case .exit(let data) = $0 { return data }
            else { return nil }
    guard let data = exitData else { return }
    print("Exit: \(data.title) with description: \(data.description)")

You can access notification data in region model region.notifications. It contains enter or exit notification with associated data. Example of obtaining enter notification data:

let enterData: IndoorwayRegionData? = region.notifications
    .flatMap {
        if case .enter(let data) = $0 { return data }
        else { return nil }

Remote defined ranges

You can specify remotely ranges to be monitored in the Indooway Dashboard. It would appear in the application automatically. You just need to handle callbacks from the delegate.

Locally defined ranges

To create range in code create an object that inherits from IndoorwayRegion. The framework provides a predefined circular region IndoorwayCircularRegion and notifications for two types of actions. As an example you can create custom range that fits your implementation needs.

First create notification that holds all data that you will receive when user enters specific region:

let enter = IndoorwayRegionNotification.exit(
        title: "Hey!",
        description: "Look at right!",
        timeout: 1.0,
        imageURL: URL(string: ""),
        actionURL: URL(string: "")

Next create, for example, a circular region specifying unique identifier, center location and radius in meters:

let circularRegion = IndoorwayCircularRegion(
    identifier: "Example unique identifier",
    center: IndoorwayLocation(
        latitude: 10.0000, // Example latitude
        longitude: 20.000, // Example longitude
        uncertainty: nil,
        buildingUUID: "The building UUID",  // Example building UUID
        mapUUID: "The map UUID"             // Example map UUID
    radius: 20.000, // Radius in meters
    notifications: [enter]

Next pass it to the IndoorwayLocationManager instance for start ranging method:


To stop ranging call:


Buildings information manager

To fetch building base information with associated maps use IndoorwayBuildingsManager class. First create building manager object:

let buildingManager = IndoorwayBuildingsManager()

and call the following method:

buildingManager.getBuildings { (buildings, error) in
    if let error = error {
        print("Error occured while obtaining buildings \(error.localizedDescription)")
    } else if let buildings = buildings {
        print("Buildings obtained:")

Points of interest types manager

To fetch points of interest types use IndoorwayPointsOfInterestTypesManager class. First create points of interest types manager object:

let poiTypesManager = IndoorwayPointsOfInterestTypesManager()

and call the following method:

poiTypeManager.getPointsOfInterestTypes { (poiTypes, error) in
    if let error = error {
        print("Error occured while obtaining POI types \(error.localizedDescription)")
    } else if let poiTypes = poiTypes {
        print("POI types obtained:")

Events tracking

The framework provides event tracking tool. You can access it from the shared instance of IndoorwayTrackingManager. First create IndoorwayEventinstance as an event that you want to track and pass it as an argument to track(_:) method:

        uuid: "Example unique identifier",
        category: "Example category",
        label: "Example label",
        interaction: "Example interaction"

For example, to track remote defined ranging events as a uuid, pass an range identifier.


This framework uses custom cryptography. When submitting your app to the AppStore that uses the framework, you must answer each of the export compliance questions:

  • Is your app designed to use cryptography or does it contain or incorporate cryptography? - YES
  • Does your app meet any of the following? [...] - YES
  • Does your app implement any encryption algorithms that are proprietary or yet-to-be-accepted as standards by international standard bodies (IEEE, IETF, ITU, and so on)? - Up to your implementation
  • Does your app implement any standard encryption algorithms instead of, or in addition to, using or accessing the encryption in Appleā€™s iOS or macOS? - YES
  • Are you releasing your app in France? - Up to your distribution


Any suggestions or reports of technical issues are welcome! Contact us via email [email protected].


IndoorwayKit is available under the custom license. See the LICENSE file for more info.

We use cookies to ensure that we give you the best experience on our website. Learn more
Got it