Build Bluetooth once. Ship it everywhere.
SimpleSwiftBLE brings SimpleBLE to
Swift on iOS, iPadOS, macOS, and Mac Catalyst. Discover and connect Bluetooth
peripherals, read and write GATT values, subscribe to notifications, and host
local GATT services through one API, with async/await and AsyncStream
throughout.
- One cross-platform API: Share Bluetooth code across your iOS, iPadOS, macOS, and Mac Catalyst apps, with the same API SimpleBLE offers in every other language.
- Native underneath: Use CoreBluetooth, and Dongl USB adapters on macOS, through one maintained stack.
- Built for real products: Act as a central or a peripheral on every supported platform.
- Commercially supported: Ship proprietary applications with commercial licensing and direct technical support from the team that builds SimpleBLE.
- Documentation: The usage guide and examples cover every API.
- Commercial support: Visit simpleble.org or email us about licensing and professional services.
- Community: Ask questions and share projects in GitHub Discussions.
- Dongl: Prototype and ship with the official SimpleBLE USB adapter.
- Contribute: Send changes to simpleble/simpleble; the development guide covers builds and tests.
Use Swift 6.0 (Xcode 16) or newer. In Xcode, choose File > Add Package
Dependencies and enter https://github.com/simpleble/simpleswiftble, or add the
package to your Package.swift:
dependencies: [
.package(url: "https://github.com/simpleble/simpleswiftble", from: "<version>")
],
targets: [
.target(name: "MyApp", dependencies: [.product(name: "SimpleSwiftBLE", package: "simpleswiftble")])
]The package includes SimpleBLE prebuilt for each platform, so only the Swift layer compiles with your app.
Scan for five seconds and print what was found:
import SimpleSwiftBLE
let adapter = try Adapter.getAdapters().first!
let found = adapter.onScanFound
let printer = Task {
for await peripheral in found {
print("\(peripheral.identifier) [\(peripheral.address)] \(peripheral.rssi) dBm")
}
}
try await adapter.scanFor(timeoutMs: 5000)
printer.cancel()Add an NSBluetoothAlwaysUsageDescription entry to your app's Info.plist.
The system asks the user the first time your app uses Bluetooth. A sandboxed
macOS app also needs the Bluetooth capability
(com.apple.security.device.bluetooth).
Events are AsyncStreams. An adapter has onPowerOn, onPowerOff,
onScanStart, onScanStop, onScanFound, and onScanUpdated; a peripheral
has onConnected and onDisconnected. Each stream buffers every event from the
moment you get it, in the order it happened, so get it before the call that
produces the events.
Use scanStart() and scanStop() when your application controls scan timing,
and scanGetResults() for the peripherals seen by the last scan. You always get
the same Peripheral object for the same device, whether it came from a stream
or from a list, and it is Identifiable for SwiftUI lists.
retrieveCachedPeripherals() returns peripherals the system already knows,
without scanning.
Calls that wait on a device or the Bluetooth stack, such as connect(),
read(), and scanFor(), are async functions that never block Swift's
cooperative thread pool, so you can call them from the main actor. Data the
library already holds, such as a peripheral's identifier, rssi, or
manufacturerData(), is returned directly. The
usage guide covers
cancellation and SwiftUI.
Read the battery level of a peripheral and follow its notifications:
let batteryService: BluetoothUUID = "0000180f-0000-1000-8000-00805f9b34fb"
let batteryLevel: BluetoothUUID = "00002a19-0000-1000-8000-00805f9b34fb"
let peripheral = try adapter.scanGetResults().first { $0.identifier == "My Sensor" }!
try await peripheral.connect()
let level = try await peripheral.read(service: batteryService, characteristic: batteryLevel)
print("Battery: \(level[0])%")
for await value in try await peripheral.notify(service: batteryService, characteristic: batteryLevel) {
print("Battery: \(value[0])%")
}services() lists the services, characteristics, capabilities, and
descriptors of a connected peripheral, and before connecting, the advertised
services with their data. read, writeRequest, and writeCommand access
characteristic values, and read and write with a descriptor UUID access
descriptors. notify and indicate return a stream once subscribed, and
cancelling its task unsubscribes. When the peripheral disconnects, the stream
ends; subscribe again after reconnecting. Failures are thrown as
SimpleBLEError, whose code is SimpleBLE's error code.
Create a local peripheral from an adapter, add services and characteristics, then start advertising:
let host = try adapter.createLocalPeripheral()
try host.addAdvertisedService(batteryService)
let battery = try host.addService(batteryService)
.addCharacteristic(batteryLevel, capabilities: [.read, .notify])
try await battery.setValue(Data([100]))
try await host.start()setValue() updates the stored value and notifies subscribers. A local
characteristic has onWrite, onSubscribed, and onUnsubscribed streams, and
setReadHandler computes a value for each read. The local peripheral has
onClientConnected and onClientDisconnected, and
setAdvertisementLocalName() sets the advertised name. Configure services
before start(); call stop() before changing them.
A Dongl is a USB Bluetooth adapter made for SimpleBLE, available on macOS. Its backend is on by default; use the adapters of the Dongl backend:
let adapter = try Backend.getDongl().getAdapters().first!Config.Dongl holds the Dongl backend settings; set them before the first
call that reaches a backend. Pairing through a Dongl happens without the
operating system: set setPasskeyRequestHandler, setPasskeyDisplayHandler,
or setNumericComparisonHandler on the peripheral before connect(). The
handlers are async, so they can wait for the user.
| Platform | Architectures | Requirements |
|---|---|---|
| iOS and iPadOS 15.8 or newer | arm64, and arm64 and x86_64 in the simulator | NSBluetoothAlwaysUsageDescription |
| Mac Catalyst 16.0 or newer | arm64, x86_64 | NSBluetoothAlwaysUsageDescription |
| macOS 15.0 or newer | arm64, x86_64 | NSBluetoothAlwaysUsageDescription |
SimpleSwiftBLE follows SimpleBLE's release versions. Stable releases use
X.Y.Z, and development builds of the main branch are published as
X.Y.Z-devN; depend on one with exact:, or on the latest with
branch: "main".
SimpleSwiftBLE follows the SimpleBLE license, the Business Source License 1.1 (BUSL-1.1). It is free for non-commercial use, and commercial use needs a commercial license. We offer free commercial licenses for small projects and early-stage companies: email us or leave us a message to discuss your use case. Commercial licenses carry no royalties or revenue sharing; see the licensing page for terms.
SimpleBLE is a project powered by The California Open Source Company.