accessorysetupkit

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

AccessorySetupKit

AccessorySetupKit

Privacy-preserving accessory discovery and setup for Bluetooth and Wi-Fi devices. Replaces broad Bluetooth/Wi-Fi permission prompts with a system-provided picker that grants per-accessory access with a single tap. Available iOS 18+ / Swift 6.3.
After setup, apps continue using CoreBluetooth and NetworkExtension for communication. AccessorySetupKit handles only the discovery and authorization step.
为蓝牙和Wi-Fi设备提供具备隐私保护能力的配件发现与设置能力,通过系统提供的选择器替代全局蓝牙/Wi-Fi权限弹窗,用户只需单次点击即可授予单配件访问权限。适用于iOS 18+ / Swift 6.3版本。
完成设置后,应用可继续使用CoreBluetooth和NetworkExtension进行通信,AccessorySetupKit仅负责发现与授权环节。

Contents

目录

Setup and Entitlements

设置与权限配置

Info.plist Configuration

Info.plist配置

Add these keys to the app's Info.plist:
KeyTypePurpose
NSAccessorySetupSupports
[String]
Required. Array containing
Bluetooth
and/or
WiFi
NSAccessorySetupBluetoothServices
[String]
Service UUIDs the app discovers (Bluetooth)
NSAccessorySetupBluetoothNames
[String]
Bluetooth names or substrings to match
NSAccessorySetupBluetoothCompanyIdentifiers
[Number]
Bluetooth company identifiers
The Bluetooth-specific keys must match the values used in
ASDiscoveryDescriptor
. If the app uses identifiers, names, or services not declared in Info.plist, the app crashes at discovery time.
将以下键添加到应用的Info.plist中:
类型用途
NSAccessorySetupSupports
[String]
必填,数组包含
Bluetooth
和/或
WiFi
NSAccessorySetupBluetoothServices
[String]
应用要发现的蓝牙服务UUID
NSAccessorySetupBluetoothNames
[String]
要匹配的蓝牙名称或子串
NSAccessorySetupBluetoothCompanyIdentifiers
[Number]
蓝牙厂商标识符
蓝牙专属的键必须与
ASDiscoveryDescriptor
中使用的值匹配,如果应用使用了未在Info.plist中声明的标识符、名称或服务,会在发现阶段崩溃。

No Bluetooth Permission Required

无需申请蓝牙权限

When an app declares
NSAccessorySetupSupports
with
Bluetooth
, creating a
CBCentralManager
no longer triggers the system Bluetooth permission dialog. The central manager's state transitions to
poweredOn
only when the app has at least one paired accessory via AccessorySetupKit.
当应用在
NSAccessorySetupSupports
中声明了
Bluetooth
后,创建
CBCentralManager
不会再触发系统蓝牙权限弹窗。只有当应用至少通过AccessorySetupKit配对过一个配件时,中央管理器的状态才会变为
poweredOn

Discovery Descriptors

发现描述符

ASDiscoveryDescriptor
defines the matching criteria for finding accessories. The system matches scanned results against all rules in the descriptor to filter for the target accessory.
ASDiscoveryDescriptor
定义了查找配件的匹配规则,系统会将扫描结果与描述符中的所有规则进行匹配,过滤出目标配件。

Bluetooth Descriptor

蓝牙描述符

swift
import AccessorySetupKit
import CoreBluetooth

var descriptor = ASDiscoveryDescriptor()
descriptor.bluetoothServiceUUID = CBUUID(string: "12345678-1234-1234-1234-123456789ABC")
descriptor.bluetoothNameSubstring = "MyDevice"
descriptor.bluetoothRange = .immediate  // Only nearby devices
A Bluetooth descriptor requires
bluetoothCompanyIdentifier
or
bluetoothServiceUUID
, plus at least one of:
  • bluetoothNameSubstring
  • bluetoothManufacturerDataBlob
    and
    bluetoothManufacturerDataMask
    (same length)
  • bluetoothServiceDataBlob
    and
    bluetoothServiceDataMask
    (same length)
