Significant change or restart app without location UIBackgroundModes key

Situation:

We have an app that only uses location UIBackgroundModes key to restart our app on significant change events as we need it to connect with a BLE device (mounted in the car) when someone starts driving. We cannot use geofence as the car might be used by multiple people so position changes and we don't want to store locations and sent them to multiple users via our servers. So currently we use significant change and just ignore all other location data.

During app review we got the following feedback:

If the app does not require persistent real-time location updates, please remove the "location" setting from the UIBackgroundModes key. You may wish to use the significant-change location service or the region monitoring location service if persistent real-time location updates are not required for the app features.

Question: How to use the significant-change location service without the "location" setting from the UIBackgroundModes key or is there any other way to start the app / connect with the BLE device when it is fully terminated/swiped away? Because the docs state that AuthorizationStatusAuthorizedAlways is required and without the UIBackgroundModes key location that wouldn't be triggered when app is in the background/swiped away.

Reference: https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/LocationAwarenessPG/CoreLocation/CoreLocation.html#//apple_ref/doc/uid/TP40009497-CH2-SW8

The "location" UIBackgroundModes key is specific to the startupdatingLocation() API that allows the app to run uninterrupted in the background, and is not necessary for the low energy APIs like significant location and region monitoring. This is what the review feedback meant as well:

If the app does not require persistent real-time location updates, please remove the "location" setting from the UIBackgroundModes key.

Indeed, the documentation you quoted says

If your iOS app must keep monitoring location even while it’s in the background, use the standard location service and specify the location value of the UIBackgroundModes key to continue running in the background

The "standard location service" mentioned here is the API I mentioned above which uses continuous GPS locations.

I should also point out that the document you have referred to is an old archived document from 2016, and while some of the information might still be valid, there could be missing changes, and definitely the modern location APIs are not there.

In the future, you may want to consult the current documentation at Core Location Framework Documentation

That said, I would also like to comment on this solution you came up with to connect to the BLE device. It is a roundabout way which will likely have a higher rate of failure than if you simply were using CoreBluetooth to continuously scan for the peripheral, and alert your app when the BLE device is close (assuming the BLE device is a peripheral and your app is running as a central).

The potential problem with your approach is the time you would be allowed to run once a significant location change event happens. This is usually limited to about 10 seconds. If your app has been terminated and being launched from start by the location event, this will include the time for your app to launch and initialize, so you may not have enough time left to scan, discover, and connect to the BLE device.

If that becomes an issue, then your solution will be to use CoreBluetooth to launch your app instead.


Argun Tekant /  WWDR Engineering / Core Technologies

Thanks for the (very) quick reply.

Part on the location background mode is clear now, thanks!

On the Bluetooth solution; this didn't work in our tests. Sometimes the app was started but more often it didn't (and no data was sent to our servers). That is why we went back to the location approach which is used as fallback mechanism and does seem to work reliably. When the app is not fully terminated this approach does work as expected and no significant change is used. But if possible, we'd like to do without location services at all and solely depend on CoreBluetooth. What are we doing wrong?

How we implemented the Bluetooth solution is keeping a list of known peripheral IDs, and on disconnect use those to retrievePeripheralsWithIdentifiers and then do this:

for (CBPeripheral* peripheral in self.knownPeripherals)
 {
    [self.centralManager connectPeripheral:peripheral options:nil];
 }

You mentioned "scanning". Do you mean to just call scanForPeripherals withService on every disconnect instead of this connect to make it work even when terminated?

I am not sure why CoreBluetooth alone is not starting your app.

First, have you opted in to Bluetooth State Restoration? Because that is the only way your app will be restarted if it has been terminated.

If you have not, there is more information in Performing Long-Term Actions in the Background on how to implement this.

Once you have that implemented, then the system will relaunch your app after termination, when there is a need to respond to the Bluetooth device, for example, a connection is established for a pending connectPeripheral call.

One downside to this, if the user force quits the app, or turns off Bluetooth, then this won't work. The only way your app will be restored after a user force quit is if you migrate your app/device to be discovered by AccessorySetupKit first.

TN3115: Bluetooth State Restoration App Relaunch Rules explains the conditions where the app will be restored or not.

Significant Location Change API does not have this limitation and will relaunch the app despite a force quit, which is why, I presume, you opted in to using location services.

While technically this is an off-label use, if it is working for you...

And, when I said scanning, it did not mean you have to use scanForPeripherals. connectPeripheral is fine as long as you have valid peripheral IDs.

Behind the scenes, the connectPeripheral call splits into a scan and then connect request. But while the app is in the background, calling connectPeripheral is going to be faster to connect than making the scan/connect calls yourself. So, you are doing the correct thing there.


Argun Tekant /  WWDR Engineering / Core Technologies

Significant change or restart app without location UIBackgroundModes key
 
 
Q