iOS SDK
The Radar SDK abstracts away cross-platform differences between location services, allowing you to add geofencing, location tracking, trip tracking, geocoding, and search to your apps with just a few lines of code.
Learn how to integrate the iOS SDK below. You can also see the source and a detailed SDK reference on GitHub.
#
Install SDKThe best way to install the SDK is via CocoaPods or Carthage.
The SDK is small and typically adds less than 500 KB to your compiled app.
#
CocoaPodsInstall CocoaPods. If you don't have an existing Podfile
, run pod init
in your project directory. Add the following to your Podfile
:
pod 'RadarSDK', '~> 3.10.0'
Then, run pod install
. You may also need to run pod repo update
.
.xcworkspace
file to open your project in Xcode instead of the .xcproject
file.#
Swift Package ManagerIn Xcode, go to File > Swift Packages > Add Package Dependency. Enter https://github.com/radarlabs/radar-sdk-ios.git
for the Package Repository URL.
#
CarthageInstall Carthage. To include Radar as a github
origin, add the following to your Cartfile
:
github "radarlabs/radar-sdk-ios" ~> 3.10.0
To include Radar as a binary
origin, add the following to your Cartfile
:
binary "https://raw.githubusercontent.com/radarlabs/radar-sdk-ios/master/RadarSDK.json" ~> 3.10.0
Then, run carthage update
and drag Build/iOS/RadarSDK.framework
into the Linked Frameworks and Libraries section of your target. Do not add the framework as an input to your copy-frameworks
run script.
#
Add manuallyYou can also add the SDK to your project manually. Download the current release, unzip the package, and drag RadarSDK.xcframework
into your Xcode project. It will automatically appear in the Frameworks, Libraries, and Embedded Content section of your target settings. Switch Do Not Embed to Embed & Sign.
#
DependenciesThe SDK depends on Apple's CoreLocation
framework. In your target settings, go to General > Frameworks, Libraries, and Embedded Content and add CoreLocation
if you haven't already.
The SDK currently supports iOS 10 and higher.
#
Initialize SDKWhen your app starts, initialize the SDK with your publishable API key, found on the Settings page.
Use your Test Publishable
key for testing and non-production environments. Use your Live Publishable
key for production environments.
- Swift
- Objective-C
import UIKitimport RadarSDK
@UIApplicationMainclass AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { Radar.initialize(publishableKey: "prj_test_pk_...")
return true }
}
#import "AppDelegate.h"@import RadarSDK;
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [Radar initializeWithPublishableKey:@"prj_test_pk_..."];
return YES;}
@end
#
Request permissionsRadar respects standard iOS location permissions.
For foreground tracking or trip tracking with continuous mode, the app's location authorization status must be or. For background tracking or geofencing with responsive mode, the app's location authorization status must be .
Before requesting permissions, you must add location usage strings to your Info.plist
file. For foreground permissions, you must add a value for NSLocationWhenInUseUsageDescription
(Privacy - Location When In Use Usage Description). For background permissions, you must add a value for NSLocationAlwaysAndWhenInUseUsageDescription
(Privacy - Location Always and When In Use Usage Description). These strings are displayed in permissions prompts.
To request foreground permissions, call on a CLLocationManager
instance. To request background permissions in the app, make sure the user has granted foreground permissions first, then call on a CLLocationManager
instance.
To request foreground location permissions and then immediately request background location permissions:
- Swift
- Objective-C
import UIKitimport RadarSDKimport CoreLocation
@UIApplicationMainclass AppDelegate: UIResponder, UIApplicationDelegate, CLLocationManagerDelegate {
let locationManager = CLLocationManager()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { Radar.initialize(publishableKey: "prj_test_pk_...") self.locationManager.delegate = self self.requestLocationPermissions()
return true }
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) { self.requestLocationPermissions() }
func requestLocationPermissions() { let status = self.locationManager.authorizationStatus
if status == .notDetermined { self.locationManager.requestWhenInUseAuthorization() } else if status == .authorizedWhenInUse { self.locationManager.requestAlwaysAuthorization() } }
}
// AppDelegate.h
#import <UIKit/UIKit.h>#import <CoreLocation/CoreLocation.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate, CLLocationManagerDelegate>
@end
// AppDelegate.m
#import "AppDelegate.h"@import RadarSDK;
@interface AppDelegate ()
@property (strong, nonatomic) CLLocationManager *locationManager;
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [Radar initializeWithPublishableKey:@"prj_test_pk_..."]; self.locationManager = [CLLocationManager new]; self.locationManager.delegate = self; [self requestLocationPermissions];
return YES;}
- (void)locationManagerDidChangeAuthorization:(CLLocationManager *)manager { [self requestPermissions];}
- (void)requestPermissions { CLAuthorizationStatus status = self.locationManager.authorizationStatus;
if (status == kCLAuthorizationStatusNotDetermined) { [self.locationManager requestWhenInUseAuthorization]; } else if (status == kCLAuthorizationStatusAuthorizedAlways) { [self.locationManager requestAlwaysAuthorization]; }}
@end
Info.plist
file. Finally, check your device settings to make sure you haven't already granted location permissions.#
Background modesIf you are planning to use RadarTrackingOptions.presetContinuous
or RadarTrackingOptions.presetResponsive
, or if you are planning to range beacons by UUID only, you must enable the Location updates and Background fetch background modes.
In your project settings, go to Signing & Capabilities, add Background Modes, and turn on Location updates and Background fetch.
Note that this requires additional justification during App Store review. Learn more below.
#
Foreground trackingOnce the user has granted foreground permissions, you can track the user's location in the foreground.
To track the user's location once in the foreground, call:
- Swift
- Objective-C
Radar.trackOnce { (status: RadarStatus, location: CLLocation?, events: [RadarEvent]?, user: RadarUser?) in // do something with location, events, user}
[Radar trackOnceWithCompletionHandler:^(RadarStatus status, CLLocation *location, NSArray<RadarEvent *> *events, RadarUser *user) { // do something with location, events, user}];
You may provide an optional completionHandler
that receives the request status, the user's location, the events generated, if any, and the user. The request status can be:
- : success
- : SDK not initialized
- : location permissions not granted
- : location services error or timeout (10 seconds)
- : network error or timeout (10 seconds)
- : bad request (missing or invalid params)
- : unauthorized (invalid API key)
- : payment required (organization disabled or usage exceeded)
- : forbidden (insufficient permissions)
- : not found
- : too many requests (rate limit exceeded)
- : internal server error
- : unknown error
status
in the completionHandler
to see what went wrong.#
Background tracking for geofencingOnce you have initialized the SDK and the user has authorized background permissions, you can start tracking the user's location in the background.
The SDK supports custom tracking options as well as three presets.
For geofencing, we recommend using RadarTrackingOptions.presetResponsive
. This preset detects whether the device is stopped or moving. When moving, it tells the SDK to send location updates to the server every 2-3 minutes. When stopped, it tells the SDK to shut down to save battery. Once stopped, the device will need to move more than 100 meters to wake up and start moving again.
Assuming the user has authorized background permissions, background tracking will work even if the app has been backgrounded or killed, as iOS location services will wake up the app to deliver events.
To start tracking for geofencing, call:
- Swift
- Objective-C
Radar.startTracking(trackingOptions: RadarTrackingOptions.presetResponsive)
[Radar startTrackingWithOptions:RadarTrackingOptions.presetResponsive];
To determine whether tracking has been started, call:
- Swift
- Objective-C
Radar.isTracking()
[Radar isTracking];
To stop tracking (e.g., when the user logs out), call:
- Swift
- Objective-C
Radar.stopTracking()
[Radar stopTracking];
You only need to call these methods once, as these settings will be persisted across app sessions.
#
Background tracking for tripsFor trips, we recommend using RadarTrackingOptions.presetContinuous
. This preset tells the SDK to send location updates to the server every 30 seconds, regardless of whether the device is moving.
Tracking for the duration of a trip is started or updated by including tracking options in the startTrip
call:
- Swift
- Objective-C
Radar.startTrip(options: tripOptions, trackingOptions: .presetContinuous) { (status: RadarStatus, trip: RadarTrip?, events: [RadarEvent]?) in if status == .success { // do something } else { // handle error }}
[Radar startTripWithOptions:tripOptions trackingOptions:RadarTrackingOptions.presetContinuous completionHandler:^(RadarStatus status, RadarTrip * _Nullable trip, NSArray<RadarEvent *> * _Nullable events) { if (status == RadarStatusSuccess) { // do something } else { // handle error }}];
If tracking was disabled before the trip started, it will stop after the trip ends. Otherwise, it will revert to the tracking options in use before the trip started:
- Swift
- Objective-C
// complete tripRadar.completeTrip()
// cancel tripRadar.cancelTrip()
// complete trip[Radar completeTrip];
// cancel trip[Radar cancelTrip];
Learn more about starting, completing, and canceling trips in the trip tracking documentation.
#
Mock tracking for testingCan't go for a walk or a drive? You can simulate a sequence of location updates. For example, to simulate a sequence of 10 location updates every 3 seconds by car from an origin to a destination, call:
- Swift
- Objective-C
Radar.mockTracking( origin: CLLocation(latitude: 40.714708, longitude: -74.035807), destination: CLLocation(latitude: 40.717410, longitude: -74.053334), mode: .car, steps: 10, interval: 3) { (status: RadarStatus, location: CLLocation?, events: [RadarEvent]?, user: RadarUser?) in // do something with location, events, user}
[Radar mockTrackingWithOrigin:origin destination:destination mode:RadarRouteModeCar steps:steps interval:1 completionHandler:^(RadarStatus status, CLLocation *location, NSArray<RadarEvent *> *events, RadarUser *user) {
}];
#
Listening for events with a delegateTo listen for events, location updates, and errors client-side, set a RadarDelegate
. Set your RadarDelegate
in a codepath that will be initialized and executed in the background. For example, make your AppDelegate
implement RadarDelegate
, not a ViewController
. AppDelegate
will be initialized in the background, whereas a ViewController
may not be.
- Swift
- Objective-C
import UIKitimport RadarSDK
@UIApplicationMainclass AppDelegate: UIResponder, UIApplicationDelegate, RadarDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { Radar.initialize(publishableKey: "prj_test_pk...") Radar.setDelegate(self)
return true }
func didReceiveEvents(_ events: [RadarEvent], user: RadarUser?) { // do something with events, user }
func didUpdateLocation(_ location: CLLocation, user: RadarUser) { // do something with location, user }
func didUpdateClientLocation(_ location: CLLocation, stopped: Bool, source: RadarLocationSource) { // do something with location, stopped, source }
func didFail(status: RadarStatus) { // do something with status }
func didLog(message: String) { // print message for debug logs }
}
#import "AppDelegate.h"@import RadarSDK;
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [Radar initializeWithPublishableKey:@"prj_test_pk_..."]; [Radar setDelegate:self];
return YES;}
- (void)didReceiveEvents:(NSArray<RadarEvent *> *)events user:(RadarUser *)user { // do something with events, user}
- (void)didUpdateLocation:(CLLocation *)location user:(RadarUser *)user { // do something with location, user}
- (void)didUpdateClientLocation:(CLLocation *)location stopped:(BOOL)stopped source:(RadarLocationSource)source { // do something with location, stopped, source}
- (void)didFailWithStatus:(RadarStatus)status { // do something with status}
- (void)didLogMessage:(NSString *_Nonnull)message { // print message for debug logs}
@end
RadarDelegate
should be instantiated only once and set before tracking begins.#
Manual trackingIf you want to manage location services yourself, you can manually update the user's location instead by calling:
- Swift
- Objective-C
Radar.trackOnce( location: location) { (status: RadarStatus, location: CLLocation?, events: [RadarEvent]?, user: RadarUser?) in // do something with location, events, user}
[Radar trackOnceWithLocation:location completionHandler:^(RadarStatus status, CLLocation *location, NSArray<RadarEvent *> *events, RadarUser *user) { // do something with location, events, user}];
where location
is a CLLocation
instance with a valid latitude, longitude, and horizontal accuracy.
#
Identify userThe SDK automatically collects installId
(a GUID generated on fresh install) and deviceId
(IDFV).
You can also assign a custom userId
, also called External ID in the dashboard. To set a custom userId
, call:
- Swift
- Objective-C
Radar.setUserId(userId)
[Radar setUserId:userId];
where userId
is a stable unique ID for the user.
userId
. See privacy best practices for more information.To set a dictionary of custom metadata for the user, call:
- Swift
- Objective-C
Radar.setMetadata(metadata)
[Radar setMetadata:metadata];
where metadata
is a dictionary with up to 16 keys and values of type string, boolean, or number.
Finally, to set an optional description for the user, displayed in the dashboard, call:
- Swift
- Objective-C
Radar.setDescription(description)
[Radar setDescription:description];
You only need to call these methods once, as these settings will be persisted across app sessions.
Learn more about when Radar creates new user records here.
#
Debug loggingBy default, only critical errors are logged to the console. To enable debug logging, call:
- Swift
- Objective-C
Radar.setLogLevel(RadarLogLevel.debug)
[Radar setLogLevel:RadarLogLevelDebug];
To log app lifecycle events, call the respective Radar methods in the AppDelegate
lifecycle methods:
- Swift
- Objective-C
import UIKitimport RadarSDK
@UIApplicationMainclass AppDelegate: UIResponder, UIApplicationDelegate, RadarDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { Radar.initialize(publishableKey: "prj_test_pk...") Radar.setDelegate(self)
return true }
func applicationWillResignActive(_ application: UIApplication) { // existing code Radar.logResigningActive() }
func applicationDidEnterBackground(_ application: UIApplication) { // existing code Radar.logBackgrounding() }
func applicationWillTerminate(_ application: UIApplication) { // existing code Radar.logTermination() }}
#import "AppDelegate.h"@import RadarSDK;
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [Radar initializeWithPublishableKey:@"prj_test_pk_..."]; [Radar setDelegate:self];
return YES;}
- (void)applicationWillResignActive:(UIApplication *)application { // existing code [Radar logResigningActive];}
- (void)applicationDidEnterBackground:(UIApplication *)application { // existing code [Radar logBackgrounding];}
- (void)applicationWillTerminate:(UIApplication *)application { // existing code [Radar logTermination];}
@end
#
Submit to App StoreApple requires that you justify your use of background location. Add something materially similar to the following to the bottom of your App Store description: This app uses background location to (insert use case here). Continued use of background location may decrease battery life.
If you turned on the Location updates background mode, Apple requires additional justification in your App Store review notes. If using RadarTrackingOptions.presetResponsive
, add something like: This app uses the Radar SDK (https://radar.com) to (insert use case here). The Radar SDK requires the background location mode to support polygon geofences and nearby place detection, which cannot be accomplished with region monitoring or visit monitoring. Or, if using RadarTrackingOptions.presetContinuous
, add something like This app uses the Radar SDK (https://radar.com) to (insert use case here). The Radar SDK requires the location background mode for live trip tracking and live ETAs.
Learn more about this requirement in section 2.5.4 of the App Store Review Guidelines here.
#
Other APIsThe iOS SDK also exposes APIs for beacons, anonymous context, geocoding, search, and distance.
#
BeaconsIf the user has granted location permissions, you can range and monitor beacons.
To range beacons in the foreground, call:
- Swift
- Objective-C
Radar.trackOnce(desiredAccuracy: .high, beacons: true) { (status: RadarStatus, location: CLLocation?, events: [RadarEvent]?, user: RadarUser?) in // do something with user.beacons}
[Radar trackOnceWithDesiredAccuracy:RadarTrackingOptionsDesiredAccuracyHigh beacons:YES completionHandler:^(RadarStatus status, CLLocation *location, NSArray<RadarEvent *> *events, RadarUser *user) { // do something with user.beacons}];
To monitor beacons in the background, update your tracking options:
- Swift
- Objective-C
let trackingOptions = RadarTrackingOptions.presetResponsivetrackingOptions.beacons = trueRadar.startTracking(trackingOptions: trackingOptions)
RadarTrackingOptions *trackingOptions = RadarTrackingOptions.presetResponsive;trackingOptions.beacons = YES;[Radar startTrackingWithOptions:trackingOptions];
#
Get locationGet a single location update without sending it to the server:
- Swift
- Objective-C
Radar.getLocation { (status: RadarStatus, location: CLLocation?, stopped: Bool) in // do something with location}
[Radar getLocationWithCompletionHandler:^(RadarStatus status, CLLocation *location, BOOL stopped) { // do something with location}];
#
ContextWith the context API, get context for a location without sending device or user identifiers to the server:
- Swift
- Objective-C
Radar.getContext { (status: RadarStatus, location: CLLocation?, context: RadarContext?) in // do something with context}
[Radar getContextWithCompletionHandler:^(RadarStatus status, CLLocation *location, RadarContext *context) { // do something with context}];
#
GeocodingWith the forward geocoding API, geocode an address, converting address to coordinates:
- Swift
- Objective-C
Radar.geocode( address: "20 jay st brooklyn ny") { (status: RadarStatus, addresses: [RadarAddress]?) in // do something with addresses}
[Radar geocodeAddress:@"20 jay st brooklyn ny" completionHandler:^(RadarStatus status, NSArray<RadarAddress *> *addresses) { // do something with addresses}];
With the reverse geocoding API, reverse geocode a location, converting coordinates to address:
- Swift
- Objective-C
Radar.reverseGeocode( location: location) { (status: RadarStatus, addresses: [RadarAddress]?) in // do something with addresses}
[Radar reverseGeocodeLocation:location completionHandler:^(RadarStatus status, NSArray<RadarAddress *> *addresses) { // do something with addresses}];
With the IP geocoding API, geocode the device's current IP address, converting IP address to city, state, and country:
- Swift
- Objective-C
Radar.ipGeocode { (status: RadarStatus, address: RadarAddress, proxy: Bool) in // do something with address, proxy}
[Radar ipGeocodeWithCompletionHandler:^(RadarStatus status, RadarAddress *address, BOOL proxy) { // do something with address, proxy}];
#
SearchWith the autocomplete API, autocomplete partial addresses and place names, sorted by relevance:
- Swift
- Objective-C
Radar.autocomplete( query: "brooklyn roasting", near: location, limit: 10) { (status: RadarStatus, addresses: [RadarAddress]?) in // do something with addresses}
[Radar autocompleteQuery:@"brooklyn roasting" near:location limit:10 completionHandler:^(RadarStatus status, NSArray<RadarAddress *> *addresses) { // do something with addresses}];
With the geofence search API, search for geofences near a location, sorted by distance:
- Swift
- Objective-C
Radar.searchGeofences( near: location, radius: 1000, // meters tags: ["store"], metadata: nil, limit: 10) { (status: RadarStatus, location: CLLocation?, geofences: [RadarGeofence]?) in // do something with geofences}
[Radar searchGeofencesNear:location radius:1000 // meters tags:@[@"store"] metadata:nil limit:100 completionHandler:^(RadarStatus status, CLLocation *location, NSArray<RadarGeofence *> *geofences) { // do something with geofences}];
With the places search API, search for places near a location, sorted by distance:
- Swift
- Objective-C
Radar.searchPlaces( near: location, radius: 1000, // meters chains: ["starbucks"], categories: nil, groups: nil, limit: 10) { (status: RadarStatus, location: CLLocation?, places: [RadarPlace]?) in // do something with places}
[Radar searchGeofencesNear:location radius:1000 // meters chains:@[@"starbucks"] categories:nil groups:nil limit:100 completionHandler:^(RadarStatus status, CLLocation *location, NSArray<RadarPlace *> *places) { // do something with places}];
With the address validation API (currently in beta), validate a structured address in the US or Canada:
- Swift
- Objective-C
Radar.validateAddress( address: address) { (status: RadarStatus, address: RadarAddress?, result: ValidationResult) in // do something with validation result and address}
[Radar validateAddress:address completionHandler:^(RadarStatus status, RadarAddress *address, ValidationResult result) { // do something with validation result and address}];
#
DistanceWith the distance API, calculate the travel distance and duration between an origin and a destination:
- Swift
- Objective-C
Radar.getDistance( origin: origin, destination: destination, modes: [.foot, .car], units: .imperial) { (status: RadarStatus, routes: RadarRoutes?) in // do something with routes}
[Radar getDistanceFromOrigin:origin destination:destination modes:RadarRouteModeFoot | RadarRouteModeCar units:RadarRouteUnitsImperial completionHandler:^(RadarStatus status, RadarRoutes *routes) { // do something with routes}];
#
MatrixWith the matrix API, calculate the travel distance and duration between multiple origins and destinations for up to 25 routes:
- Swift
- Objective-C
Radar.getMatrix( origins: origins, destinations: destinations, mode: .car, units: .imperial) { (status: RadarStatus, matrix: RadarRouteMatrix?) in // do something with matrix.routeBetween(originIndex:destinationIndex:)}
[Radar getMatrixFromOrigins:origin destinations:destinations mode:RadarRouteModeCar units:RadarRouteUnitsImperial completionHandler:^(RadarStatus status, RadarRouteMatrix *matrix) { // do something with [matrix routeBetweenOriginIndex:destinationIndex:]}];
#
ConversionsWith the conversions API, log a conversion, such as a purchase or signup, to analyze alongside your app's location activity:
- Swift
- Objective-C
Radar.logConversion( name: name, metadata: metadata,) { (status: RadarStatus, event: RadarEvent?) in // do something with the conversion event}
// conversion with revenueRadar.logConversion( name: name, revenue: revenue, metadata: metadata,) { (status: RadarStatus, event: RadarEvent?) in // do something with the conversion event}
[Radar logConversionWithName:name metadata:metadata completionHandler:^(RadarStatus status, RadarEvent *event) { // do something with the conversion event}];
// conversion with revenue[Radar logConversionWithName:name revenue:revenue metadata:metadata completionHandler:^(RadarStatus status, RadarEvent *event) { // do something with the conversion event}];