The iOS SDK Guide Follow
The Meridian iOS SDK has all the tools you’ll need to embed the Meridian Editor’s maps, turn-by-turn navigation, indoor location awareness, and notifications into your own custom iOS app.
Once you’ve added maps, routes, placemarks, and campaigns to the Meridian Editor, you can use the Meridian SDK to integrate that content into your Meridian-powered iOS app.
Go here to download the Meridian iOS SDK and Reference documentation.
The Meridian Samples App
The Meridian Samples app is included with the iOS SDK to demonstrate common Meridian features and workflows.
Add the SDK to Xcode
In order to simplify using the Meridian SDK library with your Xcode iOS project, we’ve bundled the SDK into a universal iOS framework.
The Meridian SDK must be compiled with Xcode 11 or higher and iOS 11 or higher.
You can add the SDK to your project using CocoaPods, an Objective-C and Swift dependency manager.
Add the SDK with CocoaPods
Complete these steps to add the Meridian SDK to your iOS Xcode project with CocoaPods.
-
Install CocoaPods.
$ gem install cocoapods
-
CocoaPods uses a textfile named Podfile to list dependencies. In the Podfile, add:
pod 'MeridianSDK'
-
Install the dependencies.
$ pod install
-
CocoaPods creates an
.xcworkspace
file. Make sure you use this to access your Xcode project.
Add SDK without CocoaPods
Complete these steps to add the Meridian SDK to your iOS Xcode project without CocoaPods.
Don’t complete these steps if you’re using CocoaPods.
-
In your Xcode project, put the Meridian.framework folder into the Embedded Binaries section.
-
In Build Settings in the Other Linker Flags property, make sure the -ObjC flag has been added. If not, then add it.
Add Build Script
Add a new “Run Script Phase” in your app target’s “Build Phases” and paste the following in the Run Script text field:
bash "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/Meridian.framework/strip-frameworks.sh"
Configure the SDK
In order to access the Meridian classes, in the relevant source files, add the line:
#import <Meridian/Meridian.h>
The MeridianSamples project has an example of what your finished project structure should look like.
Before you start using the SDK’s features, you’ll need to configure the SDK for your project. To do this, create an instance of MRConfig and set it in your application:didFinishLaunchingWithOptions: method.
MRConfig *config = [MRConfig new]; [Meridian configure:config];
Add a Meridian Token
Meridian uses token-based authentication. In order for your Meridian-powered app to communicate with the Editor, you’ll need to add a Meridian token to your didFinishLaunchingWithOptions
method:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) - Bool { // configure the Meridian SDK let config = MRConfig() config.applicationToken = "YOUR_EDITOR_TOKEN"
Meridian.configure(config)
Update Configuration Properties
The Meridian.configure(config)
function should only be called once during the lifetime of your application. In order to make subsequent changes to the configuration, modify properties directly via Meridian.sharedConfig
.
Meridian.sharedConfig()?.applicationToken = "EDITOR_TOKEN_FOR_SECOND_LOCATION"
Implement Meridian Analytics
Meridian analytics are provided by Keen and are enabled by default in the iOS SDK.
If for some reason you’d like to disable it, in AppDelegate.swift
set config.disableMeridianAnalytics
to true:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) - Bool { let config = MRConfig() config.disableMeridianAnalytics = true Meridian.configure(config)
Use Editor Keys
Use instances of MREditorKey to reference specific apps, maps, and placemarks that you’ve created in the Meridian Editor. Many of the Meridian SDK’s classes require valid keys during initialization.
// Creating a key representing your app in the Meridian Editor: MREditorKey *appKey = [MREditorKey keyWithIdentifier:@"5085468668573440"]; // Creating a key representing a map in the MeridianEditor: MREditorKey *mapKey = [MREditorKey keyForMap:@"5708115733494264" app:@"5085468668573440"]; // An alternate way to create a map key: MREditorKey *mapKey = [MREditorKey keyForMap:@"5708115733494264" app:appKey.identifier]; // Creating a key representing a placemark: MREditorKey *placemarkKey = [MREditorKey keyForPlacemark:@"375634485771" map:mapKey];
When you create an instance of MREditorKey to represent a specific map, your app ID becomes the parent of the mapKey
. Similarly, when you create a placemark key, the map ID becomes the parent of the placemarkKey
.
MREditorKey *placemarkKey = [MREditorKey keyForPlacemark:@"375634485771" map:someMapKey]; MREditorKey *mapKey = placemarkKey.parent; NSLog(@"My map ID: %@", mapKey.identifier); MREditorKey *appKey = mapKey.parent; NSLog(@"My app ID: %@", appKey.identifier);
Display Maps
MRMapViewController includes a self-contained interface for maps and directions. This makes it unnecessary for you to create your own maps interface. Initialize MRMapViewController with a valid MREditorKey to get started.
MREditorKey *mapKey = [MREditorKey keyForMap:@"5638404075159552" app:@"5468665088573440"]; // Meridian Portland Office MRMapViewController *mapController = [MRMapViewController new]; mapController.mapView.mapKey = mapKey;
MRMapViewController hosts an MRMapView and adopts the MRMapViewDelegate protocol, which handles the basic tasks delegated by MRMapView.
Map Annotations
MRMapView will always annotate a map with its placemarks, provided you haven’t set showsPlacemarks = NO. Similar to Apple’s MapKit, the Meridian SDK defines the MRAnnotation protocol for map annotations and the MRMapViewDelegate protocol provides a callback to customize the appearance of map annotations.
You can adopt the MRAnnotation protocol in your own model objects to add them to your maps and you can provide your own views to represent instances of MRPlacemark.
Alternately, you can provide your own objects by overriding mapView:viewForAnnotation: and returning instances of your own subclass of MRAnnotationView.
In the following example, we have a custom view to represent placemarks named “Cafe”:
// In the custom view controller that adopts the MRMapViewDelegate protocol, // don't forget to set your controller as the delegate of the MRMapView. - (MRAnnotationView *)mapView:(MRMapView *)mapView viewForAnnotation:(id
Load Directions
MRMapViewController handles loading and presenting directions in response to user interactions.
When you create an instance of MRMapViewController, you can set the pendingDestination property to initiate directions to that MRPlacemark as soon as the map view is visible.
If you want to present directions modally, the SDK provides a convenient way to do so with an instance method on UIViewController(http://files.meridianapps.com/meridian-ios-sdk/docs-6.0.0/Categories/UIViewController+Directions.html).
// Somewhere in your custom view controller // Assume myPlacemark is a valid MRPlacemark instance [self presentDirectionsToPlacemark:myPlacemark];
If you’re using MRMapView directly in your own view controller, you’ll need to handle loading directions manually. You can do this using MRDirectionsRequest. Specify the destination and starting point for the route using instances of MRMapItem
.
Like MapKit’s MKMapItem,
MRMapItem
contains information about a point on a map.
After configuring the request with the details of the desired route, you can use it to create an instance of MRDirections and then call calculateDirectionsWithCompletionHandler: to asynchronously calculate directions for the route.
Finally, provide a completion block to be executed when the request is complete, to handle the response and any errors.
// Somewhere in your view controller // Assume myPlacemark is a valid MRPlacemark instance MRDirectionsRequest *request = [MRDirectionsRequest new]; request.source = [MRDirectionsSource sourceWithCurrentLocation]; request.destination = [MRDirectionsDestination destinationWithMapKey:placemark.key.parent withPoint:placemark.point]; request.transportType = MRDirectionsTransportTypeAny; MRDirections *directions = [[MRDirections alloc] initWithRequest:request presentingView:self.view]; [directions calculateDirectionsWithCompletionHandler:^(MRDirectionsResponse *response, NSError *error) { if (!error) { if (response.routes.count 0) { // Normally only one route will be returned MRRoute *route = response.routes.firstObject; // If we have an MRMapView we can tell it to display this route [self.mapView setRoute:route animated:YES]; } else { NSLog(@"Error loading directions: %@", error.localizedDescription); } } }];
In the example, MRDirectionsSource sourceWithCurrentLocation indicates the route should start at the user’s current location. When creating the ‘MRDirections’ instance, the controller’s view is passed as the presentingViewController: parameter.
A new route is calculated when the user’s blue dot location is approximately 15 meters from the route.
When a valid current location is not available, this causes MRDirections to prompt the user to select a starting location with a modal search interface.
Send More Variables to Campaign Custom Endpoints
When using Campaign custom endpoints, you may want to send additional data to the custom endpoint.
Create a delegate for MRCampaignManager that implements campaignManager:userInfoForCampaign:
to define variables that will be put into parameters and sent to the custom endpoint.
- (NSDictionary *)campaignManager:(MRCampaignManager *)manager userInfoForCampaign:(NSString *)campaignIdentifier { return @{"custom_field_1": "custom_data"}; }
Get User Location
MRLocationManager determines the user’s location by gathering all the available location data for your Meridian-powered app. The MRLocationManagerDelegate protocol will make sure that you get callbacks as new locations are determined.
// In a view controller that adopts the MRLocationManagerDelegate protocol // Assume we have properties called appKey and locationManager - (void)viewDidLoad { [super viewDidLoad]; self.appKey = [MREditorKey keyWithIdentifier:@"5085468668573440"]; self.locationManager = [[MRLocationManager alloc] initWithApp:self.appKey]; self.locationManager.delegate = self; } // You may want to start getting location updates here - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [self.locationManager startUpdatingLocation]; } // And you may want to stop getting location updates here - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [self.locationManager stopUpdatingLocation]; } // Implementing MRLocationManagerDelegate methods - (void)locationManager:(MRLocationManager *)manager didUpdateToLocation:(MRLocation *)location { NSLog(@"New location: %@", location); } - (void)locationManager:(MRLocationManager *)manager didFailWithError:(NSError *)error { NSLog(@"Location error: %@", error); }
Location Sharing
Location sharing is a new Meridian feature that lets users share their location with each other. This feature is also available in our iOS and Android white-label apps.
Prerequisites
Location Sharing needs to be enabled by an admin in the Meridian Editor before you can use it in the SDK. If you’d like to enable Location Sharing, please email hpe-aruba-meridian@hpe.com.
If you haven’t already done so, you’ll need to generate a valid token in the Editor. To generate one, in the sidebar menu click Permissions, and then click Application Tokens. Click Add Application + to generate a token for your app. You can name it anything.
When configuring the Meridian SDK, set the application token you generated in the Editor:
MRConfig *config = [[MRConfig alloc] init]; config.applicationToken = @"kd84hf83ck93k39dyi3nxo3mf94"; [Meridian configure:config];
MRSharingSession
The MRSharingSession
class is used to create, log in to, and delete accounts. It’s also used to start and stop the sharing service and to receive location sharing updates.
Create an Account
// Creates an account (password can be any string). self.password = [NSUUID UUID].UUIDString; [MRSharingSession createAccountWithPassword:self.password firstName:@"Morgan" lastName:@"Smith" appKey:myAppKey completion:^(MRSharingSession * \_Nullable session, NSError * \_Nullable error) { if (error) { NSLog(@"Something went wrong: %@", error.localizedDescription); } else { // Now there's a sharing session. NSLog(@"Got session: %@", session.key.identifier); // Keeps a reference to the session. Needed for most subsequent requests. self.session = session; // Makes self the delegate to get callbacks with fresh data. self.session.delegate = self; } }];
Logging In
// Logging back in later. [MRSharingSession loginWithKey:self.session.key password:self.password appKey:myAppKey completion:^(MRSharingSession * \_Nullable session, NSError * \_Nullable error) { if (error) { NSLog(@"Something went wrong: %@", error.localizedDescription); } else { NSLog(@"Got session: %@", session.key.identifier); } }];
Start and Stop Location Sharing
// Starting/stopping the service. - (void)startSharing { [self.session startListeningForUpdates]; [self.session startPostingLocationUpdates]; } - (void)stopSharing { [self.session stopListeningForUpdates]; [self.session stopPostingLocationUpdates]; } // Adopts the MRSharingSessionDelegate protocol to receive callbacks. - (void)sharingSession:(MRSharingSession *)session friendsDidUpdate:(NSArray
MRInvite
The MRInvite
class is used to create, retrieve, and accept invitations.
Invitations expire after 7 days.
Create and Retrieve Invitations
// Creates invitations. [MRInvite createInviteWithSession:self.session completion:^(MRInvite * \_Nullable invite, NSError * \_Nullable error) { if (error) { NSLog(@"Something went wrong: %@", error.localizedDescription); } else { NSLog(@"Invitation URL: %@", invite.shareURL); } }]; // Retrieves invitations. [MRInvite getAllInvitesWithSession:self.session completion:^(NSArray *invites, NSError *error){ if (error) { NSLog(@"Something went wrong: %@", error.localizedDescription); } else { NSLog(@"Got %lu invitations", (unsigned long)invites.count); } }]; // Accepts another user's invitation. NSURL *inviteURL = someValidShareURL; [MRInvite acceptInviteWithURL:someValidShareURL session:self.session completion:^(MRFriend * \_Nullable friend, NSError * \_Nullable error) { if (error) { NSLog(@"Something went wrong: %@", error.localizedDescription); } else { NSLog(@"Now sharing locations with %@", friend.firstName); } }];
MRFriend
Use the MRFriend
class to load the details for a location sharing user, get a list of the user’s friends, and more.
Get a User’s Profile
// Gets a user's profile. [MRFriend getFriendWithKey:self.session.key session:self.session completion:^(MRFriend * \_Nullable friend, NSError * \_Nullable error) { if (error) { NSLog(@"Something went wrong: %@", error.localizedDescription); } else { NSLog(@"Got data for %@", friend.firstName); // Updates a user's profile. [friend updateFirstName:@"Morgan" lastName:@"Smith" withSession:self.session completion:^(MRFriend * \_Nullable friend, NSError * \_Nullable error) { if (error) { NSLog(@"failed to update profile: %@", error); } else { NSLog(@"profile updated successfully"); } }]; } }
Get a List of Friends
// Gets a list of a user's friends. [MRFriend getAllFriendsWithSession:self.session completion:^(NSArray
Use Local Search
You can use the MRLocalSearch, MRLocalSearchRequest, MRLocalSearchResponse, MRLocalSearchResult classes to find points of interest near the user’s location. Combine these classes with MRLocationManager to provide a location and its nearby placemarks.
For best performance, limit local search results to 20 or less.
- (void)findNearbyCafe { MRLocalSearchRequest *request = [MRLocalSearchRequest new]; request.app = self.appKey; request.location = self.locationManager.location; request.transportType = MRDirectionsTransportTypeWalking; request.naturalLanguageQuery = @"Cafe"; request.limit = 20; MRLocalSearch *search = [[MRLocalSearch alloc] initWithRequest:request]; [search startWithCompletionHandler:^(MRLocalSearchResponse *response, NSError *error) { if (response) { for (MRLocalSearchResult *result in response.results) NSLog(@"%@ is %f seconds away", result.placemark.name, result.expectedTravelTime); } else if (error) { NSLog(@"Error loading search results: %@", error.localizedDescription); } }]; }
Asset Tracking
Asset Tracking is a feature that uses Aruba Tags hardware to track valuable locations on a map. You can use the SDK to get Tag location updates and render Tag locations on a map.
Show All Tags on a Map
To show all tags on a map, set the showsTags
MRMapView
variable to true:
func showTags(mapView: MRMapView) { mapView.showTags = true }
Show a Specific Tag on a Map
To show a specific tag on a map, setup a predicate for that Tag’s MAC address:
func showTag(mapView: MRMapView, tag: MRTag) { mapView.showsTags = true mapView.tagPredicate = NSPredicate.init(format: "mac LIKE %@", tag.mac) }
Show a Tag from Search Results
To show a tag from search results, first use the Editor API to determine which map it’s on:
func showTagFromSearchResults(tag: MRTag, appID: String) { // MRTag returned via search doesn't contain location data. // Use the asset-beacons API to get more info. // Future SDK enhancements should make this explicit URL creation unnecessary. let tagEndpoint = "https://edit.meridianapps.com/api/locations/\(appID)/asset-beacons/\(tag.mac)" guard let url = URL(string: tagEndpoint) else { print("Error: cannot create URL") return } let task = URLSession.shared.dataTask(with: URLRequest(url: url)) {data, response, error in if let requestError = error { print("Tag request error: \(requestError.localizedDescription)") return } guard let tagResponse = response as? HTTPURLResponse, let tagData = data else { print("Error: did not receive tag response") return } if tagResponse.statusCode != 200 { print("Error: HTTP status code %li", tagResponse.statusCode) return } do { guard let json = try JSONSerialization.jsonObject(with: tagData, options: []) as? [String: Any] else { print("error trying to convert data to JSON") return } // Pull up the last map that a Tag was seen on. guard let calculationDictionary = json["calculations"] as? [String: Any], let defaultDictionary = calculationDictionary["default"] as? [String: Any], let locationDictionary = defaultDictionary["location"] as? [String: Any], let mapID = locationDictionary["map_id"] as? String else { print("Unable to retrieve location info from tag response") return } let controller = MRMapViewController() controller.mapView.mapKey = MREditorKey(forMap: mapID, app: appID) controller.mapView.showsTags = true controller.mapView.tagPredicate = NSPredicate.init(format: "mac LIKE %@", tag.mac) DispatchQueue.main.async { self.navigationController?.pushViewController(controller, animated: true) } } catch { print("error trying to convert data to JSON") return } } task.resume() }
Removing the 20 Beacon Limit
With the release of the Meridian iOS SDK version 2.7.2, our developers have created a workaround to the iOS limit on region monitoring.
In previous versions of the SDK, locations were limited to a total of 20 Proximity Beacon push notification campaigns or regions. Once an iOS device detected 20 regions, it was unable to receive a push notification from any additional campaigns.
This new workaround uses a dynamic region monitoring strategy to listen to a continually rotating set of the Beacons nearest the device. Before reaching the 20 region limit, our new method unmonitors those regions that are farther away. As a result, an iOS device will never reach the region limit.
This new method uses a more active method for monitoring Proximity Beacons in the background. As a result, it’s important to be mindful of your app’s background usage in order to safeguard your users’ device batteries.
Another advantage of this update is that you will no longer need to leave a Campaign’s region in order to reset it.
Comments
0 comments
Please sign in to leave a comment.