Simple guide to Apple Core Bluetooth iOS 12.11.2023

Core Bluetooth is an iOS Framework to build Bluetooth Low Energy (BLE) applications that communicate with hardware devices.

A Bluetooth device can be either a central or peripheral

  • Central (CBCentralManager). The object that scan, connect, disconnect from a Bluetooth device.
  • Peripheral (CBPeripheral). The Bluetooth device that publishes data to be consumed by other devices. Bluetooth peripherals broadcast some of the data they have in the form of advertising packets. The peripheral’s data is organized into services and characteristics.

Here, we presume that the iOS device will be the central, receiving some data from some peripheral.

Everything to do with Bluetooth is event based. Let's go through main classes and protocols that we'll use in this tutorial

  • CBCentralManager, CBCentralManagerDelegate. CBCentralManager is the first object you’ll need to instantiate to set up a Bluetooth connection. It handles monitoring the Bluetooth state of the device, scanning for Bluetooth peripherals and manage peripherals.
  • CBPeripheral, CBPeripheralDelegate. Represents physical BLE devices which were discovered by CBCentralManager. They are identified by UUID which contains one or more services.
  • CBService. There are list of service that BLE device has, also provide data associated behaviors and characteristics.
  • CBCharacteristics. Represent the data of the device’s service and contains a single value. Here we can read, write, and subscribe to the data from the device.

Let's start with setuping XCode. After creating a project first you’ll need to configure certain permissions to allow your app to use Bluetooth. In Info.plist file we are going to add following keys

<key>NSBluetoothAlwaysUsageDescription</key>
<string>We use bluetooth to connect to nearby bluetooth Devices</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>Bluetooth is used to connect to user devices</string>

Now we can open a ViewController and import CoreBluetooth class.

First, we have to initiate instance of CBCentralManager and implement centralManagerDidUpdateState delegates methods in order to get updates when the Bluetooth peripheral is switched on or off, and after that we can start scanning peripherals, by calling scanForPeripherals method. You may pass in an array of CBUUIDs that represent specific services you want to filter for.

import UIKit
import CoreBluetooth

class BluetoothViewController: UIViewController {
    var centralManager: CBCentralManager!
    var peripheral: CBPeripheral? = nil
    var someCharacteristic: CBCharacteristic? = nil
    var scanningTimer = Timer()

    override func viewDidLoad() {
        super.viewDidLoad()

        // Start manager
        centralManager = CBCentralManager(delegate: self, queue: nil)//, options: [CBCentralManagerOptionShowPowerAlertKey: true])
    }
}

extension BluetoothViewController: CBCentralManagerDelegate {
    // MARK: - Scanning

    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        switch central.state {
        case .unauthorized:
            print("State is unauthorized")
        case .poweredOn:
            central.scanForPeripherals(withServices: nil, options: nil)
            print("Scanning...")
            scanningTimer = Timer.scheduledTimer(timeInterval: 10.0, target: self, selector: #selector(stopBluetoothScanning), userInfo: nil, repeats: false)
        default:
            print("\(central.state)")
        }
    }

    @objc func stopBluetoothScanning() {
        centralManager.stopScan()
        print("Stopped...")
    }

    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        guard let peripheralName = peripheral.name else { return }

        print(peripheralName)

        if peripheralName == "iPhone 16 Ultra Pro" {
            print("Device found!")
            // Stop scan
            //centralManager.stopScan()

            // Connect
            //centralManager.connect(peripheral, options: nil)
            //self.peripheral = peripheral
        }
    }

    // MARK: - Connected

    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        // Discover all service

        peripheral.discoverServices(nil)
        peripheral.delegate = self
    }
}

extension BluetoothViewController : CBPeripheralDelegate {
    // MARK: - Discover services

    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        if let services = peripheral.services {
            for service in services {
                peripheral.discoverCharacteristics(nil, for: service)
            }
        }
    }

    // MARK: - Discover characteristics for the service

    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        guard let characteristics = service.characteristics else { return }

        let characteristicId = CBUUID(string: "0x1234")

        for characteristic in characteristics {
            if characteristic.uuid == characteristicId {
                self.someCharacteristic = characteristic
                print("Found characteristic - \(characteristic)")
            }
        }
    }
}

Second, we need another delegate method didDiscover to receive scan results. After it we can connect and store a copy of BLE-device. To obtain data from a peripheral you’ll need to connect to it.

Third, once you’ve obtained a reference to the desired CBPeripheral object, you may attempt to connect to it by simply calling the connect method on your central manager and passing in the peripheral. On a successful connection, you’ll receive a didConnect delegate call, or on connection failure.

Fourth, we call discoverServices and implement CBPeripheralDelegate methods to discover its services and then its characteristics. In discoverServices if you pass nil it’s going to discover all services but if you know specific service you can discover only that.

Fifth, we need didDiscoverServices delegate method to discover characteristics by calling discoverCharacteristics. If you pass nil it's going to discover all characteristics.

Sixth, we need to implement didDiscoverCharacteristicsFor method coming from the CBPeripheral delegate for getting characteristics.

That's all for beginning.

Here is some useful snippets. We can write a value to the CBPeripheral

let data = Data(bytes: [bytes])
peripheral?.writeValue(data, for: someCharacteristic, type: .withResponse)

Read a value from the CBPeripheral

peripheral?.readValue(for: someCharacteristic)

To disconnect or cancel an active local connection from a peripheral, use the cancelPeripheralConnection method.

if let peripheral {
    centralManager.cancelPeripheralConnection(blePeripheral)
}