swift
import AccessorySetupKit
import CoreBluetooth

var descriptor = ASDiscoveryDescriptor()
descriptor.bluetoothServiceUUID = CBUUID(string: "12345678-1234-1234-1234-123456789ABC")
descriptor.bluetoothNameSubstring = "MyDevice"
descriptor.bluetoothRange = .immediate  // 仅匹配近距离设备
蓝牙描述符需要配置
bluetoothCompanyIdentifier
bluetoothServiceUUID
,同时至少配置以下一项:
  • bluetoothNameSubstring
  • bluetoothManufacturerDataBlob
    bluetoothManufacturerDataMask
    (长度必须一致)
  • bluetoothServiceDataBlob
    bluetoothServiceDataMask
    (长度必须一致)

Wi-Fi Descriptor

Wi-Fi描述符

swift
var descriptor = ASDiscoveryDescriptor()
descriptor.ssid = "MyAccessory-Network"
// OR use a prefix:
// descriptor.ssidPrefix = "MyAccessory-"
Supply either
ssid
or
ssidPrefix
, not both. The app crashes if both are set. The
ssidPrefix
must have a non-zero length.
swift
var descriptor = ASDiscoveryDescriptor()
descriptor.ssid = "MyAccessory-Network"
// 或者使用前缀匹配:
// descriptor.ssidPrefix = "MyAccessory-"
只能配置
ssid
ssidPrefix
其中一项,同时配置会导致应用崩溃,
ssidPrefix
长度不能为0。

Bluetooth Range

蓝牙范围

Control the physical proximity required for discovery:
ValueBehavior
.default
Standard Bluetooth range
.immediate
Only accessories in close physical proximity
控制配件发现所需的物理距离:
行为
.default
标准蓝牙覆盖范围
.immediate
仅匹配物理距离极近的配件

Support Options

支持选项

Set
supportedOptions
on the descriptor to declare the accessory's capabilities:
swift
descriptor.supportedOptions = [.bluetoothPairingLE, .bluetoothTransportBridging]
OptionPurpose
.bluetoothPairingLE
BLE pairing support
.bluetoothTransportBridging
Bluetooth transport bridging
.bluetoothHID
Bluetooth HID device
在描述符上设置
supportedOptions
来声明配件的能力:
swift
descriptor.supportedOptions = [.bluetoothPairingLE, .bluetoothTransportBridging]
选项用途
.bluetoothPairingLE
支持BLE配对
.bluetoothTransportBridging
支持蓝牙传输桥接
.bluetoothHID
蓝牙HID设备

Presenting the Picker

展示选择器

Creating the Session

创建会话

Create and activate an
ASAccessorySession
to manage discovery lifecycle:
swift
import AccessorySetupKit

final class AccessoryManager {
    private let session = ASAccessorySession()

    func start() {
        session.activate(on: .main) { [weak self] event in
            self?.handleEvent(event)
        }
    }

    private func handleEvent(_ event: ASAccessoryEvent) {
        switch event.eventType {
        case .activated:
            // Session ready. Check session.accessories for previously paired devices.
            break
        case .accessoryAdded:
            guard let accessory = event.accessory else { return }
            handleAccessoryAdded(accessory)
        case .accessoryChanged:
            // Accessory properties changed (e.g., display name updated in Settings)
            break
        case .accessoryRemoved:
            // Accessory removed by user or app
            break
        case .invalidated:
            // Session invalidated, cannot be reused
            break
        default:
            break
        }
    }
}
创建并激活
ASAccessorySession
来管理发现生命周期:
swift
import AccessorySetupKit

final class AccessoryManager {
    private let session = ASAccessorySession()

    func start() {
        session.activate(on: .main) { [weak self] event in
            self?.handleEvent(event)
        }
    }

