iBeacon: Reconnecting an iOS App to a Bluetooth Device if the App is Terminated
While working on a recent iOS project in which an iOS app connects to a Bluetooth device, we discovered an issue: the connection between the app and the Bluetooth device could not be re-established if the app was terminated unless the user manually relaunched the app. Our Bluetooth device was turned on and off periodically throughout the day, so it was essential that it was able to reconnect automatically, even if the app was in the background or had been terminated.
An app can be terminated by the user (by swiping up) or by the system (e.g. the app crashes or the system needs more memory). Once an app is terminated, Apple prevents the app from relaunching itself. However, there are several exceptions. One such exception is if the app detects a beacon within range.
Our solution was simple: we would have the Bluetooth device act as an iBeacon. An iBeacon is a Bluetooth Low Energy (BLE) device that prompts nearby smart devices to perform a specified action. In this case, our iBeacon would prompt the smart device to relaunch the terminated app.
When a registered smart device with the iOS app was within range of our Bluetooth device, the iBeacon’s broadcasted identifier would prompt the system to automatically relaunch the app in the background. Once the app was relaunched, the system could then re-establish the Bluetooth connection.
There are two pieces to this approach:
- Hardware: An iBeacon needed to be added to the Bluetooth device.
- Software: iBeacon support needed to be added to the iOS app.
In this post, we will discuss the software solution. The iBeacon support was added to our iOS app using the following steps:
Step 1: Add Privacy Terms
Because monitoring for an iBeacon requires the user’s location to work, the first thing we did was add a couple of privacy items to the Info.p list, including:
- “Privacy – Location Always and When In Use Usage Description”
- “Privacy – Location When In Use Usage Description”
We also had to add “location” background mode to our application.
Step 2: Create a Location Manager
In order for the app to launch when in proximity to the Bluetooth device, the app needed to track the location of the smart device at all times. Any time an app tracks a user’s location, authorization is required.
We requested user authorization by creating a location manager:
let locationManager = CLLocationManager()
// set the delegate in the init or somewhere that gets called when your app starts the location manager
locationManager.delegate = self
// request location permission
locationManager.requestAlwaysAuthorization()
Step 3: Set the Delegate
Next, we added the delegate methods so that the app could respond to the iBeacon when it was detected:
//
func locationManager(_ manager: CLLocationManager, didRange beacons: [CLBeacon], satisfying beaconConstraint: CLBeaconIdentityConstraint) {
// respond to the beacon being detected in range. We call our bluetooth connect method here
}
//r
func locationManager(_ manager: CLLocationManager, didDetermineState state: CLRegionState, for region: CLRegion) {
// handle beacon boundary transitions (inside/outside)
}
func locationManager(_ manager: CLLocationManager, monitoringDidFailFor region: CLRegion?, withError error: Error) {
// handle errors
}
Step 4: Request Location Monitoring Permission
Finally, we needed to start monitoring the beacon. An iBeacon can broadcast a universally unique identifier (UUID), major value, minor value, and identifier. The major and minor values can be used to distinguish between multiple iBeacons with the same UUID, if necessary. However, for the purposes of our project, we only had one iBeacon, so we only needed to use the UUID and identifier.
We created a UUID and identifier in the app using the iBeacon’s UUID and identifier. We then used those to create a CLBeaconRegion that would identify the iBeacon of the Bluetooth device.
We checked that monitoring was available, then started to monitor for the region and ranged for beacons:
guard CLLocationManager.isMonitoringAvailable(for: CLBeaconRegion.self),
let beaconUUID = UUID(uuidString: "0000aa11-1234-5678-ab912-12345a67bc8d") else { return }
let beaconID = "id_of_your_beacon"
let region = CLBeaconRegion(uuid: beaconUUID, identifier: beaconID)
locationManager.startMonitoring(for: region)
locationManager.startRangingBeacons(satisfying: CLBeaconIdentityConstraint(uuid: beaconUUID))
}
That’s it! Once the app registered the beacon’s UUID once, it reacted to the beacon whenever it was in range, regardless of whether the app was open, in the background, or terminated. This allowed the app to relaunch in the background and reconnect to the wearable Bluetooth device even if it had been terminated.
At Grio, we pride ourselves on finding creative solutions to any problems our clients encounter. To find out what we can do for you, reach out today.