Integrating Bluetooth capabilities into your iOS application can open up a world of possibilities, particularly for IoT (Internet of Things) applications. Core Bluetooth is Apple’s framework for interacting with Bluetooth Low Energy (BLE) devices. This guide will walk you through creating an app that uses Core Bluetooth to scan for, connect to, and communicate with BLE devices.

Setting Up Your Project

  1. Open Xcode and Create a New Project:
    • Select the “App” template.
    • Name your project (e.g., “BluetoothIoT”).
    • Choose Swift as the language and SwiftUI as the user interface.
  2. Configure Privacy Settings:
    • Open Info.plist.
    • Add the key NSBluetoothAlwaysUsageDescription with a description like “We need access to Bluetooth to connect to IoT devices”.

Designing the User Interface

  1. Open ContentView.swift and set up a basic UI to display discovered devices and connect to them.

import SwiftUI

struct ContentView: View {
    @ObservedObject var bluetoothManager = BluetoothManager()

    var body: some View {
        NavigationView {
            List(bluetoothManager.devices, id: \.self) { device in
                Button(action: {
                    bluetoothManager.connect(to: device)
                }) {
                    Text(device.name ?? "Unknown Device")
                }
            }
            .navigationTitle("Bluetooth Devices")
            .onAppear {
                bluetoothManager.startScanning()
            }
        }
    }
}

Implementing the Bluetooth Manager

  1. Create a new Swift file named BluetoothManager.swift.
  2. Import Core Bluetooth and implement the necessary functionality.

import Foundation
import CoreBluetooth

class BluetoothManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeripheralDelegate {
    @Published var devices: [CBPeripheral] = []
    private var centralManager: CBCentralManager!
    private var connectedPeripheral: CBPeripheral?

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

    func startScanning() {
        centralManager.scanForPeripherals(withServices: nil, options: nil)
    }

    func connect(to peripheral: CBPeripheral) {
        centralManager.stopScan()
        connectedPeripheral = peripheral
        peripheral.delegate = self
        centralManager.connect(peripheral, options: nil)
    }

    // CBCentralManagerDelegate methods
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        if central.state == .poweredOn {
            startScanning()
        }
    }

    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        if !devices.contains(peripheral) {
            devices.append(peripheral)
        }
    }

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

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

    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        if let characteristics = service.characteristics {
            for characteristic in characteristics {
                print("Discovered characteristic: \(characteristic)")
                if characteristic.properties.contains(.read) {
                    peripheral.readValue(for: characteristic)
                }
                if characteristic.properties.contains(.write) {
                    // Example: Write a value to the characteristic
                    let value = "Hello IoT".data(using: .utf8)!
                    peripheral.writeValue(value, for: characteristic, type: .withResponse)
                }
            }
        }
    }

    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        if let value = characteristic.value {
            print("Received value: \(value)")
        }
    }
}

Explanation

  1. ContentView: This SwiftUI view displays a list of discovered Bluetooth devices. When a device is tapped, the app attempts to connect to it.
  2. BluetoothManager: This class manages Bluetooth interactions. It:
    • Initializes a CBCentralManager.
    • Starts scanning for peripherals.
    • Connects to a selected peripheral.
    • Discovers services and characteristics.
    • Reads and writes characteristic values.

Running the App

  1. Build and run the app on a physical device (the simulator does not support Bluetooth).
  2. The app will start scanning for nearby Bluetooth devices. Discovered devices will be listed.
  3. Tapping on a device will connect to it and discover its services and characteristics.

Enhancing the App

To make this app more robust and feature-rich, consider adding:

  • Error handling for Bluetooth operations.
  • UI updates to indicate connection status.
  • Detailed views for displaying services and characteristics.
  • Write operations based on user input.

In this guide, we’ve built a basic Bluetooth scanner and connector using Core Bluetooth in Swift. This app can serve as a foundation for more complex IoT applications, allowing you to interact with a wide range of Bluetooth-enabled devices. By leveraging Core Bluetooth, you can create powerful and versatile applications that bring the physical and digital worlds closer together.