SDK

Location Services

In this page you'll dive deeper into functionality like using GPS signals to get the user's location or monitor their visits to regions and proximity to BTLE devices. These services will bring a new level of contextuality to your app, allowing you to create geo-triggers to send notifications or categorize users based on their location behaviour.

Tracking a user location in iOS will require the user's authorization, this means that your app must declare usage description texts explaining why it needs to track location. For older versions of iOS you may optionally declare the key NSLocationUsageDescription in your app's .plist file. In iOS8 and higher, it is mandatory the use of the key NSLocationAlwaysUsageDescription. In iOS 11 a new permission was introduced and you are required to include both NSLocationAlwaysAndWhenInUseUsageDescription and NSLocationWhenInUseUsageDescription in your app's .plist file. Bottom line if you want to support all versions you'll need to include the following in your app's .plist file:

ios location privacy plist entries

Make sure you provide a text that best describes why and how you are going to use the user’s location. This text will be displayed to the user the first time you request the use of location services. Users are more likely to trust your app with location data if they understand why you need it.

Location Updates

Location services can only be requested after a successful device registration. To make sure this happens correctly, the best place to request location services is in the following delegate method:

- (void)notificarePushLib:(NotificarePushLib *)library onReady:(nonnull NotificareApplication *)application {
    [[NotificarePushLib shared] startLocationUpdates];
}
func notificarePushLib(_ library: NotificarePushLib, onReady application: NotificareApplication) {
    NotificarePushLib.shared().startLocationUpdates()
}

The first time this method is invoked, it will prompt the user with a permission dialogue. In older versions of our SDK, Notificare will automatically start monitoring significant location changes (roughly every 500m/1,640ft). It will also start monitoring for the nearest regions and beacons you might have created in our dashboard or API. This is basically all it takes to use location services, at this point you should be able to manage all your regions, geo-triggers, beacons and send geo-targeted notifications through our dashboard or API.

On the other hand, if you are using SDK 2.4 and up, whenever the user agrees to share location with your app, we will only request permission for while in use location updates. Depending on their choice (once or while in use) this permission will never be requested again if the user chooses to share the location with your app when in use. But the While In Use mode will not be enough if you are planning to support geo-fencing or beacon monitoring.

For geo-fencing and beacon monitoring, you will need to request permission to retrieve the user location when the app is not being used. Upgrading from While In Use to Always can be done by invoking the following method:

[[NotificarePushLib shared] requestAlwaysAuthorizationForLocationUpdates];
NotificarePushLib.shared().requestAlwaysAuthorizationForLocationUpdates()

Additionally, if you are using SDK 2.4 and up in iOS 14, Apple introduced a new option for users that allow them to provide your app with a reduced precision for locations updates. This mode will not allow your app to obtain the user's precise location and therefore making it impossible for some feature to work. You can however request the user's location with full accuracy on a temporary basis. This might be extremely useful for those apps that only require precise location for a certain amount of time (like a navigation app to show turn by turn indications). For this to work correctly you will need to provide one or more entries in the app's Info.plist. These descriptions will be prompted to the user whenever you invoke these methods and will help you display an explanatory text that can help users understand the purpose of such requests.

For each type of temporary full accuracy usage add an entry to the NSLocationTemporaryUsageDescriptionDictionary dictionary object:

<key>NSLocationTemporaryUsageDescriptionDictionary</key>
<dict>
  <key>geocheck</key>
  <string>This app needs accurate location so it can verify that you're in a supported region.</string>
  <key>navigation</key>
  <string>This app needs accurate location so it can show you turn by turn navigation.</string>
</dict>

After you've provided these entries, you can then invoke temporary full accuracy by invoking the following method (using the appropriate usage key):

[[NotificarePushLib shared] requestTemporaryFullAccuracyAuthorizationWithPurposeKey:@"geocheck"];
NotificarePushLib.shared().requestTemporaryFullAccuracyAuthorizationWithPurposeKey("geocheck")

Please note that this method can only be invoked if the user only authorized reduced accuracy and while the app is in foreground. As soon as the app transitions to the background this authorization will be reverted to the reduced state.

Location Services Delegates

In order for you to update your app's UI, react programmatically to location updates or any other custom implementation you might want to add, you should implement the following delegates. Please note that the implementation of these delegates is optional and only required according to the use cases you have for location services in your app.

This delegate will be triggered whenever your app enables location updates. When this delegate is triggered, it is possible to retrieve the authorization status for location services:

