iosswiftswiftuicore-bluetoothios-bluetooth

How to do Bluetooth in background in SwiftUI


I'm trying to listen for my Bluetooth device in the background, but nothing seems to be happening. I tried following this guide here and modifying it for SwiftUI.

https://developer.apple.com/library/archive/documentation/NetworkingInternetWeb/Conceptual/CoreBluetooth_concepts/CoreBluetoothBackgroundProcessingForIOSApps/PerformingTasksWhileYourAppIsInTheBackground.html

I enabled the bluetooth-central background mode.

I opt In to State Preservation and Restoration.

let options = [CBCentralManagerOptionRestoreIdentifierKey : "myCentralManagerIdentifier"]
centralManager = CBCentralManager(delegate: self, queue: nil, options: options)

I added Restoration Delegate Method. This method gets called when I first run the app again.

func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) {

    guard let peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey] as? [CBPeripheral] else {
        return
    }
    
    for peripheral in peripherals {
        // ...
    }

}

The step for launch options doesn't seem to work for me. The launchOptions dictionary is always nil for me.

import SwiftUI
import UIKit

class AppDelegate: NSObject, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        guard let options = launchOptions else {
            return true
        }
        
        let centralManagerIdentifiers = options[UIApplication.LaunchOptionsKey.bluetoothCentrals]
        return true
    }
}

@main
struct MyApp: App {
    
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

Solution

  • Once you know the device's peripheralID, you can fetch a CBPeripheral for it any time using retrievePeripherals(withIdentifiers:) whether it's connected or not. If it's not connected, you can just call connect to start a connection request. There's no need to scan for it once you've seen it before (and this is discouraged, since it's slow and power intensive to scan).

    Connection requests never time out. When the app goes into the background, the request will continue. It'll even continue if the app is terminated or the device is rebooted. A connection request can run for weeks. It's totally fine, expected, and battery efficient.

    When the device is turned on and in range and connectable, then the OS will handle connecting. It will then either hand control to your app in the background, or launch your app using state restoration. At that point, you can do whatever you want with the connected device.