Riding the Bluetooth Lightning: Connecting SwiftUI Apps to External Devices
Ever been in a technical interview when the interviewer casually asks, “So how does Bluetooth pairing and data transfer actually work?” and…
Riding the Bluetooth Lightning: Connecting SwiftUI Apps to External Devices
Ever been in a technical interview when the interviewer casually asks, “So how does Bluetooth pairing and data transfer actually work?” and you suddenly feel like James Hetfield staring blankly at an unplugged amp? Yeah, we’ve all been there. Bluetooth seems simple until you dive under the hood, then things get gnarly pretty quick.
No worries though — today we’re cranking up the amps and diving headfirst into Bluetooth connectivity with CoreBluetooth in SwiftUI. We’ll break down exactly how Bluetooth communication happens, from discovering peripherals to pairing and sending data packets back and forth. And yes, we’ll do it while building a slick, fully-functional Bluetooth scanning app so you can connect to devices faster than Kirk rips through the “Master of Puppets” solo.
We’ll also sprinkle in solid unit tests — because let’s face it, an app crashing mid-connection is as tragic as Metallica losing the Best Metal Performance Grammy to Jethro Tull. By the end, you’ll not only know your CBPeripheral from your CBCentralManager but you’ll also be able to confidently answer those tricky Bluetooth questions in your next interview.
Ready to crank it up? Let’s get connected!
Setting Up Bluetooth Scanning in SwiftUI
Before we start blasting through code, let’s break down how Bluetooth Low Energy (BLE) devices communicate:
- Central & Peripheral Roles: Your iPhone acts as a central device that scans for peripheral devices (e.g., Bluetooth headphones, heart rate monitors, or even custom hardware).
- CBPeripheral & CBCentralManager: These are the CoreBluetooth classes that handle device discovery, connections, and data transfers.
- UUIDs & Services: Each Bluetooth device broadcasts a unique identifier and optional services that define what data can be exchanged.
Alright, enough theory — let’s get this thing built.
Building the Bluetooth Scanner App
We’ll build a SwiftUI app that:
- Uses CoreBluetooth to scan for nearby Bluetooth peripherals.
- Displays discovered devices in a SwiftUI List.
- Allows users to select a device and navigate to a details screen to see more Bluetooth metadata.
- Handles Bluetooth permissions correctly.
Step 1: Create the CoreBluetooth Manager
First, we need a BluetoothManager
to handle all Bluetooth interactions. This will act as the brains of our scanner:
import Foundation
import CoreBluetooth
@Observable
final class BluetoothManager: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate {
var discoveredDevices: [CBPeripheral] = []
var selectedDevice: CBPeripheral?
private var centralManager: CBCentralManager!
override init() {
super.init()
centralManager = CBCentralManager(delegate: self, queue: DispatchQueue.main)
}
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == .poweredOn {
centralManager.scanForPeripherals(withServices: nil, options: nil)
} else {
print("Bluetooth is off or not available")
}
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
if !discoveredDevices.contains(peripheral) {
discoveredDevices.append(peripheral)
}
}
func connect(to peripheral: CBPeripheral) {
selectedDevice = peripheral
centralManager.stopScan()
centralManager.connect(peripheral, options: nil)
}
}
This class:
- Initializes a
CBCentralManager
instance to handle Bluetooth operations and assigns the class itself as its delegate, allowing it to respond to Bluetooth state changes and device discoveries. - Starts scanning for Bluetooth peripherals as soon as Bluetooth is powered on by checking the state in the delegate method
centralManagerDidUpdateState
. If Bluetooth is off or unavailable, it logs a message. - Implements the
centralManager(_:didDiscover:advertisementData:rssi:)
delegate method to keep track of discovered peripherals, adding each unique peripheral to the publisheddiscoveredDevices
array. - Provides a
connect(to:)
method to stop scanning, select a specific peripheral from the discovered devices, and initiate a connection, preparing for further interaction like reading device services or characteristics.
Step 2: Create the SwiftUI Interface
Now, let’s create a simple SwiftUI View
to display the discovered devices and allow selection:
import SwiftUI
struct BluetoothScannerView: View {
private var bluetoothManager = BluetoothManager()
@State private var selectedDevice: CBPeripheral?
var body: some View {
NavigationView {
List(bluetoothManager.discoveredDevices, id: \ .identifier) { device in
NavigationLink(destination: BluetoothDetailView(peripheral: device)) {
Text(device.name ?? "Unknown Device")
.onTapGesture {
selectedDevice = device
bluetoothManager.connect(to: device)
}
}
}
.navigationTitle("Bluetooth Scanner")
}
}
}
This SwiftUI view:
- Displays each discovered Bluetooth device in a
List
. - Navigates to
BluetoothDetailView
when a device is selected.
Step 3: Create the Bluetooth Details Screen
Now, let’s add a details screen to show all available Bluetooth metadata:
import SwiftUI
import CoreBluetooth
struct BluetoothDetailView: View {
let peripheral: CBPeripheral
var body: some View {
VStack(alignment: .leading, spacing: 10) {
Text("Device Name: \(peripheral.name ?? "Unknown")")
.font(.headline)
Text("Identifier: \(peripheral.identifier.uuidString)")
Text("Services: \(peripheral.services?.count ?? 0)")
}
.padding()
.navigationTitle("Device Details")
}
}
Final Thoughts
And there you have it! A fully working Bluetooth scanner that not only detects devices but also lets you select one and view all its details. Now, the next time someone throws a Bluetooth question at you in an interview, you’ll be ready to shred through it like a Kirk Hammett solo.
If you’re hungry for more tips, tricks, and a few battle-tested stories from the trenches of native mobile development, swing by my collection of articles: https://medium.com/@wesleymatlock.
These posts are packed with real-world solutions, some laughs, and the kind of knowledge that’s saved me from a few late-night debugging sessions. Let’s keep building apps that rock — and if you’ve got questions or stories of your own, drop me a line. I’d love to hear from you.
🔥 Rock on, and happy scanning! 🎸⚡
By Wesley Matlock on March 6, 2025.
Exported from Medium on May 10, 2025.