$npx -y skills add dpearson2699/swift-ios-skills --skill accessorysetupkitDiscover 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 sca
| 1 | # AccessorySetupKit |
| 2 | |
| 3 | Use the iOS 18+ system picker for privacy-preserving Bluetooth/Wi-Fi accessory |
| 4 | discovery and authorization, then hand off communication to CoreBluetooth or |
| 5 | NetworkExtension. |
| 6 | |
| 7 | ## Contents |
| 8 | |
| 9 | - [Setup and Entitlements](#setup-and-entitlements) |
| 10 | - [Discovery Descriptors](#discovery-descriptors) |
| 11 | - [Presenting the Picker](#presenting-the-picker) |
| 12 | - [Event Handling](#event-handling) |
| 13 | - [Bluetooth Accessories](#bluetooth-accessories) |
| 14 | - [Wi-Fi Accessories](#wi-fi-accessories) |
| 15 | - [Migration from CoreBluetooth](#migration-from-corebluetooth) |
| 16 | - [Common Mistakes](#common-mistakes) |
| 17 | - [Review Checklist](#review-checklist) |
| 18 | - [References](#references) |
| 19 | |
| 20 | ## Setup and Entitlements |
| 21 | |
| 22 | ### Info.plist Configuration |
| 23 | |
| 24 | Add these keys to the app's Info.plist: |
| 25 | |
| 26 | | Key | Type | Purpose | |
| 27 | |---|---|---| |
| 28 | | `NSAccessorySetupSupports` | `[String]` | Required. Array containing `Bluetooth` and/or `WiFi` | |
| 29 | | `NSAccessorySetupBluetoothServices` | `[String]` | Service UUIDs the app discovers (Bluetooth) | |
| 30 | | `NSAccessorySetupBluetoothNames` | `[String]` | Bluetooth names or substrings to match | |
| 31 | | `NSAccessorySetupBluetoothCompanyIdentifiers` | `[String]` | Two-byte Bluetooth company identifiers | |
| 32 | |
| 33 | The Bluetooth-specific keys must match the values used in `ASDiscoveryDescriptor`. |
| 34 | If the app uses identifiers, names, or services not declared in Info.plist, the |
| 35 | app crashes during AccessorySetupKit discovery. For Wi-Fi accessories, include |
| 36 | `WiFi` in `NSAccessorySetupSupports` and match the descriptor's SSID rule. |
| 37 | |
| 38 | ### No Bluetooth Permission Required |
| 39 | |
| 40 | When an app declares `NSAccessorySetupSupports` with `Bluetooth`, creating a |
| 41 | `CBCentralManager` no longer triggers the system Bluetooth permission dialog. |
| 42 | The central manager's state transitions to `poweredOn` only when the app has |
| 43 | at least one paired accessory via AccessorySetupKit. |
| 44 | |
| 45 | ## Discovery Descriptors |
| 46 | |
| 47 | `ASDiscoveryDescriptor` defines the matching criteria for finding accessories. |
| 48 | The system matches scanned results against all rules in the descriptor to |
| 49 | filter for the target accessory. |
| 50 | |
| 51 | ### Bluetooth Descriptor |
| 52 | |
| 53 | ```swift |
| 54 | import AccessorySetupKit |
| 55 | import CoreBluetooth |
| 56 | |
| 57 | var descriptor = ASDiscoveryDescriptor() |
| 58 | descriptor.bluetoothServiceUUID = CBUUID(string: "12345678-1234-1234-1234-123456789ABC") |
| 59 | descriptor.bluetoothNameSubstring = "MyDevice" |
| 60 | descriptor.bluetoothRange = .immediate // Only nearby devices |
| 61 | ``` |
| 62 | |
| 63 | A Bluetooth descriptor needs at least one of `bluetoothCompanyIdentifier` or |
| 64 | `bluetoothServiceUUID`. Add narrower matchers as needed: |
| 65 | |
| 66 | - `bluetoothNameSubstring` with a company identifier or service UUID |
| 67 | - `bluetoothManufacturerDataBlob` and `bluetoothManufacturerDataMask` with a |
| 68 | company identifier; blob and mask must have the same length |
| 69 | - `bluetoothServiceDataBlob` and `bluetoothServiceDataMask` with a service UUID; |
| 70 | blob and mask must have the same length |
| 71 | |
| 72 | ### Wi-Fi Descriptor |
| 73 | |
| 74 | ```swift |
| 75 | var descriptor = ASDiscoveryDescriptor() |
| 76 | descriptor.ssid = "MyAccessory-Network" |
| 77 | // OR use a prefix: |
| 78 | // descriptor.ssidPrefix = "MyAccessory-" |
| 79 | ``` |
| 80 | |
| 81 | Supply either `ssid` or `ssidPrefix`, not both. The app crashes if both are set. |
| 82 | The `ssidPrefix` must have a non-zero length. |
| 83 | |
| 84 | ### Bluetooth Range |
| 85 | |
| 86 | Control the physical proximity required for discovery: |
| 87 | |
| 88 | | Value | Behavior | |
| 89 | |---|---| |
| 90 | | `.default` | Standard Bluetooth range | |
| 91 | | `.immediate` | Only accessories in close physical proximity | |
| 92 | |
| 93 | ### Support Options |
| 94 | |
| 95 | Set `supportedOptions` on the descriptor to declare the accessory's capabilities: |
| 96 | |
| 97 | ```swift |
| 98 | descriptor.supportedOptions = [.bluetoothPairingLE, .bluetoothTransportBridging] |
| 99 | ``` |
| 100 | |
| 101 | | Option | Purpose | |
| 102 | |---|---| |
| 103 | | `.bluetoothPairingLE` | BLE pairing support | |
| 104 | | `.bluetoothTransportBridging` | Bluetooth transport bridging | |
| 105 | | `.bluetoothHID` | Bluetooth HID device | |
| 106 | |
| 107 | ## Presenting the Picker |
| 108 | |
| 109 | ### Creating the Session |
| 110 | |
| 111 | Create and activate an `ASAccessorySession` to manage discovery lifecycle. Wait for `.activated` before reading `session.accessories` or presenting the picker: |
| 112 | |
| 113 | ```swift |
| 114 | import AccessorySetupKit |
| 115 | |
| 116 | final class AccessoryManager { |
| 117 | private let session = ASAccessorySession() |
| 118 | |
| 119 | func start() { |
| 120 | session.activate(on: .main) { [weak self] event in |
| 121 | self?.handleEvent(event) |
| 122 | } |
| 123 | } |
| 124 | |
| 125 | private func handleEvent(_ event: ASAccessoryEvent) { |
| 126 | switch event.eventType { |
| 127 | case .activated: |
| 128 | // Session ready. Check session.accessories for previously paired devices. |
| 129 | break |
| 130 | case .accessoryAdded: |
| 131 | guard let accessory = event.accessory else { return } |
| 132 | handleAccessoryAdded(accessory) |
| 133 | case .accessory |