Skip to content
All essays
iOSMarch 27, 202512 min

Swift Core Bluetooth: LE Device Integration

Connect to Bluetooth Low Energy devices. Peripheral scanning, data transfer, and BLE communication.

Ü
Ümit Uz
Mobile & Full Stack Developer

Core Bluetooth enables your app to communicate with Bluetooth Low Energy devices. Learn to scan, connect, and exchange data.

Core Bluetooth Setup

Enable Bluetooth

  1. 1Go to Project Settings > Signing & Capabilities
  2. 2Add "Bluetooth" capability
  3. 3Add Bluetooth-Always-If-You-Want-It-Usage key to Info.plist

Initialize Central Manager

swift
import CoreBluetooth
import SwiftUI

class BluetoothManager: NSObject, ObservableObject, CBCentralManagerDelegate {
    private var centralManager: CBCentralManager!
    private var peripherals: [CBPeripheral] = []

    @Published var discoveredPeripherals: [CBPeripheral] = []
    @Published var connectedPeripheral: CBPeripheral?
    @Published var isScanning = false

    override init() {
        super.init()
        centralManager = CBCentralManager(delegate: self, queue: nil)
    }

    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        switch central.state {
        case .poweredOn:
            print("Bluetooth is powered on")
        case .poweredOff:
            print("Bluetooth is powered off")
        case .unauthorized:
            print("Bluetooth is unauthorized")
        default:
            print("Bluetooth state: \(central.state.rawValue)")
        }
    }
}

Scanning

Scan for Peripherals

swift
extension BluetoothManager {
    func startScanning() {
        guard centralManager.state == .poweredOn else {
            print("Bluetooth not powered on")
            return
        }

        isScanning = true

        centralManager.scanForPeripherals(
            withServices: nil,
            options: [CBCentralManagerScanOptionAllowDuplicatesKey: false]
        )
    }

    func stopScanning() {
        centralManager.stopScan()
        isScanning = false
    }

    func scanForSpecificServices() {
        let serviceUUIDs = [
            CBUUID(string: "180D"), // Heart Rate Service
            CBUUID(string: "180A")  // Battery Service
        ]

        centralManager.scanForPeripherals(withServices: serviceUUIDs, options: nil)
    }
}

Handle Discovery

swift
extension BluetoothManager {
    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
        if !discoveredPeripherals.contains(where: { $0.identifier == peripheral.identifier }) {
            discoveredPeripherals.append(peripheral)
            print("Discovered: \(peripheral.name ?? "Unknown")")
        }
    }
}

Connecting

Connect to Peripheral

swift
extension BluetoothManager {
    func connect(to peripheral: CBPeripheral) {
        peripheral.delegate = self
        centralManager.connect(peripheral, options: nil)
    }

    func disconnect(peripheral: CBPeripheral) {
        centralManager.cancelPeripheralConnection(peripheral)
    }

    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        connectedPeripheral = peripheral
        peripheral.discoverServices(nil)
        print("Connected to \(peripheral.name ?? "Unknown")")
    }

    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
        if let error = error {
            print("Disconnected with error: \(error)")
        } else {
            print("Disconnected")
        }

        if connectedPeripheral?.identifier == peripheral.identifier {
            connectedPeripheral = nil
        }
    }
}

Services and Characteristics

Discover Services

swift
extension BluetoothManager {
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        guard let services = peripheral.services else { return }

        for service in services {
            print("Service: \(service.uuid)")

            if let characteristics = service.characteristics {
                print("Characteristics: \(characteristics.count)")
            }

            peripheral.discoverCharacteristics(nil, for: service)
        }
    }

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

        for characteristic in characteristics {
            print("Characteristic: \(characteristic.uuid)")

            if characteristic.properties.contains(.read) {
                peripheral.readValue(for: characteristic)
            }

            if characteristic.properties.contains(.notify) {
                peripheral.setNotifyValue(true, for: characteristic)
            }
        }
    }
}