- (void)notificarePushLib:(NotificarePushLib *)library didReceiveLocationServiceAuthorizationStatus:(NotificareGeoAuthorizationStatus)status {
    if (NotificareGeoAuthorizationStatusAuthorizedAlways == status) {
        // This mode will allow all the functionality
    } else {
        // When always mode is not provided and your app uses geo-fencing or beacon monitoring, you can use this result to display UI
        // The most common use case here is to display some UI element that drives users directly to the device's settings in order to change the location permissions
    }
}
func notificarePushLib(_ library: NotificarePushLib, didReceiveLocationServiceAuthorizationStatus status: NotificareGeoAuthorizationStatus) {
    if (NotificareGeoAuthorizationStatusAuthorizedAlways == status) {
        // This mode will allow all the functionality
    } else {
        // When always mode is not provided and your app uses geo-fencing or beacon monitoring, you can use this result to display UI
        // The most common use case here is to display some UI element that drives users directly to the device's settings in order to change the location permissions
    }
}

The following delegate will be triggered if locations services could not be started. Typically you can use this delegate to retry again in case it's a network related error or find out why your app is incorrectly configured to use location services:

- (void)notificarePushLib:(NotificarePushLib *)library didFailToStartLocationServiceWithError:(NSError *)error{

    //Handle error

}
func notificarePushLib(_ library: NotificarePushLib, didFailToStartLocationServiceWithError error: Error?) {

    //Handle error

}

Like previously mentioned, in iOS 14 and up, Apple introduced a new privacy feature around location services. The ability for an user to allow one of two types of accuracy for location updates. Users are able to provide your app with full or reduced precision for their location updates. When only reduced accuracy is allowed, features like geo-fencing or beacon monitoring will not be available. When that is the case you can use the following delegate to update your UI accordingly.

- (void)notificarePushLib:(NotificarePushLib *)library didReceiveLocationServiceAccuracyAuthorization:(NotificareGeoAccuracyAuthorization)accuracy{
    if (NotificareGeoAccuracyAuthorizationFull == accuracy) {
        // This mode will allow all the functionality
    } else {
        // When reduced accuracy is provided and your app uses geo-fencing or beacon monitoring, you can use this result to display UI
        // The most common use case here is to display some UI element that drives users directly to the device's settings in order to change the location permissions
    }
}
func notificarePushLib(_ library: NotificarePushLib, didReceiveLocationServiceAccuracyAuthorization accuracy: NotificareGeoAccuracyAuthorization) {
    if (NotificareGeoAccuracyAuthorizationFull == accuracy) {
        // This mode will allow all the functionality
    } else {
        // When reduced accuracy is provided and your app uses geo-fencing or beacon monitoring, you can use this result to display UI
        // The most common use case here is to display some UI element that drives users directly to the device's settings in order to change the location permissions
    }
}

This following delegate will be called when location updates are received. It will contain an array of one or more CLLocation objects. This array always contains at least one object representing the current location. If updates were deferred or if multiple locations arrived before they could be delivered, the array may contain additional entries. The objects in the array are organized in the order in which they occurred. Therefore, the most recent location position is at the end of the array.

- (void)notificarePushLib:(NotificarePushLib *)library didUpdateLocations:(NSArray<NotificareLocation*> *)locations {
    //Handle Locations
}
func notificarePushLib(_ library: NotificarePushLib, didUpdateLocations locations: [NotificareLocation]) {
    //Handle Locations
}

If you've created Geo-Zones in Notificare, you may want get information of the regions being monitored or any events and errors that might occur. This will display all the regions that started being monitored:

- (void)notificarePushLib:(NotificarePushLib *)library didStartMonitoringForRegion:(id)region {
    //Region can be a NotificareRegion or NotificareBeacon
}
func notificarePushLib(_ library: NotificarePushLib, didStartMonitoringForRegion region: Any) {
    //Region will can be a NotificareRegion or NotificareBeacon
}

You can also get the information if Geo-Zones failed to be monitored. Notificare will automatically retry it for you and no action is required. This could happen because there is a poor cell reception or no reliable GPS signal. The following delegate method will be triggered:

- (void)notificarePushLib:(NotificarePushLib *)library monitoringDidFailForRegion:(id)region withError:(NSError *)error {
    //Handle error
}
func notificarePushLib(_ library: NotificarePushLib, monitoringDidFailForRegion region: Any) throws {
    //Handle error
}

You can also track Geo-Zones states by implementing the following delegate:

- (void)notificarePushLib:(NotificarePushLib *)library didDetermineState:(NotificareRegionState)state forRegion:(id)region {
    switch (state) {
        case NotificareRegionStateInside:
        break;
            case NotificareRegionStateOutside:
        break;
            case NotificareRegionStateUnknown:
        break;
            default:
        break;
    }
}
func notificarePushLib(_ library: NotificarePushLib, didDetermineState state: NotificareRegionState, forRegion region: Any) {
    switch state {
        case NotificareRegionStateInside:
            break
        case NotificareRegionStateOutside:
            break
        case NotificareRegionStateUnknown:
            break
        default:
            break
    }
}

Finally, you can also be informed whenever the user enters or leaves a certain region:

- (void)notificarePushLib:(NotificarePushLib *)library didEnterRegion:(id)region {
    //User enter a region or beacon
}
- (void)notificarePushLib:(NotificarePushLib *)library didExitRegion:(id)region {
    //User left a region or beacon
}
func notificarePushLib(_ library: NotificarePushLib, didEnterRegion region: Any) {
    //User enter a region or beacon
}

func notificarePushLib(_ library: NotificarePushLib, didExitRegion region: Any) {
    //User left a region or beacon
}

Using iBeacons

This technology will require the device to have Bluetooth Service ON. If enabled your app will monitor any BTLE device in the vicinity. By default this is automatically done by our library so there is not really any additionally step to take.

However you might want to retrieve the proximity level of any beacons around you. This is only possible while the app is in foreground. To get this information you will need to implement the following delegate:

- (void)notificarePushLib:(NotificarePushLib *)library didRangeBeacons:(NSArray<NotificareBeacon *> *)beacons inRegion:(NotificareBeacon *)region {
    //Handle list of beacons in the proximity
    //Do not call any UI thread from here
}
func notificarePushLib(_ library: NotificarePushLib, didRangeBeacons beacons: [NotificareBeacon], inRegion region: NotificareBeacon) {
    //Handle list of beacons in the proximity
    //Do not call any UI thread from here
}

If by any reason your app fails to start ranging beacons, it will trigger the following delegate:

- (void)notificarePushLib:(NotificarePushLib *)library rangingBeaconsDidFailForRegion:(NotificareBeacon *)region withError:(NSError *)error {
    //Handle error
}
func notificarePushLib(_ library: NotificarePushLib, rangingBeaconsDidFailForRegion region: NotificareBeacon) throws {
    //Handle error
}

Using Visits API

As part of our location services feature, you will be able to use Apple's Visits API. This API will allow you to get events whenever users visit a location. This might be a great way to discover where areas a user visits. To use this feature you will need to turn the USE_VISITS_API property to YES, under OPTIONS in your app's Notificare.plist:

notificare use visits api

When enabled, as soon as you start location updates you will be receiving visits events too. To handle those visits make sure you implement the following delegate method:

- (void)notificarePushLib:(NotificarePushLib *)library didVisit:(nonnull NotificareVisit *)visit {
    //Handle event
}
func notificarePushLib(_ library: NotificarePushLib?, didVisit visit: NotificareVisit) {
    //Handle event
}

Using Heading API

Also part of our location services feature, Apple's Heading API will allow you to obtain the user's current heading. To use this feature you will need to turn the USE_HEADING_API property to YES, under OPTIONS in your app's Notificare.plist:

notificare use heading api

When enabled, as soon as you start location updates you will be receiving heading events too. To handle those visits make sure you implement the following delegate method:

- (void)notificarePushLib:(NotificarePushLib *)library didUpdateHeading:(nonnull NotificareHeading *)heading {
    //Handle event
}
func notificarePushLib(_ library: NotificarePushLib?, didUpdate heading: NotificareHeading) {
    //Handle event
}

Opt-out from Location Services

In our 2.2.0 release, we've added the possibility to opt-out from Notificare's Location Services. Since CLLocationManager is a singleton class shared by any implementation of location services in your app, Notificare will automatically receive location updates even when you implement this class yourself without invoking any of the methods above. To allow you to disable this behaviour we've added a new configuration property under OPTIONS in the Notificare.plist. It's called DISABLE_LOCATION_SERVICES and when present we will ignore any attempt to invoke the methods above as well as storing any location data.

Additionally, when using this new behaviour, you can also clear any location data previously stored for a device by using the following method:

[[NotificarePushLib shared] clearDeviceLocation:^(id  _Nullable response, NSError * _Nullable error) {

}];
NotificarePushLib.shared().clearDeviceLocation({(_ response: Any?, _ error: Error?) -> Void in

})