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, and placemarks 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 xcframework.
The Meridian SDK must be compiled with Xcode 12 or higher and iOS 13 or higher.
You can add the SDK to your project using the Swift Package Manager, CocoaPods, or manually.
Add the SDK with Swift Package Manager
Complete these steps to add the Meridian SDK to your iOS Xcode project.
- In XCode 13 or later, click "File" -> "Add Packages..."
- In the "Search or Enter Package URL" field, enter:
https://github.com/arubanetworks/meridian-ios-sdk.git - Click "Add Package".
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 the SDK Manually
Complete these steps to add the Meridian SDK to your iOS Xcode project manually.
Don’t complete these steps if you’re using CocoaPods or the Swift Package Manager.
-
In your Xcode project, put the Meridian.xcframework 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.
Note: Meridian SDK install instructions for versions 7.5 and earlier included the following Run Script Phase
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:
Swift:
import Meridian
ObjC:
#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. 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 the configuration.
Swift:
let config = MRConfig()
config.applicationToken = "YOUR_EDITOR_TOKEN"
Meridian.configure(config)
ObjC:
MRConfig *config = [[MRConfig alloc] init];
config.applicationToken = "YOUR_EDITOR_TOKEN" [Meridian configure:config];
Update Configuration Properties
The configure() 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 the sharedConfig object.
Swift:
Meridian.sharedConfig()?.applicationToken = "EDITOR_TOKEN_FOR_SECOND_LOCATION"
ObjC:
[Meridian sharedConfig].applicationToken = "EDITOR_TOKEN_FOR_SECOND_LOCATION"
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.
Swift:
// Creating a key representing your app in the Meridian Editor:
let appKey = MREditorKey(identifier: "EDITOR_LOCATION_ID")
// Creating a key representing a map in the MeridianEditor:
let mapKey = MREditorKey(forMap: "EDITOR_MAP_ID", app: "EDITOR_LOCATION_ID")
// An alternate way to create a map key:
let mapKey2 = MREditorKey(forMap: "EDITOR_MAP_ID", app: appKey.identifier)
// Creating a key representing a placemark:
let placemarkKey = MREditorKey(forPlacemark: "EDITOR_PLACEMARK_ID", map: mapKey)
ObjC:
// Creating a key representing your app in the Meridian Editor: MREditorKey *appKey = [MREditorKey keyWithIdentifier:@"EDITOR_LOCATION_ID"];
// Creating a key representing a map in the MeridianEditor: MREditorKey *mapKey = [MREditorKey keyForMap:@"EDITOR_MAP_ID" app:@"EDITOR_LOCATION_ID"]; // An alternate way to create a map key: MREditorKey *mapKey = [MREditorKey keyForMap:@"EDITOR_MAP_ID" app:appKey.identifier]; // Creating a key representing a placemark: MREditorKey *placemarkKey = [MREditorKey keyForPlacemark:@"EDITOR_PLACEMARK_ID" 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
.
Swift:
// Creating a key representing a placemark:
let placemarkKey = MREditorKey(forPlacemark: "EDITOR_PLACEMARK_ID", map: someMapKey)
let mapKey = placemarkKey.parent
print("My map ID: \(String(describing: mapKey?.identifier))")
let appKey = mapKey?.parent
print("My app ID: \(String(describing: appKey?.identifier))")
ObjC:
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.
Swift:
let mapKey = MREditorKey(forMap: "5638404075159552", app: "5468665088573440") // Meridian Portland Office
let mapController = MRMapViewController(editorKey: mapKey)
ObjC:
MREditorKey *mapKey = [MREditorKey keyForMap:@"5638404075159552" app:@"5468665088573440"]; // Meridian Portland Office MRMapViewController *mapController = [[MRMapViewController alloc] initWithEditorKey: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.
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.
Swift:
// Somewhere in your custom view controller
// Assume myPlacemark is a valid MRPlacemark instance
presentDirections(to: myPlacemark)
ObjC:
[self presentDirectionsToPlacemark:myPlacemark];
You can also 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.
Swift:
// Assume myPlacemark is a valid MRPlacemark instance
let request = MRDirectionsRequest()
request.source = MRDirectionsSource.withCurrentLocation()
request.destination = MRDirectionsDestination(placemarkKey: myPlacemark.key)
directions = MRDirections(request: request, presenting: self)
directions?.calculate(completionHandler: {[weak self] (response, error) in
if let error = error {
print("Error loading directions: \(error.localizedDescription)")
} else {
if let routes = response?.routes, routeResponse.count > 0 {
// Normally only one route will be returned
mapView.setRoute(routeResponse[0], animated: true)
}
}
})
ObjC:
MRDirectionsRequest *request = [MRDirectionsRequest new]; request.source = [MRDirectionsSource sourceWithCurrentLocation]; request.destination = [MRDirectionsDestination destinationWithMapKey:myPlacemark.key.parent withPoint:myPlacemark.point]; MRDirections *directions = [[MRDirections alloc] initWithRequest:request presentingView:self.view]; [directions calculateDirectionsWithCompletionHandler:^(MRDirectionsResponse *response, NSError *error) { if (!error) { if (response.routes.count 0) { MRRoute *route = response.routes.firstObject; [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.
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.
Swift:
// In a view controller that adopts the MRLocationManagerDelegate protocol
// With appKey and locationManager properties
override func viewDidLoad() {
super.viewDidLoad()
appKey = MREditorKey(identifier: "YOUR_APP_ID")
locationManager = MRLocationManager(app: appKey)
locationManager.delegate = self
}
// You may want to begin location updates here
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
locationManager.startUpdatingLocation()
}
// You may want to end location updates here
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
locationManager.stopUpdatingLocation()
}
// MARK: MRLocationManagerDelegate
func locationManager(_ manager: MRLocationManager, didUpdateTo location: MRLocation) {
print("New Location: \(location)")
}
func locationManager(_ manager: MRLocationManager, didFailWithError error: Error) {
print("Location Error: \(error.localizedDescription)")
}
ObjC:
- (void)viewDidLoad { [super viewDidLoad]; self.appKey = [MREditorKey keyWithIdentifier:@"YOUR_APP_ID"]; self.locationManager = [[MRLocationManager alloc] initWithApp:self.appKey]; self.locationManager.delegate = self; }
- (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated];
[self.locationManager startUpdatingLocation];
} - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [self.locationManager stopUpdatingLocation]; } - (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 Meridian feature that lets users share their location with each other.
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.
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
Swift:
// Password can be any string
let password = UUID().uuidString
MRSharingSession.createAccount(withPassword: password, name: "Morgan", appKey: myAppKey) { [weak self] session, error in
if let error = error {
print("Something went wrong: \(error.localizedDescription)")
} else if session = session {
print("Got session: \(session)")
// Keeps a reference to the session and set the delegate. Needed for most subsequent requests.
self?.mySession = session
self?.mySession.delegate = self
}
}
ObjC:
NSString *password = [NSUUID UUID].UUIDString; [MRSharingSession createAccountWithPassword:password name:@"Morgan" appKey:myAppKey completion:^(MRSharingSession *session, NSError *error) { if (error) { NSLog(@"Something went wrong: %@", error.localizedDescription); } else { NSLog(@"Got session: %@", session.key.identifier); self.mySession = session; self.mySession.delegate = self;
}
}];
Logging In
Swift:
// Logging back in later
let password = "PREVIOUSLY_SAVED_PASSWORD"
MRSharingSession.login(with: mySession.key, password: password, appKey: myAppKey) { [weak self] session, error in
if let error = error {
print("Something went wrong: \(error.localizedDescription)")
} else if let session = session {
print("Got session: \(session.key.identifier)")
}
}
ObjC:
NSString *password = @"PREVIOUSLY_SAVED_PASSWORD";
[MRSharingSession loginWithKey:self.mySession.key password:password appKey:myAppKey completion:^(MRSharingSession *session, NSError *error) { if (error) { NSLog(@"Something went wrong: %@", error.localizedDescription); } else { NSLog(@"Got session: %@", session.key.identifier); } }];
Start and Stop Location Sharing
Swift:
func startSharing() {
mySession.startListeningForUpdates()
mySession.startPostingLocationUpdates()
}
func stopSharing() {
mySession.stopListeningForUpdates()
mySession.stopPostingLocationUpdates()
}
ObjC:
- (void)startSharing { [self.mySession startListeningForUpdates];
[self.mySession startPostingLocationUpdates]; }
- (void)stopSharing { [self.mySession stopListeningForUpdates]; [self.mySession stopPostingLocationUpdates]; }
MRInvite
The MRInvite
class is used to create, retrieve, and accept invitations.
Invitations expire after 7 days.
Create and Retrieve Invitations
Swift:
MRInvite.createInvite(with: mySession) { invite, error in
if let error = error {
print("Something went wrong: \(error.localizedDescription)")
} else if let invite = invite {
print("Invitation URL: \(invite.shareURL)")
}
}
// Retrieve invitations
MRInvite.getAllInvites(with: mySession) { invites, error in
if let error = error {
print("Something went wrong: \(error.localizedDescription)")
} else if let invites = invites {
print("Got \(invites.count) invitations")
}
}
// Accept another user's invitations
let inviteURL = someValidShareURL
MRInvite.accept(with: inviteURL, session: mySession) { friend, error in
if let error = error {
print("Something went wrong: \(error.localizedDescription)")
} else if let friend = friend {
print("Now sharing locations with \(friend.name ?? "<unnamed>")")
}
}
ObjC:
[MRInvite createInviteWithSession:self.mySession completion:^(MRInvite *invite, NSError *error) { if (error) { NSLog(@"Something went wrong: %@", error.localizedDescription); } else { NSLog(@"Invitation URL: %@", invite.shareURL); } }]; [MRInvite getAllInvitesWithSession:self.mySession completion:^(NSArray *invites, NSError *error){ if (error) { NSLog(@"Something went wrong: %@", error.localizedDescription); } else { NSLog(@"Got %lu invitations", (unsigned long)invites.count); } }]; NSURL *inviteURL = someValidShareURL; [MRInvite acceptInviteWithURL:someValidShareURL session:self.mySession completion:^(MRFriend *friend, NSError *error) { if (error) { NSLog(@"Something went wrong: %@", error.localizedDescription); } else { NSLog(@"Now sharing locations with %@", friend.name); } }];
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 and Modify a User’s Profile
Swift:
// Get a user's profile
MRFriend.getWith(savedFriendKey, session: mySession) { [weak self] friend, error in
guard let strongSelf = self else { return }
if let error = error {
print("Something went wrong: \(error.localizedDescription)")
} else if let friend = friend {
print("got data for friend: \(friend.name ?? "<unnamed>")")
// Update user profiles
friend.updateName("New Name", with: strongSelf.mySession) { _, error in
if let error = error {
print("Something went wrong: \(error.localizedDescription)")
} else {
print("Profile updated successfully")
}
}
}
}
ObjC:
[MRFriend getFriendWithKey:self.session.key session:self.session completion:^(MRFriend *friend, NSError *error) { if (error) { NSLog(@"Something went wrong: %@", error.localizedDescription); } else { NSLog(@"Got data for %@", friend.name); [friend updateName:@"New Name" withSession:self.session completion:^(MRFriend *friend, NSError *error) { if (error) { NSLog(@"failed to update profile: %@", error); } else { NSLog(@"profile updated successfully"); } }]; } }];
Get a List of Friends
Swift:
// Get all friends associated with the session
MRFriend.getAllFriends(with: mySession) { friends, error in
if let error = error {
print("Something went wrong: \(error.localizedDescription)")
} else if let friends = friends {
print("You have \(friends.count) friends")
}
}
ObjC:
[MRFriend getAllFriendsWithSession:self.session completion:^(NSArray *friends, NSError *error) {
if (error) {
NSLog(@"Something went wrong: %@", error.localizedDescription);
} else {
NSLog(@"You have %lu friends", (unsigned long)friends.count);
}
}];
Use Local Search
You can use the MRLocalSearch, MRLocalSearchRequest, MRPlacemarkResponse, MRPlacemarkResult 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.
Swift:
func findNearbyCafes() {
let request = MRLocalSearchRequest()
request.app = myAppKey
request.location = myLocationManager.location!
request.transportType = .walking
request.naturalLanguageQuery = "Cafe"
request.limit = 10
let search = MRLocalSearch(request: request)
search?.start(completionHandler: { response, error in
if let searchResults = response?.results {
for result in searchResults {
print("\(result.placemark.name ?? "<unnamed>") is \(result.expectedTravelTime) seconds away")
}
} else if let error = error {
print("Error loading search results: \(error.localizedDescription)")
}
})
}
Objc:
- (void)findNearbyCafes { MRLocalSearchRequest *request = [[MRLocalSearchRequest alloc] init]; request.app = self.myAppKey; request.location = self.myLocationManager.location; request.transportType = MRDirectionsTransportTypeWalking; request.naturalLanguageQuery = @"Cafe"; request.limit = 10; MRLocalSearch *search = [[MRLocalSearch alloc] initWithRequest:request]; [search startWithCompletionHandler:^(MRPlacemarkResponse *response, NSError *error) { if (response) { for (MRPlacemarkResult *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 BLE 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 regions. Once an iOS device detected 20 regions, it was unable to receive a push notification from any additional regions.
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.
Comments
0 comments
Please sign in to leave a comment.