mcpField
A
accessorysetupkit

accessorysetupkit

github.com/dpearson2699开发1.2k3.1k 次安装13 周前更新
代码仓库

关于 accessorysetupkit

面向 iOS 18 及以上版本开发者的 AccessorySetupKit 集成指南,介绍如何通过系统选择器发现并授权蓝牙或 Wi-Fi 配件。内容还涵盖配对后的通信、事件处理及从 CoreBluetooth 迁移。

主要功能

  1. 01蓝牙与 Wi-Fi 配件发现
  2. 02系统选择器与授权设置
  3. 03会话事件处理
  4. 04配对后连接配件
  5. 05迁移现有配件

适用场景

  1. 01在应用中引导用户发现并授权配件
  2. 02通过 CoreBluetooth 连接已配对的蓝牙设备
  3. 03使用 NetworkExtension 加入配件 Wi-Fi 网络
  4. 04将现有 CoreBluetooth 配件迁移至新流程

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/dpearson2699/swift-ios-skills --skill accessorysetupkit

安装后在 Claude Code 中输入/accessorysetupkit确认已加载。

附带文件evals/evals.jsonreferences/accessorysetupkit-patterns.md

详细介绍

AccessorySetupKit

使用 iOS 18+ 系统选择器,以保护隐私的方式发现并授权蓝牙/Wi-Fi 配件,然后将通信交由 CoreBluetooth 或 NetworkExtension 处理。

目录

设置和 Entitlements

Info.plist 配置

将以下键添加到应用的 Info.plist 中:

键类型用途
NSAccessorySetupSupports[String]必需。包含 Bluetooth 和/或 WiFi 的数组
NSAccessorySetupBluetoothServices[String]应用发现的服务 UUID(蓝牙)
NSAccessorySetupBluetoothNames[String]用于匹配的蓝牙名称或子字符串
NSAccessorySetupBluetoothCompanyIdentifiers[String]两字节蓝牙公司标识符

蓝牙专用键必须与 ASDiscoveryDescriptor 中使用的值匹配。 如果应用使用的标识符、名称或服务未在 Info.plist 中声明,应用会在 AccessorySetupKit 发现期间崩溃。对于 Wi-Fi 配件,请在 NSAccessorySetupSupports 中包含 WiFi,并确保描述符的 SSID 规则相匹配。

无需蓝牙权限

当应用使用 Bluetooth 声明 NSAccessorySetupSupports 时,创建 CBCentralManager 不再触发系统蓝牙权限对话框。只有当应用通过 AccessorySetupKit 至少配对了一个配件时,中心管理器的状态才会变为 poweredOn。

发现描述符

ASDiscoveryDescriptor 定义用于查找配件的匹配条件。 系统会将扫描结果与描述符中的所有规则进行匹配,以筛选目标配件。

蓝牙描述符
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

蓝牙描述符至少需要设置 bluetoothCompanyIdentifier 或 bluetoothServiceUUID 之一。可按需添加更精确的匹配条件:

  • 将 bluetoothNameSubstring 与公司标识符或服务 UUID 配合使用
  • 将 bluetoothManufacturerDataBlob 和 bluetoothManufacturerDataMask 与公司标识符配合使用;blob 和 mask 的长度必须相同
  • 将 bluetoothServiceDataBlob 和 bluetoothServiceDataMask 与服务 UUID 配合使用; blob 和 mask 的长度必须相同
Wi-Fi 描述符
var descriptor = ASDiscoveryDescriptor()
descriptor.ssid = "MyAccessory-Network"
// OR use a prefix:
// descriptor.ssidPrefix = "MyAccessory-"

请提供 ssid 或 ssidPrefix,不能同时提供两者。如果两者都设置,应用会崩溃。 ssidPrefix 的长度必须大于零。

蓝牙范围

控制发现所需的物理距离:

值行为
.default标准蓝牙范围
.immediate仅发现近距离内的配件
支持选项

在描述符上设置 supportedOptions,以声明配件的功能:

descriptor.supportedOptions = [.bluetoothPairingLE, .bluetoothTransportBridging]
选项用途
.bluetoothPairingLE支持 BLE 配对
.bluetoothTransportBridging蓝牙传输桥接
.bluetoothHID蓝牙 HID 设备

显示选择器

创建会话

创建并激活 ASAccessorySession,以管理发现生命周期。在读取 session.accessories 或显示选择器之前,请等待 .activated:

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
        @unknown default:
            break
        }
    }
}
显示选择器

创建包含名称、产品图片和发现描述符的 ASPickerDisplayItem 实例,然后将其传递给已激活的会话:

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)")
        }
    }
}