    private func handleEvent(_ event: ASAccessoryEvent) {
        switch event.eventType {
        case .activated:
            // 会话已就绪,可通过session.accessories查询之前配对的设备
            break
        case .accessoryAdded:
            guard let accessory = event.accessory else { return }
            handleAccessoryAdded(accessory)
        case .accessoryChanged:
            // 配件属性已变更(例如在设置中更新了显示名称)
            break
        case .accessoryRemoved:
            // 配件被用户或应用移除
            break
        case .invalidated:
            // 会话已失效,无法复用
            break
        default:
            break
        }
    }
}

Showing the Picker

显示选择器

Create
ASPickerDisplayItem
instances with a name, product image, and discovery descriptor, then pass them to the session:
swift
func showAccessoryPicker() {
    var descriptor = ASDiscoveryDescriptor()
    descriptor.bluetoothServiceUUID = CBUUID(string: "ABCD1234-0000-1000-8000-00805F9B34FB")

    guard let image = UIImage(named: "my-accessory") else { return }

    let item = ASPickerDisplayItem(
        name: "My Bluetooth Accessory",
        productImage: image,
        descriptor: descriptor
    )

    session.showPicker(for: [item]) { error in
        if let error {
            print("Picker failed: \(error.localizedDescription)")
        }
    }
}
The picker runs in a separate system process. It shows each matching device as a separate item. When multiple devices match a given descriptor, the picker creates a horizontal carousel.
创建包含名称、产品图和发现描述符的
ASPickerDisplayItem
实例,然后传递给会话:
swift
func showAccessoryPicker() {
    var descriptor = ASDiscoveryDescriptor()
    descriptor.bluetoothServiceUUID = CBUUID(string: "ABCD1234-0000-1000-8000-00805F9B34FB")

    guard let image = UIImage(named: "my-accessory") else { return }

    let item = ASPickerDisplayItem(
        name: "My Bluetooth Accessory",
        productImage: image,
        descriptor: descriptor
    )

    session.showPicker(for: [item]) { error in
        if let error {
            print("选择器加载失败: \(error.localizedDescription)")
        }
    }
}
选择器运行在独立的系统进程中,会将每个匹配的设备展示为单独的条目。当多个设备匹配同一个描述符时,选择器会生成横向轮播列表。

Setup Options

设置选项

Configure picker behavior per display item:
swift
var item = ASPickerDisplayItem(
    name: "My Accessory",
    productImage: image,
    descriptor: descriptor
)
item.setupOptions = [.rename, .confirmAuthorization]
OptionEffect
.rename
Allow renaming the accessory during setup
.confirmAuthorization
Show authorization confirmation before setup
.finishInApp
Signal that setup continues in the app after pairing
针对每个展示项配置选择器行为:
swift
var item = ASPickerDisplayItem(
    name: "My Accessory",
    productImage: image,
    descriptor: descriptor
)
item.setupOptions = [.rename, .confirmAuthorization]
选项效果
.rename
允许在设置过程中重命名配件
.confirmAuthorization
设置前展示授权确认页
.finishInApp
标识配对完成后会在应用内继续后续设置流程

Product Images

产品图规范

The picker displays images in a 180x120 point container. Best practices:
  • Use high-resolution images for all screen scale factors
  • Use transparent backgrounds for correct light/dark mode appearance
  • Adjust transparent borders as padding to control apparent accessory size
  • Test in both light and dark mode
选择器会在180x120point的容器中展示图片,最佳实践:
  • 为所有屏幕缩放因子提供高分辨率图片
  • 使用透明背景以适配亮/暗模式显示效果
  • 调整透明边框作为内边距来控制配件的显示大小
  • 在亮/暗模式下分别测试显示效果

Event Handling

事件处理

Event Types

事件类型

