Did anything change with the implementation of CoreBluetooth under SwiftUI?

I want to create an App with SwiftUI, which uses Bluetooth. I worked a little with CoreBluetooth and UIKit in the past, but how can I implement CoreBluetooth in a SwiftUI project?

Accepted Answer

I created a fake variant of your code to test with, adding a button that would just turn on the

connected
property via a background queue. Initially I saw the same problem, but looking at the value of
BLEControl.BLESingleton.connected
via the debugger, that one was
true
. Turns out that when I called the
.environmentObject
modifier in my scene delegate, I was passing
BLEControl()
, not
BLEControl.BLESingleton
. Thus the view was bound to a different instance. Can you check whether the same was happening in your case, i.e. that your
SceneDelegate
or suchlike is passing the singleton value itself, and not a new instance of
BLEControl
? You can put a breakpoint in your button's action to print out both the local value and the singleton to see if they have the same address (as a class type, they should).

Answers

There should be no appreciable difference in the Bluetooth APIs due to the use of SwiftUI. Your app code is free to make connections etc. exactly as before, the only difference is how to present the results onscreen. I'm not familiar with the CoreBluetooth APIs, but it should be possible to use (for example) an @ObservedObject value provided to a SwiftUI view to have it update when you change the content of a class managing the bluetooth options.


As a rough example, completely off the cuff:


class BluetoothController: ObservedObject {
    @Published var connectedDeviceName: String?
    @Published var availableDeviceNames: [String]

    // Usual bluetooth methods would now go here.
}

struct BluetoothInfoView: View {
    @ObservedObject var controller: BluetoothController

    var body: some View {
        List {
            Section(header: Text("Connected")) {
                if controller.connectedDeviceName != nil {
                    Text(verbatim: controller.connectedDeviceName!)
                        .bold()
                } else {
                    Text("No device connected")
                        .foregroundColor(.secondary)
                }
            }

            Section(header: Text("Available")) {
                ForEach(controller.availableDeviceNames, id: \.self) { name in 
                    Text(verbatim: name)
                }
            }
        }
    }
}

Even though I can connect to the Bluetooth device and I get a notification when it value changes, the value does not update in my view.

struct MyView: View {
     @EnvironmentObject var bluetooth: BLEControl
     
     var body: some View {
          VStack {     
               Button(action: {
                    print(self.bluetooth.connected)
               }) {
                    Text("check connection")
               }
           if bluetooth.conneced {
               Text(verbatim: String(decodeValue(data: bluetooth.characteristicValue).rpm))
               } else {
                    Text("--")
               }
          }
     }
)

BLEControl is a singleton and an observable object:

class BLEControl: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate, ObservableObject {
    @Published var connected = false
    @Published var characteristicValue = ""

    static let BLESingleton = BLEControl()
    override init() {}


    var manager: CBCentralManager!

    var peripheralESP: CBPeripheral!
     
     func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        self.connected = true
        print("connected")
        peripheral.discoverServices(nil)
     }

     func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        print(String(describing: characteristic.value))
        
        let value: String = String(decoding: characteristic.value!, as: UTF8.self)
        print(value)
        
        characteristicValue = value
     }
}


When I press the "check connection" button I always get false, even if "connected" in BLEControl is true. Does anyone know how to fix this?

I created a fake variant of your code to test with, adding a button that would just turn on the

connected
property via a background queue. Initially I saw the same problem, but looking at the value of
BLEControl.BLESingleton.connected
via the debugger, that one was
true
. Turns out that when I called the
.environmentObject
modifier in my scene delegate, I was passing
BLEControl()
, not
BLEControl.BLESingleton
. Thus the view was bound to a different instance. Can you check whether the same was happening in your case, i.e. that your
SceneDelegate
or suchlike is passing the singleton value itself, and not a new instance of
BLEControl
? You can put a breakpoint in your button's action to print out both the local value and the singleton to see if they have the same address (as a class type, they should).