Data Transfer

Read and Write Data

swift
extension BluetoothManager {
    func readValue(from characteristic: CBCharacteristic, peripheral: CBPeripheral) {
        peripheral.readValue(for: characteristic)
    }

    func writeValue(_ data: Data, to characteristic: CBCharacteristic, peripheral: CBPeripheral) {
        peripheral.writeValue(data, for: characteristic, type: .withResponse)
    }

    func writeValueWithoutResponse(_ data: Data, to characteristic: CBCharacteristic, peripheral: CBPeripheral) {
        guard characteristic.properties.contains(.writeWithoutResponse) else {
            print("Characteristic doesn't support write without response")
            return
        }

        peripheral.writeValue(data, for: characteristic, type: .withoutResponse)
    }
}

Handle Data Updates

swift
extension BluetoothManager {
    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        guard let data = characteristic.value else { return }

        // Process data
        processIncomingData(data: data, from: characteristic)

        print("Received data: \(data as NSData)")
    }

    func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
        if let error = error {
            print("Write error: \(error)")
        } else {
            print("Write successful")
        }
    }

    private func processIncomingData(data: Data, from characteristic: CBCharacteristic) {
        // Parse data based on characteristic UUID
        if characteristic.uuid.uuidString == "2A37" {
            // Heart rate measurement
            parseHeartRate(data: data)
        } else if characteristic.uuid.uuidString == "2A19" {
            // Battery level
            parseBatteryLevel(data: data)
        }
    }

    private func parseHeartRate(data: Data) {
        let heartRate = data.withUnsafeBytes { pointer in
            pointer.load(as: UInt8.self)
        }

        DispatchQueue.main.async {
            // Update UI with heart rate
            print("Heart Rate: \(heartRate) BPM")
        }
    }

    private func parseBatteryLevel(data: Data) {
        let batteryLevel = data.withUnsafeBytes { pointer in
            pointer.load(as: UInt8.self)
        }

        DispatchQueue.main.async {
            // Update UI with battery level
            print("Battery: \(batteryLevel)%")
        }
    }
}

Peripheral Mode

Create Peripheral

swift
import CoreBluetooth

class PeripheralManager: NSObject, CBPeripheralManagerDelegate {
    private var peripheralManager: CBPeripheralManager?
    private var transferCharacteristic: CBMutableCharacteristic?

    func startAdvertising() {
        peripheralManager = CBPeripheralManager(delegate: self, queue: nil)

        let serviceUUID = CBUUID(string: "180D")
        let characteristicUUID = CBUUID(string: "2A37")

        let service = CBMutableService(type: serviceUUID)

        transferCharacteristic = CBMutableCharacteristic(
            type: characteristicUUID,
            properties: [.read, .write, .notify],
            value: nil,
            permissions: [.readable, .writeable]
        )

        service.characteristics = [transferCharacteristic!]

        let advertisementData: [String: Any] = [
            CBAdvertisementDataServiceUUIDsKey: [serviceUUID],
            CBAdvertisementDataLocalNameKey: "My Device"
        ]

        peripheralManager?.startAdvertising(advertisementData)
    }

    func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
        if peripheral.state == .poweredOn {
            let service = CBMutableService(type: CBUUID(string: "180D"))
            peripheral.add(service)
        }
    }

    func stopAdvertising() {
        peripheralManager?.stopAdvertising()
    }
}

Best Practices

  1. 1Permissions: Request Bluetooth permissions
  2. 2Scanning: Scan efficiently
  3. 3Connection: Manage connections properly
  4. 4Data: Handle data transfer carefully
  5. 5Battery: Optimize for battery life
  6. 6Error Handling: Handle all errors
  7. 7Testing: Test on real devices
  8. 8Background: Support background modes

Core Bluetooth enables powerful BLE device integration!

Related essays

Next essay
Swift ReplayKit: Screen Recording and Broadcasting