NAV Navbar
  • Introduction
  • Version
  • Installation
  • Usage
  • Function & Helpers
  • Database
  • Delegates
  • Models
  • Extra
  • Troubleshooting
  • Introduction

    Welcome to the MapLibre iOS SDK reference, an easy helper to have fully featured map interactions and routing solution.

    A full demo in swift is available upon request.


    Current stable version is: 5.14.0 with XCode support 13.3.


    Installation in managed by Cocoapods, (if you don't already have it installed, see the "Getting Started" guide).

    Add simply a few lines in your file like this:

    pod 'ProximiioMapLibre'

    And then proceed installing open your terminal to the directory where the Podfile is stored and run this command:

    pod repo update
    pod install


    In order to use the ProximiioMapLibre plugin we need to have installed and working Proximiio, in particular add support for Synchronization and caching.

    Here a simple overview on how use it:

    import ProximiioMapLibre
    /// setup map and proximiio integration
    fileprivate func setupMap() {
        /// setup map
        let mapToken = Proximiio.sharedInstance()?.token() ?? ""
        Proximiio.sharedInstance()?.delegate = self
        let config = ProximiioMapLibreConfiguration(token: mapToken)
        config.showRasterFloorplans = false
        config.showGeoJSONFloorplans = true
        /// prepare map
        mapView = MGLMapView(frame: view.frame)
        /// setup ProximiioMapLibre handler
        if let mapView = mapView {
            ProximiioMapLibre.shared.setup(mapView: mapView, configuration: config)
            ProximiioMapLibre.shared.initialize { result in
                if result == .success {
                    ProximiioMapLibre.shared.mapNavigation = self
                    ProximiioMapLibre.shared.mapInteraction = self
    #import <ProximiioMapLibre/ProximiioMapLibre.h>
    - (void) setupMap {
        NSString *mapToken = [[Proximiio sharedInstance] token];
        ProximiioMapLibreConfiguration *configuration = [[ProximiioMapLibreConfiguration alloc] initWithToken:mapToken];
        self.mapView = [[MGLMapView alloc] initWithFrame:self.view.frame];
        [self.view addSubview:self.mapView];
        if (self.mapView != NULL) {
            [[ProximiioMapLibre shared] setupWithMapView:self.mapView configuration:configuration apiVersion:@"v5"];
            [[ProximiioMapLibre shared] initialize:^(enum ProximiioMapLibreAuthorizationResult result) {
                if (result == ProximiioMapLibreAuthorizationResultSuccess) {
                    [ProximiioMapLibre shared].mapNavigation = self;
                    [ProximiioMapLibre shared].mapInteraction = self;

    Function & Helpers

    ProximiioMapLibre provides you some functions and helpers to fast manage your implementation.

    Here listed the most relevant ones.

    Follow user position

    Library has built-in automation to follow user position, you can enable/disable that with a simple snippet.

    ProximiioMapLibre.shared.followingUser = true
    ProximiioMapLibre.shared.followingUser = YES;


    New map plugin provides easy handlers for map floor navigation:

    /// force the map to change to upper floor
    /// force the map to change to lower floor
    /// force the map to change to user specified floor
    /// get current floor of the map
    let floor = ProximiioMapLibre.shared.mapFloor
    /// force the map to change to upper floor
    [ProximiioMapLibre.shared floorUp];
    /// force the map to change to lower floor
    [ProximiioMapLibre.shared floorDown];
    /// force the map to change to user specified floor
    [ProximiioMapLibre.shared floorAt:1];
    /// get current floor of the map
    NSUInteger floor = ProximiioMapLibre.shared.mapFloor;

    Map position helper

    You can easily manage to move the map with some quick helpers:

    /// center the map to user position
    ProximiioMapLibre.shared.centerAtUser(zoom: Double = 18, animated: Bool = true, completed: { success in
        /// callback on completed
    /// center the map to the specified feature
    ProximiioMapLibre.shared.centerAtFeature(_ feature: ProximiioGeoJSON, zoom: Double = 18, animated: Bool = true, completed: { success in
        /// callback on completed
    /// center the map to user position
    [ProximiioMapLibre.shared centerAtUserWithZoom:18 animated:YES
                                      completed:^(BOOL) {
    /// center the map to the specified feature
    [ProximiioMapLibre.shared centerAtFeature:feature zoom:18 animated:YES completed:^(ProximiioGeoJSON * _Nonnull) {
        /// callback on completed

    Both the two functions expose a callback that can be optionally triggered when the animation ends.


    We provide a built-in, offline, solution to calculate routes between user position and a target POI. Once a route has been calculated you can proceed starting/stopping it:

    /// start route
    /// stop route
    ProximiioMapLibre.shared.routeCancel(silent: true)
    /// start route
    [ProximiioMapLibre.shared routeStart:nil];
    /// stop route
    [ProximiioMapLibre.shared routeCancelWithSilent:YES];

    Routes can be calculate according some specific requirements, we add a PIORouteConfiguration model that take care of this.

    Also in order to provide async support, now the route is returned in a dedicate callback, with an optional PIORoute object.

    We provided also some quick helper for preview and auto start like:

    ProximiioMapLibre.shared.routeFindAndStart(configuration: PIORouteConfiguration, callback: ((PIORoute?) -> Void))
    ProximiioMapLibre.shared.routeFindAndPreview(configuration: PIORouteConfiguration, callback: ((PIORoute?) -> Void))
    [ProximiioMapLibre.shared routeFindAndStartWithConfiguration: callaback:]
    [ProximiioMapLibre.shared routeFindAndPreviewWithConfiguration: callaback:]

    Or search only:

    ProximiioMapLibre.shared.routeFind(configuration: PIORouteConfiguration, callback: ((PIORoute?) -> Void))
    [ProximiioMapLibre.shared routeFindWithConfiguration: callaback:]


    This model provide information and limitation to be applied by the offline algorithm that generates the route to a POI.

    The tweakable values are:

    let options = PIORouteConfiguration(
        start: ProximiioGeoJSON,
        destination: ProximiioGeoJSON,
        waypointList: [ProximiioWaypoint],
        wayfindingOptions: PIOWayfindingOptions

    Let's check one by one:


    Waypoints are intermediary steps for a path, SDK actually supports two types: SimpleWaypoint and VariableWaypoint.

    Simple support just a ProximiioGeoJSON as feature, instead Variable support an array.

    Basically the difference between the two is that, in Variable, you can provide a list of ProximiioGeoJSON (e.g. a list of parkings) and the wayfinding algorithm will take care of figure out the best that fit the route between your from and destination.

    An example of usage is

    let waypointSimple = SimpleWaypoint(feature: feature)
    let waypointVariable = VariableWaypoint(features: [feature1, feature2, feature3])


    PIOWayfidingOptions is an evolution of previous model PIORouteOptions.

    Here an example of implementation:

    let options = PIOWayfidingOptions()
    options.avoidBarriers = false
    options.avoidElevators = false
    options.avoidEscalators = false
    options.avoidNarrowPaths = false
    options.avoidRamps = false
    options.avoidRevolvingDoors = false
    options.avoidStairs = false
    options.avoidTicketGates = false
    options.pathFixDistance = 1.0

    By default all the values are set to false.


    PIOAnnotation allows you to add custom annotation on the marker with extra level support. Also it support onTap callback returning the object that triggered the event.

    let annotation = PIOAnnotationWithData()
    annotation.coordinate = CLLocationCoordinate2D(latitude: 60.1671950369849, longitude: 24.921695923476054)
    annotation.image = UIImage.add = 0 = 1
    annotation.title = "A test annotation at level 0"
    annotation.level = 0
    annotation.onTap = { current in
    let annotation2 = PIOAnnotationWithData()
    annotation2.coordinate = CLLocationCoordinate2D(latitude: 60.16746993048443, longitude: 24.922311676612107)
    annotation2.image = UIImage.remove = 1 = 2
    annotation2.title = "A second annotation at level 0"
    annotation2.level = 1
    annotation2.onTap = { current in
        guard let casted = current as? PIOAnnotationWithData else { return }
        print("TAPPED \(")
    let annotation3 = PIOAnnotationWithData()
    annotation3.image = UIImage.actions
    annotation3.coordinate = CLLocationCoordinate2D(latitude: 60.16714117139544, longitude: 24.92290485238969) = 2 = 3
    annotation3.title = "A third annotation at level 0"
    annotation3.level = 0
    annotation3.onTap = { current in
        guard let casted = current as? PIOAnnotationWithData else { return }
        print("TAPPED \( \(")

    In this example we are able to create a custom annotation, visible at a specific position and for a specific level. If the map changes the shown level, the annotation will be shown/hidden according.

    Once we have an annotation in place we can render that using MapLibre function:

    mapView?.addAnnotations([annotation, annotation2, annotation3])

    The solution allows you to set 1 to N annotations, is just enough to create all the single annotations and then use addAnnotations passing an array of annotation.

    In order to support the onTap callback, you will have to tweak the code you use to rendere the marker with something like:

    public func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {
            if let current = annotation as? PIOAnnotation {
                // weak identifier, if you have an ID prefer to use that
                let identifier = "\(current.coordinate.longitude)\(current.level)"
                var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier)
                if annotationView == nil {
                    annotationView = MGLAnnotationView(annotation: annotation, reuseIdentifier: identifier)
                    let imageView = UIImageView(image: current.image)
                    annotationView!.frame = CGRect(x: 0, y: 0, width: current.image.size.width, height: current.image.size.height)
                    annotationView?.centerOffset = CGVector(dx: 0, dy: -current.image.size.height / 2.0)
                    // introduces onTap callback
                    annotationView?.addTapGesture(handler: { gesture in
                // handles hiding according the floor
                annotationView?.isHidden = current.isHidden
                return annotationView
            return nil


    ProximiioMapLibre has an embedded local database to keep POI information.

    You can easily access it using PIODatabase.sharedInstance() shared object.

    let db = PIODatabase.sharedInstance()
    // amenities
    let amenities = db.amenities
    // features
    let poi = db.features
    // poi
    let poi = db.pois
    // poi and level changers
    let poi = db.poisAndLevelChanger
    ProximiioDatabase *db = PIODatabase.sharedInstance;
    // amenities
    let amenities = db.amenities
    // features
    let poi = db.features
    // poi
    let poi = db.pois
    // poi and level changers
    let poi = db.poisAndLevelChanger

    Each is an array of elements of type ProximiioGeoJSON that provide out of the box helpers for different information about the entity.


    ProximiioGeoJSON model has automated localization handling for some fields like description and title. In order to use feel free to use helpers like getTitle().


    ProximiioMapLibre exposes two delegates with different scopes:

    Their implementation and naming can have some difference between swift and objective-c implementation, refer below.


    Takes care of providing delegate for interaction on the mapbox instance like: changing floor, tapping on map or request a re-route.

    - (void)changeWithFloor:(NSInteger)floor;
    - (void)onTapWithFeature:(ProximiioGeoJSON * _Nonnull)feature;
    - (void)onRequestReRoute;
        func change(floor: Int) {
            // triggered when floor has changed
        func onTap(feature: ProximiioGeoJSON) {
            // triggered if user taps on a POI
        func onRequestReRoute() {
            // triggered if a re-route is manually requested


    Takes care of providing delegate for events connected to a navigation.

    - (void)onRouteWithRoute:(PIORoute * _Nullable)route;
    - (void)routeEventWithEventType:(enum PIORouteUpdateType)type text:(NSString * _Nonnull)text additionalText:(NSString * _Nullable)additionalText data:(PIORouteUpdateData * _Nullable)data;
    - (void)onHazardEntered:(ProximiioGeoJSON * _Nonnull)hazard;
    - (void)onSegmentEntered:(ProximiioGeoJSON * _Nonnull)segment;
    - (void)onDecisionEntered:(ProximiioGeoJSON * _Nonnull)decision;
    - (void)onLandmarkEntered:(NSArray<PIOLandmark *> * _Nonnull)landmark;
    - (void)onHazardExit:(ProximiioGeoJSON * _Nonnull)hazard;
    - (void)onSegmentExit:(ProximiioGeoJSON * _Nonnull)segment;
    - (void)onDecisionExit:(ProximiioGeoJSON * _Nonnull)decision;
    - (void)onLandmarkExit:(NSArray<ProximiioGeoJSON *> * _Nonnull)landmark;
        func onRoute(route: PIORoute?) {
            // triggered if a route has been found
        func routeEvent(eventType type: PIORouteUpdateType, text: String, additionalText: String?, data: PIORouteUpdateData?) {
            // triggered if a route event happen
        func onHazardEntered(_ hazard: ProximiioGeoJSON) {
            // triggered if user is close to hazard
        func onSegmentEntered(_ segment: ProximiioGeoJSON) {
            // triggered if user has entered a new segment
        func onPositionUpdate(_ position: CLLocationCoordinate2D) {
            // useful to have current user position
        func onHeadingUpdate(_ heading: Double) {
            // useful to have current user heading
        func onDecisionEntered(_ decision: ProximiioGeoJSON){
            // useful to have a decision poi
        func onLandmarkEntered(_ landmark: [PIOLandmark]){
            // useful to have landmarks information
        func onHazardExit(_ hazard: ProximiioGeoJSON){
            // hazard exit
        func onSegmentExit(_ segment: ProximiioGeoJSON){
            // segment exit
        func onDecisionExit(_ decision: ProximiioGeoJSON){
            // decision exit
        func onLandmarkExit(_ landmarks: [ProximiioGeoJSON]){
            // landmark exit


    The library makes use of several model and primitives. Here a list of the most relevant, their scope and values.


    This enum is used on routeEvent response, it easily provide you information about how to handle the visual interfance for the navigation.

    Supported values are the following:

    @objc public enum PIORouteUpdateType: Int {
        case calculating, recalculating // if route is being calculate or re-calculated
        case routeNotfound // if route fails to be generated
        case canceled // if the user dimiss a route
        case  finished // if the user has reached the end of a route
        // priority of the update
        case new, update, soon, immediate


    This model provides a reference to the current node of the route involved in an update. If exists it provides also a reference to the next node with a similar structure (but with optional values).

    @objc public class PIORouteUpdateData: NSObject {
        /// current index of the node is processed from the route nodeList
        @objc public let nodeIndex: Int
        /// bearing of current node
        @objc public let stepBearing: Double
        /// direction
        @objc public let stepDirection: PIOGuidanceDirection
        /// distance of the node
        @objc public let stepDistance: Double
        // optional reference to the next step
        @objc public let nextStepBearing: Double?
        @objc public let nextStepDistance: Double?
        @objc public let nextStepDirection: PIOGuidanceDirection?
        /// current position
        @objc public let position: CLLocationCoordinate2D
        /// remaining distance in meters
        @objc public let pathLengthRemaining: Double
        public var stepHeading: PIOHeading {
            return calculateHeading(stepBearing.degreeNormalized)


    This model provide an helper to the direction of the user.

    @objc public enum PIOGuidanceDirection: Int, Equatable {
        case start // beginning of the route
        case turnAround // when user need to change direction
        case finish // user reaches the end
        case straight // continue straight
        /// turn types
        case leftSlight, leftNormal, leftHard
        case rightSlight, rightNormal, rightHard
        /// level change types
        case upElevator, upEscalator, upStairs
        case downElevator, downEscalator, downStairs

    The type of turn is calculated agains the degree between user position and node according to this logic:

        /// return current information about guidance direction
         func degreesToStepDirection() -> PIOGuidanceDirection {
            let test = self.degreeNormalized
            if fabs(test) < 22.5 { return .straight }
            if fabs(test) > 157.2 { return .turnAround}
            if test < -112.5 { return .leftHard}
            if test <= -67.5 { return .leftNormal}
            if test <= -22.5 { return .leftSlight}
            if test > 157.5 { return .rightHard}
            if test >= 67.5 { return .rightNormal}
            if test >= 22.5 { return .rightSlight}
            return .straight


    This model provide an helper to the heading of the route.

    @objc public class PIOHeading: NSObject {
        @objc public var text: String
        @objc public var rotation: Double

    It exposes a degree value in rotation and a text, localized, about heading direction.


    It takes care of provide information about any landmark the routing system is able to discover close to current user location.

    It provides two sub variables side and feature.

    Side is an enum that give you information on the side the landmark is:

    @objc public enum PIOLandmarkSide: Int {
        case left, right

    Feature is a normal ProxmiioGeoJSON object.


    Hide Amenities from map

    A new feature allows you to hide amenities from map temporary. If you want for example to run a navigation and hide any other amenity to avoid any disturb to the user you can simply use this helper:

    ProximiioMapLibre.shared.blacklistRenderAmenitiesIds = ["replace:identifier"]

    The helper is able to accept as input an array of amenities that we want to remove. If we want to restore the initial contidition, we have just to pass an empty array.

    Preload POI images

    Map can make use of images for POI and Amenities, an interesting way to boost performance and lower time-await is to preload that before app starts.

    In combination with Kingfisher (that you have to install as third party pod), you can easily create a function like the one presented below.

    Looping the PIODatabase will be possible to force pre-caching of the images needed.

    Actually we suggest to run this func when both the syncAmenities and syncFeatures are finished.

    import ProximiioMapLibre
    private func preloadFeatureImages(callback: @escaping ((Bool) -> Void)) {
            var completed = 0
            PIODatabase.shared.features.forEach { feature in
                feature.images.forEach {
                    if let imgUrl = URL(string: $0) {
                        completed += 1
                        ImageDownloader.default.downloadImage(with: imgUrl, retrieveImageTask: nil, options: [], progressBlock: nil) { (image, _, url, _) in
                            completed -= 1
                            if completed == 0 {
                            if let image =  image, let url = url {
                      , forKey: url.absoluteString)


    If the library produce error on build, try add these few lines at the bottom of your Podfile.

    post_install do |installer|
        installer.pods_project.targets.each do |target|
            target.build_configurations.each do |config|
                config.build_settings['SWIFT_VERSION'] = '5.2'
                config.build_settings['ENABLE_BITCODE'] = 'NO'

    Instead if during submisison you receive a warning mail for Too many simbols you can tweak your podfile like below:

    post_install do |installer|
        installer.pods_project.targets.each do |target|
            target.build_configurations.each do |config|
                config.build_settings['DEBUG_INFORMATION_FORMAT'] = 'dwarf'

    If needed you can even combine the two above in one solution.