IOS 8 Swift Detecting Lost Bluetooth Connection
Once your IOS app has paired to a Bluetooth device, there is no delegate call to let you know the device is no longer connected.
You have a few different approaches for polling if the device is still around.
- Check RSSI signal strength
- Query the Bluetooth device and wait for a reply
- Check the peripheral.state (recommended)
OPTION 1 – RSSI
Receive Signal Strength Indication (RSSI) is a dB measurement of the wireless signal power which sounds like the perfect way to detect if the device is still connected and also if the device is getting too far away. Unfortunately with IOS 8, the readrssi() command is semi-broken. Its been a reported issue from about September 2014 and it still hasn’t been fixed around February 2015 at IOS 8.1.1. Once the device connects the first time, the readrssi() command works as expected. But once the device goes to sleep or disconnects, and then reconnects, the readrssi() will fail until the ipad/iphone gets restarted. I wouldn’t hold your breath for this to be fixed anytime soon.
OPTION 2 – QUERY
Another route is to query a characteristic from the Bluetooth device and see if you get a reply. The problem with this is you will need to wait an unknown amount of time for a reply and the reply will go into the peripheral protocol didUpdateValueForCharacteristic which might also trigger updating other parts of your application.
OPTION 3 – PERIPHERAL.STATE (RECOMMENDED)
So Apple has an additional way to check for connection status that was added in October 2013 but they don’t really point to it (like in the readrssi documentation). The best part is that it updates the Bluetooth state on its own and you can just check the variable state to see if it is still connected without having to wait for a reply or callback.
Simple Example
// peripheral variable is your CBPeripheral device // peripheral = CBPeripheral() if self.peripheral?.state != CBPeripheralState.Connected { NSLog("StillAlive :: Failed") }else{ NSLog("StillAlive :: Passed") }
Advanced Listener and Timer Example
this example has a central swift file/class that handles everything with the bluetooth device and then uses a listener protocol to allow any View Controller to check if the device is still online. The timer is used to check that the bluetooth is still there every second.
In the file that you handle the CBPeripheral variable:
// peripheral variable is your CBPeripheral device // var peripheral: CBPeripheral? var isAliveListener: IsAliveListener? func isStillAlive(){ if self.peripheral?.state != CBPeripheralState.Connected { self.isAliveListener?.controlBoardLost(self) } } protocol IsAliveListener { func bluetoothLost(deviceListener: ClassName) }
Then you can use this in any file in your project
(red code shows which lines added to your existing classes)
class OneOfYourViewController: IsAliveListener { var isStillAliveTimer = NSTimer() override func viewWillAppear(){ super.viewWillAppear(animated) self.yourApp.yourBluetoothClass.isAliveListener = self } override func viewWillDisappear(animated:bool){ super.viewWillDisappear(animated) self.yourApp.yourBluetoothClass.isAliveListener = nil } func oneOfYourFunctions{ isStillAliveTimer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "isStillThere", userInfo: nil, repeats: true) } func isStillThere() { self.yourApp.yourBluetoothClass?.isStillAlive() } func bluetoothLost(deviceListener: ClassName){ isStillAliveTimer.invalidate() // stops the timer every 1s NSLog("Connection lost") }
Let me know if there are errors or questions about the implementation.
In the end, you should have an easy time adding this code to detect if you lost your bluetooth connection so your app can take the user back to a pairing screen instead of pretending that the device is still receiving commands.
Marius ( )
I have only one viewcontroller, I have no idea how to implement this, I get errors everywhere
Agustin ( )
I love the way you approached it with protocols, and how you took into checking the state of the peripheral… Just wondering: wouldn’t it be easier and a bit faster to respond if you were to use NSNotifications? I personally like that way a bit better.. I’m not a fan of protocols tbh
Chuck Reina ( )
Instead of using a timer, you can KVO observe the peripheral:
peripheral?.addObserver(self, forKeyPath: “state”, options: [.New, .Initial], context: &kvoContext)
That will give you the initial value in case you are not connected at the onset. I find this a better approach to monitoring as it doesn’t use timers which are a bit ugly and a waste of cycles.