The session delivers
ASAccessoryEvent
objects through the event handler:
EventWhen
.activated
Session is active, query
session.accessories
.accessoryAdded
User selected an accessory in the picker
.accessoryChanged
Accessory properties updated (e.g., renamed)
.accessoryRemoved
Accessory removed from system
.invalidated
Session invalidated, create a new one
.migrationComplete
Migration of legacy accessories completed
.pickerDidPresent
Picker appeared on screen
.pickerDidDismiss
Picker dismissed
.pickerSetupBridging
Transport bridging setup in progress
.pickerSetupPairing
Bluetooth pairing in progress
.pickerSetupFailed
Setup failed
.pickerSetupRename
User is renaming the accessory
.accessoryDiscovered
New accessory found (custom filtering mode)
会话会通过事件处理器传递
ASAccessoryEvent
对象:
事件触发时机
.activated
会话已激活,可查询
session.accessories
.accessoryAdded
用户在选择器中选中了一个配件
.accessoryChanged
配件属性已更新(例如被重命名)
.accessoryRemoved
配件已从系统中移除
.invalidated
会话已失效,需要创建新会话
.migrationComplete
旧配件迁移已完成
.pickerDidPresent
选择器已在屏幕上展示
.pickerDidDismiss
选择器已关闭
.pickerSetupBridging
传输桥接设置进行中
.pickerSetupPairing
蓝牙配对进行中
.pickerSetupFailed
设置失败
.pickerSetupRename
用户正在重命名配件
.accessoryDiscovered
发现新配件(自定义过滤模式)

Coordinating Picker Dismissal

协调选择器关闭逻辑

When the user selects an accessory,
.accessoryAdded
fires before
.pickerDidDismiss
. To show custom setup UI after the picker closes, store the accessory on the first event and act on it after dismissal:
swift
private var pendingAccessory: ASAccessory?

private func handleEvent(_ event: ASAccessoryEvent) {
    switch event.eventType {
    case .accessoryAdded:
        pendingAccessory = event.accessory
    case .pickerDidDismiss:
        if let accessory = pendingAccessory {
            pendingAccessory = nil
            beginCustomSetup(accessory)
        }
    default:
        break
    }
}
当用户选中配件时,
.accessoryAdded
会在
.pickerDidDismiss
之前触发。如果需要在选择器关闭后展示自定义设置UI,可以在第一个事件触发时暂存配件,在选择器关闭后再进行处理:
swift
private var pendingAccessory: ASAccessory?

private func handleEvent(_ event: ASAccessoryEvent) {
    switch event.eventType {
    case .accessoryAdded:
        pendingAccessory = event.accessory
    case .pickerDidDismiss:
        if let accessory = pendingAccessory {
            pendingAccessory = nil
            beginCustomSetup(accessory)
        }
    default:
        break
    }
}

Bluetooth Accessories

蓝牙配件

After an accessory is added via the picker, use CoreBluetooth to communicate. The
bluetoothIdentifier
on the
ASAccessory
maps to a
CBPeripheral
.
swift
import CoreBluetooth

func handleAccessoryAdded(_ accessory: ASAccessory) {
    guard let btIdentifier = accessory.bluetoothIdentifier else { return }

    // Create CBCentralManager — no Bluetooth permission prompt appears
    let centralManager = CBCentralManager(delegate: self, queue: nil)

    // After poweredOn, retrieve the peripheral
    let peripherals = centralManager.retrievePeripherals(
        withIdentifiers: [btIdentifier]
    )
    guard let peripheral = peripherals.first else { return }
    centralManager.connect(peripheral, options: nil)
}
Key points:
  • CBCentralManager
    state reaches
    .poweredOn
    only when the app has paired accessories
  • Scanning with
    scanForPeripherals(withServices:)
    returns only accessories paired through AccessorySetupKit
  • No
    NSBluetoothAlwaysUsageDescription
    is needed when using AccessorySetupKit exclusively
通过选择器添加配件后,使用CoreBluetooth进行通信,
ASAccessory
上的
bluetoothIdentifier
对应
CBPeripheral
的标识:
swift
import CoreBluetooth

