iOS 27 introduces Bluetooth Channel Sounding via Core Bluetooth and Nearby Interaction, enabling apps to measure actual distance (and optionally direction) to paired Bluetooth accessories without requiring UWB hardware.
⢠Replaces unreliable RSSI-based distance estimates with real phase-based ranging over the 2.4GHz band, giving developers meter-level accuracy
⢠Works with accessories that only have a Bluetooth 6.3 chipset ā no UWB chip required, lowering hardware cost
⢠Integrating with NearbyInteraction adds camera-fused direction estimates, enabling spatial UI flows like "accessory is 8m to your right"
Demonstrates starting a Core Bluetooth Channel Sounding session against a paired peripheral and logging distance measurements as they arrive via the delegate callback.
import SwiftUI
import CoreBluetooth
// MARK: - Channel Sounding Delegate Handler
class ChannelSoundingManager: NSObject, ObservableObject {
@Published var distanceMeters: Double? = nil
@Published var statusMessage: String = "Idle"
private var centralManager: CBCentralManager!
private var targetPeripheral: CBPeripheral?
override init() {
super.init()
centralManager = CBCentralManager(delegate: nil, queue: .main)
}
func startSession(with peripheral: CBPeripheral) {
guard CBCentralManager.supportsFeatures(.channelSounding) else {
statusMessage = "Channel Sounding not supported on this device"
return
}
targetPeripheral = peripheral
peripheral.delegate = self
peripheral.startChannelSoundingSession()
statusMessage = "Channel Sounding session started"
}
func stopSession() {
targetPeripheral?.cancelChannelSoundingSession()
statusMessage = "Session cancelled"
}
}
// MARK: - CBPeripheralDelegate
extension ChannelSoundingManager: CBPeripheralDelegate {
func peripheral(_ peripheral: CBPeripheral,
didReceive results: CBChannelSoundingResult) {
if let distance = results.distance {
distanceMeters = distance
statusMessage = String(format: "Distance: %.2f m", distance)
} else {
statusMessage = "Measurement returned nil distance"
}
}
func peripheralDidCompleteChannelSoundingSession(_ peripheral: CBPeripheral) {
statusMessage = "Session completed"
distanceMeters = nil
}
}
// MARK: - SwiftUI View
struct AccessoryDistanceView: View {
@StateObject private var manager = ChannelSoundingManager()
var body: some View {
VStack(spacing: 24) {
Text("Channel Sounding Demo")
.font(.title2.bold())
if let distance = manager.distanceMeters {
Text(String(format: "š” %.2f meters", distance))
.font(.largeTitle)
.monospacedDigit()
} else {
Text("ā")
.font(.largeTitle)
.foregroundStyle(.secondary)
}
Text(manager.statusMessage)
.font(.footnote)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
Button("Stop Session", role: .destructive) {
manager.stopSession()
}
.buttonStyle(.borderedProminent)
}
.padding()
}
}Channel Sounding only runs in the foreground ā sessions pause automatically on backgrounding. iOS may throttle measurement frequency under heavy Bluetooth/Wi-Fi load. NINearbyObject distance and direction are both Optional and may be nil if a procedure fails. CameraAssistance must be explicitly enabled on the NISessionConfiguration to receive direction data.
Requires iPhone with Apple N1 chip. Accessory must support Bluetooth 6.3 with inline PCT, mode-0 and mode-2 phase-based ranging, and T_FCS of at least 100µs. Session pauses when app moves to background.
More iOS 27 APIs land every week.
Get notified when new capabilities are published ā no noise, just signal.