Radius Networks Developer Blog

Starting with iOS 11, the behavior of enabling and disabling Bluetooth and Wifi via Control Center changed. This behavior is described and justified in this Apple Support note. Using the Control Center to disable Bluetooth (as opposed to turning it off completely) has some interesting (and perhaps unexpected) effects on beacon ranging, beacon region monitoring, and advertising as a beacon from an iPhone.

The Swift code that demonstrates the behavior described in this article can be found here.

Setup

To test this new behavior in iOS 11, I setup a UIViewController which is also a CLLocationManagerDelegate, a CBPeripheralManagerDelegate, and a CBCentralManagerDelegate. Under normal circumstances (with Bluetooth on and with location authorization set to .authorizedAlways), when my app starts up, the following values are reported:

centralManagerDidUpdateState(_:) –> .poweredOn

locationManager(_:didChangeAuthorization:) –> .authorizedAlways

Disabling Bluetooth in the Control Panel

With the app running, I go to the Control Panel and disable Bluetooth by tapping on the Bluetooth icon. The icon now has a white background.

Upon returning to my app, I now see that centralManagerDidUpdateState(_:) has reported .poweredOff.

Next, I try to start monitoring a CLBeaconRegion and I get the following error via locationManager(_:monitoringDidFailFor:withError:):

The operation couldn't be completed. (kCLErrorDomain error 5.)

That being said, when I now turn on a beacon inside the CLBeaconRegion I tried to monitor, I now get a call to locationManager(_:didEnterRegion:) for this region! Turning off the same beacon results in a call to locationManager(_:didExitRegion:). I am also able to start ranging beacons in the region and receive callbacks to locationManager(_:didRangeBeacons:in:) including my beacon.

Finally, let’s try advertising as a beacon from my iPhone. Upon doing so, the callback to peripheralManagerDidUpdateState(_:) indicates .poweredOff. Yet the callback to peripheralManagerDidStartAdvertising(_:error:) indicates no error, and the beacon advertisement is confirmed to be successful.

Disabling Bluetooth in Settings

Now let’s go into Settings and turn Bluetooth off.

Looking at the Control Panel we see the Bluetooth icon has a gray background and a slash through it:

Now when I start monitoring the beacon region, I get the same error I got when Bluetooth was disabled:

The operation couldn't be completed. (kCLErrorDomain error 5.)

However, after turning on my beacon, I do not see a callback to locationManager(_:didEnterRegion:).

Attempting to range beacons in the region results in a callback to locationManager(_:rangingBeaconsDidFailFor:withError:) with the error:

The operation couldn't be completed. (kCLErrorDomain error 16.)

Finally, trying to advertise as a beacon from my iPhone results in a callback to peripheralManagerDidStartAdvertising(_:error:) without an error, but also without actually advertising.

What Have We Learned?

  1. “Turning off” Bluetooth in the Control Panel causes the state of the CBCentralManager and CBPeripheralManager to return .poweredOff. However, in this state, you can still monitor for a beacon region, range beacons, and advertise as a Bluetooth peripheral.

  2. Actually turning off Bluetooth via Settings also causes the state of the CBCentralManager and CBPeripheralManager to return .poweredOff. In this state, you cannot monitor for a beacon region, range beacons, or advertise as a Bluetooth peripheral.

What is the Bottom Line?

The bottom line is that if the state of your CBCentralManager and/or CBPeripheralManager is .poweredOff, you don’t know if you truly cannot conduct Bluetooth-related operations unless you try. Apple should really give us a different state for the case where Bluetooth is “disabled” via the Control Panel but not actually “powered off.”