accessorysetupkit について
iOS 18 以降を対象とする開発者向けの AccessorySetupKit 統合ガイドです。システムピッカーによる Bluetooth・Wi-Fi アクセサリの検出と認証に加え、セットアップ後の通信や CoreBluetooth からの移行を説明します。
主な機能
- 01Bluetooth・Wi-Fi アクセサリの検出
- 02システムピッカーと認証設定
- 03セッションイベントの処理
- 04ペアリング後の接続
- 05既存アクセサリの移行
活用シーン
- 01アプリ内でアクセサリの検出と認証を案内
- 02CoreBluetooth でペアリング済み Bluetooth 機器に接続
- 03NetworkExtension でアクセサリの Wi-Fi に接続
- 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 以降のシステムピッカーを使用して、プライバシーを保護しながら Bluetooth/Wi-Fi アクセサリを検出して認証し、その後、通信を CoreBluetooth または NetworkExtension に引き継ぎます。
目次
- セットアップとエンタイトルメント
- 検出ディスクリプター
- ピッカーの表示
- イベント処理
- Bluetooth アクセサリ
- Wi-Fi アクセサリ
- CoreBluetooth からの移行
- よくある間違い
- レビュー用チェックリスト
- 参考資料
セットアップとエンタイトルメント
Info.plist の設定
アプリの Info.plist に次のキーを追加します。
| キー | 型 | 用途 |
|---|---|---|
NSAccessorySetupSupports | [String] | 必須です。Bluetooth および/または WiFi を含む配列です |
NSAccessorySetupBluetoothServices | [String] | アプリが検出するサービス UUID(Bluetooth) |
NSAccessorySetupBluetoothNames | [String] | 照合する Bluetooth 名または部分文字列 |
NSAccessorySetupBluetoothCompanyIdentifiers | [String] | 2 バイトの Bluetooth 企業識別子 |
Bluetooth 固有のキーは、ASDiscoveryDescriptor で使用する値と一致させる必要があります。
Info.plist で宣言されていない識別子、名前、またはサービスをアプリが使用すると、
AccessorySetupKit による検出時にアプリがクラッシュします。Wi-Fi アクセサリの場合は、
NSAccessorySetupSupports に WiFi を含め、ディスクリプターの SSID ルールと一致させてください。
Bluetooth の許可は不要
アプリが NSAccessorySetupSupports に Bluetooth を指定すると、
CBCentralManager を作成してもシステムの Bluetooth 許可ダイアログは表示されなくなります。
AccessorySetupKit を介して少なくとも 1 つのアクセサリをペアリングした場合にのみ、
セントラルマネージャーの状態は poweredOn に遷移します。
検出ディスクリプター
ASDiscoveryDescriptor は、アクセサリを検索するための照合条件を定義します。
システムはスキャン結果をディスクリプター内のすべてのルールと照合し、
対象のアクセサリに絞り込みます。
Bluetooth ディスクリプター
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
Bluetooth ディスクリプターには、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 は空でない長さである必要があります。
Bluetooth の通信範囲
検出に必要な物理的な近さを設定します。
| 値 | 動作 |
|---|---|
.default | 標準的な Bluetooth の通信範囲 |
.immediate | 物理的に近い場所にあるアクセサリのみ |
サポートオプション
ディスクリプターの supportedOptions を設定して、アクセサリの機能を宣言します。
descriptor.supportedOptions = [.bluetoothPairingLE, .bluetoothTransportBridging]
| オプション | 用途 |
|---|---|
.bluetoothPairingLE | BLE ペアリングのサポート |
.bluetoothTransportBridging | Bluetooth トランスポートブリッジング |
.bluetoothHID | Bluetooth 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 | Bluetoothのペアリング中 |
.pickerSetupFailed | セットアップに失敗したとき |
.pickerSetupRename | ユーザーがアクセサリの名前を変更しているとき |
.accessoryDiscovered | 新しいアクセサリが見つかったとき(カスタムフィルタリングモード) |
ピッカーを閉じるタイミングの調整
ユーザーがアクセサリを選択すると、.accessoryAddedは.pickerDidDismissより先に発生します。ピッカーが閉じた後にカスタムのセットアップUIを表示するには、最初のイベントでアクセサリを保存し、閉じた後に処理します。
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
}
}
Bluetoothアクセサリ
ピッカーからアクセサリを追加した後、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にない | ピッカーを表示する前に、すべてのBluetoothサービス、名前、会社識別子を宣言します。 |
ssidとssidPrefixの両方が設定されている | 一致方法を必ずどちらか一方だけ選びます。 |
| CoreBluetoothが移行完了前に開始される | .migrationCompleteを待ってから、CBCentralManagerを作成します。 |
| ユーザーの明示的な操作なしにピッカーが表示される | ユーザーの操作からのみピッカーを表示します。 |
| 無効になったセッションが再利用される | 新しいASAccessorySessionを作成して有効化し、保持します。 |
レビューチェックリスト
-
NSAccessorySetupSupportsを Info.plist に追加し、Bluetoothおよび/またはWiFiを指定している -
showPickerを呼び出す前にセッションを有効化している - イベントハンドラーで
[weak self]を使用し、保持サイクルを回避している -
@unknown defaultを含め、すべてのASAccessoryEventTypeケースを処理している - 製品画像に透明な背景を使用し、適切な解像度にしている
- セットアップ後の接続に
ASAccessoryのbluetoothIdentifierまたはssidを使用している - アプリの状態をクリーンアップできるよう、アクセサリの削除イベントを処理している