func handleAccessoryAdded(_ accessory: ASAccessory) {
    guard let btIdentifier = accessory.bluetoothIdentifier else { return }

    // 创建CBCentralManager — 不会弹出蓝牙权限申请
    let centralManager = CBCentralManager(delegate: self, queue: nil)

    // 状态变为poweredOn后,获取外设
    let peripherals = centralManager.retrievePeripherals(
        withIdentifiers: [btIdentifier]
    )
    guard let peripheral = peripherals.first else { return }
    centralManager.connect(peripheral, options: nil)
}
核心要点:
  • 只有当应用有已配对的配件时,
    CBCentralManager
    的状态才会变为
    .poweredOn
  • 使用
    scanForPeripherals(withServices:)
    扫描只会返回通过AccessorySetupKit配对的配件
  • 仅使用AccessorySetupKit时,无需配置
    NSBluetoothAlwaysUsageDescription

Wi-Fi Accessories

Wi-Fi配件

For Wi-Fi accessories, the
ssid
on the
ASAccessory
identifies the network. Use
NEHotspotConfiguration
from NetworkExtension to join it:
swift
import NetworkExtension

func handleWiFiAccessoryAdded(_ accessory: ASAccessory) {
    guard let ssid = accessory.ssid else { return }

    let configuration = NEHotspotConfiguration(ssid: ssid)
    NEHotspotConfigurationManager.shared.apply(configuration) { error in
        if let error {
            print("Wi-Fi join failed: \(error.localizedDescription)")
        }
    }
}
Because the accessory was discovered through AccessorySetupKit, joining the network does not trigger the standard Wi-Fi access prompt.
对于Wi-Fi配件,
ASAccessory
上的
ssid
标识对应的网络,使用NetworkExtension的
NEHotspotConfiguration
加入网络:
swift
import NetworkExtension

func handleWiFiAccessoryAdded(_ accessory: ASAccessory) {
    guard let ssid = accessory.ssid else { return }

    let configuration = NEHotspotConfiguration(ssid: ssid)
    NEHotspotConfigurationManager.shared.apply(configuration) { error in
        if let error {
            print("加入Wi-Fi失败: \(error.localizedDescription)")
        }
    }
}
由于配件是通过AccessorySetupKit发现的,加入网络不会触发标准的Wi-Fi访问权限弹窗。

Migration from CoreBluetooth

从CoreBluetooth迁移

Apps with existing CoreBluetooth-authorized accessories can migrate them to AccessorySetupKit using
ASMigrationDisplayItem
. This is a one-time operation that registers known accessories in the new system.
swift
func migrateExistingAccessories() {
    guard let image = UIImage(named: "my-accessory") else { return }

    var descriptor = ASDiscoveryDescriptor()
    descriptor.bluetoothServiceUUID = CBUUID(string: "ABCD1234-0000-1000-8000-00805F9B34FB")

    let migrationItem = ASMigrationDisplayItem(
        name: "My Accessory",
        productImage: image,
        descriptor: descriptor
    )
    // Set the peripheral identifier from CoreBluetooth
    migrationItem.peripheralIdentifier = existingPeripheralUUID

    // For Wi-Fi accessories:
    // migrationItem.hotspotSSID = "MyAccessory-WiFi"

    session.showPicker(for: [migrationItem]) { error in
        if let error {
            print("Migration failed: \(error.localizedDescription)")
        }
    }
}
Migration rules:
  • If
    showPicker
    contains only migration items, the system shows an informational page instead of a discovery picker
  • If migration items are mixed with regular display items, migration happens only when a new accessory is discovered and set up
  • Do not initialize
    CBCentralManager
    before migration completes — doing so causes an error and the picker fails to appear
  • The session receives
    .migrationComplete
    when migration finishes