选择器在独立的系统进程中运行。它会将每个匹配设备显示为单独的条目。当多个设备与某个描述符匹配时,选择器会创建水平轮播列表。

设置选项

为每个显示项配置选择器行为:

var item = ASPickerDisplayItem(
    name: "My Accessory",
    productImage: image,
    descriptor: descriptor
)
item.setupOptions = [.rename, .confirmAuthorization]
选项效果
.rename允许在设置期间重命名配件
.confirmAuthorization在设置前显示授权确认
.finishInApp表示配对后将在应用中继续设置
产品图片

选择器会在 180x120 点的容器中显示图片。最佳实践:

  • 为所有屏幕缩放系数使用高分辨率图片
  • 使用透明背景,以确保在浅色和深色模式下正确显示
  • 调整透明边框作为内边距,以控制配件的视觉尺寸
  • 在浅色和深色模式下都进行测试

事件处理

事件类型

会话通过事件处理程序传递 ASAccessoryEvent 对象:

事件时机
.activated会话已激活,可查询 session.accessories
.accessoryAdded用户在选择器中选中了配件
.accessoryChanged配件属性已更新(例如,名称已更改)
.accessoryRemoved配件已从系统中移除
.invalidated会话已失效,需创建新会话
.migrationComplete旧版配件迁移已完成
.pickerDidPresent选择器已显示在屏幕上
.pickerDidDismiss选择器已关闭
.pickerSetupBridging正在设置传输桥接
.pickerSetupPairing正在进行蓝牙配对
.pickerSetupFailed设置失败
.pickerSetupRename用户正在重命名配件
.accessoryDiscovered找到新配件(自定义筛选模式)
协调选择器关闭

用户选择配件时,.accessoryAdded 会先于 .pickerDidDismiss 触发。若要在选择器关闭后显示自定义设置界面,请在第一个事件中保存配件,并在选择器关闭后处理:

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)
        }
    @unknown default:
        break
    }
}

蓝牙配件

通过选择器添加配件后,可使用 CoreBluetooth 进行通信。 ASAccessory 上的 bluetoothIdentifier 对应一个 CBPeripheral。

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)
}

要点:

  • 仅当应用拥有已配对配件时,CBCentralManager 状态才会变为 .poweredOn
  • 使用 scanForPeripherals(withServices:) 扫描时,仅会返回通过 AccessorySetupKit 配对的配件
  • 仅使用 AccessorySetupKit 时,无需设置 NSBluetoothAlwaysUsageDescription

Wi-Fi 配件

对于 Wi-Fi 配件,ASAccessory 上的 ssid 用于标识网络。 使用 NetworkExtension 中的 NEHotspotConfiguration 加入网络:

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)")
        }
    }
}

由于该配件是通过 AccessorySetupKit 发现的,加入网络时不会触发标准的 Wi-Fi 权限提示。

从 CoreBluetooth 迁移

拥有现有 CoreBluetooth 授权配件的应用,可以使用 ASMigrationDisplayItem 将其迁移到 AccessorySetupKit。这是一次性操作,会在新系统中注册已知配件。

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)")
        }
    }
}

迁移规则:

  • 如果 showPicker 仅包含迁移项,系统会显示信息页面,而不是发现选择器
  • 如果迁移项与常规显示项混合,只有在发现并设置新配件时才会进行迁移
  • 迁移完成前不要初始化 CBCentralManager,否则会导致错误,且选择器无法显示
  • 迁移完成后,会话会收到 .migrationComplete

常见错误

错误修正方法
Info.plist 中缺少描述符标识符显示选择器前,声明所有蓝牙服务、名称和公司标识符。
同时设置了 ssid 和 ssidPrefix选择一种匹配策略。
CoreBluetooth 在迁移完成前启动等待 .migrationComplete,然后创建 CBCentralManager。
选择器在没有明确用户意图时显示仅在用户执行操作时显示。
重用了已失效的会话创建、激活并保留新的 ASAccessorySession。

审查清单

  • 在 Info.plist 中添加 NSAccessorySetupSupports,并设置 Bluetooth 和/或 WiFi
  • 调用 showPicker 前激活会话
  • 事件处理程序使用 [weak self],避免循环引用
  • 处理所有 ASAccessoryEventType 情况,包括 @unknown default
  • 产品图片使用透明背景和适当的分辨率
  • 使用 ASAccessory 中的 bluetoothIdentifier 或 ssid 在设置后连接
  • 处理配件移除事件,以清理应用状态

参考资料

评论

支持 Markdown