Discover and configure Bluetooth and Wi-Fi accessories using AccessorySetupKit. Use when presenting a privacy-preserving accessory picker, defining discovery descriptors for BLE or Wi-Fi devices, handling accessory session events, migrating from CoreBluetooth permission-based scanning, or setting up accessories without requiring broad Bluetooth permissions.
94%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
Discover and pair Bluetooth and Wi-Fi accessories with privacy-preserving iOS 18+ system pickers. ASAccessorySession eliminates the need for broad system Bluetooth authorization prompts. After user authorization, hand off communication to CoreBluetooth or NetworkExtension.
Add matching configuration keys to Info.plist:
| Key | Type | Purpose |
|---|---|---|
NSAccessorySetupSupports | [String] | Required. Contains Bluetooth and/or WiFi |
NSAccessorySetupBluetoothServices | [String] | Service UUIDs discovered by the app |
NSAccessorySetupBluetoothNames | [String] | Accessory name substrings to match |
NSAccessorySetupBluetoothCompanyIdentifiers | [String] | Two-byte Bluetooth company identifiers |
The Bluetooth-specific Info.plist values must strictly match the rules in ASDiscoveryDescriptor. If an accessory matches a descriptor not declared in Info.plist, the app crashes at runtime.
No user Bluetooth permission prompt is triggered; CBCentralManager transitions to poweredOn once at least one accessory is authorized via AccessorySetupKit.
Define matching criteria for scanning nearby hardware:
import AccessorySetupKit
import CoreBluetooth
// Bluetooth Descriptor
var btDescriptor = ASDiscoveryDescriptor()
btDescriptor.bluetoothServiceUUID = CBUUID(string: "12345678-1234-1234-1234-123456789ABC")
btDescriptor.bluetoothNameSubstring = "SmartSensor"
btDescriptor.bluetoothRange = .immediate // Only nearby devices
// Wi-Fi Descriptor (provide either ssid OR ssidPrefix, not both)
var wifiDescriptor = ASDiscoveryDescriptor()
wifiDescriptor.ssidPrefix = "SmartSensor-"Activate an ASAccessorySession and present the system picker:
import AccessorySetupKit
final class AccessoryCoordinator {
let session = ASAccessorySession()
func start() {
session.activate(on: .main) { [weak self] event in
switch event.eventType {
case .accessoryAdded, .accessoryChanged:
if let accessory = event.accessory {
self?.handleAuthorizedAccessory(accessory)
}
case .accessoryRemoved:
self?.handleRemovedAccessory(event.accessory)
case .sessionReset:
self?.session.activate(on: .main, eventHandler: { _ in })
@unknown default:
break
}
}
}
func presentPicker(descriptor: ASDiscoveryDescriptor) {
let displayItem = ASPickerDisplayItem(
name: "Smart Sensor",
productImage: UIImage(named: "sensor")!,
descriptor: descriptor
)
session.showPicker(for: [displayItem]) { error in
if let error { print("Picker failed: \(error)") }
}
}
}After the user selects an accessory in the system picker:
CBCentralManager.retrievePeripherals(withIdentifiers: [accessory.bluetoothIdentifier]). Do not run general scans.NEHotspotConfigurationManager).ASDiscoveryDescriptor must be declared in Info.plist or the app crashes.ssid or ssidPrefix, never both.scanForPeripherals(); retrieve authorized peripherals by identifier..sessionReset: The system can invalidate sessions during daemon restarts; recreate or reactivate the session upon .sessionReset.NSAccessorySetupSupports declared in Info.plist with Bluetooth / WiFissid or ssidPrefix, not bothretrievePeripherals(withIdentifiers:) without scanning.accessoryAdded, .accessoryRemoved, and .sessionResetthienqo/ios-skills
6bf8b62
Canonical home
since May 23, 2026
Also appears in
on Sep 6, 2026
since May 22, 2026
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.