已有CoreBluetooth授权配件的应用可以使用
ASMigrationDisplayItem
将其迁移到AccessorySetupKit,这是一次性操作,会将已知配件注册到新系统中:
swift
func migrateExistingAccessories() {
    guard let image = UIImage(named: "my-accessory") else { return }

    var descriptor = ASDiscoveryDescriptor()
    descriptor.bluetoothServiceUUID = CBUUID(string: "ABCD1234-0000-1000-8000-00805F9B34FB")

    let migrationItem = ASMigrationDisplayItem(
        name: "My Accessory",
        productImage: image,
        descriptor: descriptor
    )
    // 设置CoreBluetooth中的外设标识
    migrationItem.peripheralIdentifier = existingPeripheralUUID

    // 对于Wi-Fi配件:
    // migrationItem.hotspotSSID = "MyAccessory-WiFi"

    session.showPicker(for: [migrationItem]) { error in
        if let error {
            print("迁移失败: \(error.localizedDescription)")
        }
    }
}
迁移规则:
  • 如果
    showPicker
    仅包含迁移项,系统会展示信息页而非发现选择器
  • 如果迁移项与普通展示项混合使用,只有当新配件被发现并设置完成时才会执行迁移
  • 迁移完成前不要初始化
    CBCentralManager
    ,否则会导致错误,选择器无法展示
  • 迁移完成后会话会收到
    .migrationComplete
    事件

Common Mistakes

常见错误

DON'T: Omit Info.plist keys for Bluetooth discovery

不要遗漏蓝牙发现对应的Info.plist键

The app crashes if it uses identifiers, names, or services in descriptors that are not declared in Info.plist.
swift
// WRONG — service UUID not in NSAccessorySetupBluetoothServices
var descriptor = ASDiscoveryDescriptor()
descriptor.bluetoothServiceUUID = CBUUID(string: "UNDECLARED-UUID")
session.showPicker(for: [item]) { _ in }  // Crash

// CORRECT — declare all UUIDs in Info.plist first
// Info.plist: NSAccessorySetupBluetoothServices = ["ABCD1234-..."]
var descriptor = ASDiscoveryDescriptor()
descriptor.bluetoothServiceUUID = CBUUID(string: "ABCD1234-...")
如果应用在描述符中使用了未在Info.plist中声明的标识符、名称或服务,会导致崩溃。
swift
// 错误 — 服务UUID不在NSAccessorySetupBluetoothServices中
var descriptor = ASDiscoveryDescriptor()
descriptor.bluetoothServiceUUID = CBUUID(string: "UNDECLARED-UUID")
session.showPicker(for: [item]) { _ in }  // 崩溃

// 正确 — 先在Info.plist中声明所有UUID
// Info.plist: NSAccessorySetupBluetoothServices = ["ABCD1234-..."]
var descriptor = ASDiscoveryDescriptor()
descriptor.bluetoothServiceUUID = CBUUID(string: "ABCD1234-...")

DON'T: Set both ssid and ssidPrefix

不要同时设置ssid和ssidPrefix

swift
// WRONG — crashes at runtime
var descriptor = ASDiscoveryDescriptor()
descriptor.ssid = "MyNetwork"
descriptor.ssidPrefix = "My"  // Cannot set both

// CORRECT — use one or the other
var descriptor = ASDiscoveryDescriptor()
descriptor.ssid = "MyNetwork"
swift
// 错误 — 运行时会崩溃
var descriptor = ASDiscoveryDescriptor()
descriptor.ssid = "MyNetwork"
descriptor.ssidPrefix = "My"  // 不能同时设置

// 正确 — 仅设置其中一项
var descriptor = ASDiscoveryDescriptor()
descriptor.ssid = "MyNetwork"

DON'T: Initialize CBCentralManager before migration

不要在迁移完成前初始化CBCentralManager

swift
// WRONG — migration fails, picker does not appear
let central = CBCentralManager(delegate: self, queue: nil)
session.showPicker(for: [migrationItem]) { error in
    // error is non-nil
}

// CORRECT — wait for .migrationComplete before using CoreBluetooth
session.activate(on: .main) { event in
    if event.eventType == .migrationComplete {
        let central = CBCentralManager(delegate: self, queue: nil)
    }
}
swift
// 错误 — 迁移失败,选择器无法展示
let central = CBCentralManager(delegate: self, queue: nil)
session.showPicker(for: [migrationItem]) { error in
    // error不为空
}

// 正确 — 等待.migrationComplete事件后再使用CoreBluetooth
session.activate(on: .main) { event in
    if event.eventType == .migrationComplete {
        let central = CBCentralManager(delegate: self, queue: nil)
    }
}

DON'T: Show the picker without user intent

不要在无用户主动操作的情况下展示选择器

swift
// WRONG — picker appears unexpectedly on app launch
override func viewDidLoad() {
    super.viewDidLoad()
    session.showPicker(for: items) { _ in }
}

// CORRECT — bind picker to a user action
@IBAction func addAccessoryTapped(_ sender: UIButton) {
    session.showPicker(for: items) { _ in }
}
swift
// 错误 — 应用启动时选择器会意外弹出
override func viewDidLoad() {
    super.viewDidLoad()
    session.showPicker(for: items) { _ in }
}

// 正确 — 将选择器展示绑定到用户操作
@IBAction func addAccessoryTapped(_ sender: UIButton) {
    session.showPicker(for: items) { _ in }
}

DON'T: Reuse an invalidated session

不要复用已失效的会话

swift
// WRONG — session is dead after invalidation
session.showPicker(for: items) { _ in }  // No effect

// CORRECT — create a new session
let newSession = ASAccessorySession()
newSession.activate(on: .main) { event in
    // Handle events
}
swift
// 错误 — 失效后的会话无法正常工作
session.showPicker(for: items) { _ in }  // 无任何效果

// 正确 — 创建新的会话
let newSession = ASAccessorySession()
newSession.activate(on: .main) { event in
    // 处理事件
}

Review Checklist

审核清单

  • NSAccessorySetupSupports
    added to Info.plist with
    Bluetooth
    and/or
    WiFi
  • Bluetooth-specific plist keys (
    NSAccessorySetupBluetoothServices
    ,
    NSAccessorySetupBluetoothNames
    ,
    NSAccessorySetupBluetoothCompanyIdentifiers
    ) match descriptor values
  • Session activated before calling
    showPicker
  • Event handler uses
    [weak self]
    to avoid retain cycles
  • All
    ASAccessoryEventType
    cases handled, including
    @unknown default
  • Product images use transparent backgrounds and appropriate resolution
  • ssid
    and
    ssidPrefix
    are never set simultaneously on a descriptor
  • Picker presentation tied to explicit user action, not automatic
  • CBCentralManager
    not initialized until after migration completes (if migrating)
  • bluetoothIdentifier
    or
    ssid
    from
    ASAccessory
    used to connect post-setup
  • Invalidated sessions replaced with new instances
  • Accessory removal events handled to clean up app state
  • 已在Info.plist中添加
    NSAccessorySetupSupports
    ,包含
    Bluetooth
    和/或
    WiFi
  • 蓝牙专属plist键(
    NSAccessorySetupBluetoothServices
    NSAccessorySetupBluetoothNames
    NSAccessorySetupBluetoothCompanyIdentifiers
    )与描述符中的值匹配
  • 调用
    showPicker
    前已激活会话
  • 事件处理器使用
    [weak self]
    避免循环引用
  • 已处理所有
    ASAccessoryEventType
    case,包括
    @unknown default
  • 产品图使用透明背景,分辨率符合要求
  • 描述符上永远不会同时设置
    ssid
    ssidPrefix
  • 选择器展示绑定到用户明确操作,而非自动触发
  • (如果需要迁移)迁移完成前不会初始化
    CBCentralManager
  • 设置完成后使用
    ASAccessory
    bluetoothIdentifier
    ssid
    进行连接
  • 已失效的会话会被新实例替代
  • 已处理配件移除事件,清理应用状态

References

参考文档