Upgrade to Pro — share decks privately, control downloads, hide ads and more …

AccessorySetupKitで実現するシームレスなペアリング体験 / Seamless ...

Avatar for nekowen nekowen
September 19, 2025

AccessorySetupKitで実現するシームレスなペアリング体験 / Seamless pairing with AccessorySetupKit

Avatar for nekowen

nekowen

September 19, 2025
Tweet

More Decks by nekowen

Other Decks in Programming

Transcript

  1. Bluetooth Classic(BR/EDR/HS) Bluetooth Low Energy(BLE) ɾैདྷ͔Β͋Δඪ४తͳن֨ ɾ࠷େ 3Mbps ͷσʔλసૹ଎౓ ɾϖΞϦϯά͸ඞਢ

    ɾফඅిྗ͕ߴ͍ ɾBluetooth 4.0 ͔Βొ৔ ɾ࠷େ 1 - 2Mbps ͷσʔλసૹ଎౓ ɾϖΞϦϯά͸೚ҙ ɾফඅిྗ͕௿͍ Bluetooth ʹ͍ͭͯ
  2. AccessorySetupKit ͱ͸Կ͔ • iOS18 ͔Βొ৔ͨ͠ϑϨʔϜϫʔΫ • ϓϥΠόγʔΛอͬͨ·· Bluetooth/WiFi Λར༻ ͨ͠σόΠεͷݕग़ɾϖΞϦϯάॲཧΛ୲͏

    • ΞΫηαϦ͕௥Ճ͞Εͨޙ͸ɺCore Bluetooth ΁ ઀ଓॲཧͷҾ͖ܧ͕͗Մೳ ref: https://developer.apple.com/videos/play/wwdc2024/10203/
  3. ઀ଓϑϩʔ͕౷Ұ͞ΕΔ • σόΠεͱͷ઀ଓʹඞཁͳॲཧɾUI ΛϑϨʔϜϫʔΫ ଆ͕ఏڙ͢Δ • Bluetooth LE σόΠεͷεΩϟϯ •

    ݟ͔ͭͬͨσόΠεͷબ୒ • (PIN ίʔυ͕ඞཁͳ৔߹) PIN ίʔυͷೖྗ • ϖΞϦϯάɾϘϯσΟϯά
  4. ࣗ෼͸͜͜ʹ͍ΔΑʂ Service UUID: xxxx-xxxx Local Name: BLE-Demo Manufacture Speci f

    ic Data: … ΞυόλΠζͱ͸ ઀ଓର৅ͷσόΠεͩͳʂϤγʂ
  5. AccessorySetupKit Λ࢖ͬͨϖΞϦϯάखॱ 1. Info.plist ͷઃఆ 2. ASAccessorySession ͷ activate ϝιουͰΠϕϯτͷ

    ϋϯυϦϯάΛ࣮૷ 3. ઀ଓ͢ΔσόΠε৘ใΛ ASDiscoveryDescriptor ʹࢦఆ 4. showPicker ϝιουΛݺͼग़͢
  6. AccessorySetupKit Λ࢖ͬͨϖΞϦϯάखॱ 1. Info.plist ͷઃఆ 2. ASAccessorySession ͷ activate ϝιουͰΠϕϯτͷ

    ϋϯυϦϯάΛ࣮૷ 3. ઀ଓ͢ΔσόΠε৘ใΛ ASDiscoveryDescriptor ʹࢦఆ 4. showPicker ϝιουΛݺͼग़͢
  7. <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/

    PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>NSAccessorySetupKitSupports</key> <array> <string>Bluetooth</string> </array> <key>NSAccessorySetupBluetoothCompanyIdentifiers</key> <array> <string>XXXX</string> </array> <key>NSAccessorySetupBluetoothServices</key> <array> <string>181C</string> </array> <key>NSAccessorySetupBluetoothNames</key> <array> <string>BLE</string> </array> </dict> </plist>
  8. <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/

    PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>NSAccessorySetupKitSupports</key> <array> <string>Bluetooth</string> </array> <key>NSAccessorySetupBluetoothCompanyIdentifiers</key> <array> <string>XXXX</string> </array> <key>NSAccessorySetupBluetoothServices</key> <array> <string>181C</string> </array> <key>NSAccessorySetupBluetoothNames</key> <array> <string>BLE</string> </array> </dict> </plist>
  9. <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/

    PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>NSAccessorySetupKitSupports</key> <array> <string>Bluetooth</string> </array> <key>NSAccessorySetupBluetoothCompanyIdentifiers</key> <array> <string>XXXX</string> </array> <key>NSAccessorySetupBluetoothServices</key> <array> <string>181C</string> </array> <key>NSAccessorySetupBluetoothNames</key> <array> <string>BLE</string> </array> </dict> </plist> Bluetooth SIGͷϝϯόʔاۀʹׂΓ౰ͯΒΕΔݻ༗൪߸(೚ҙ)
  10. <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/

    PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>NSAccessorySetupKitSupports</key> <array> <string>Bluetooth</string> </array> <key>NSAccessorySetupBluetoothCompanyIdentifiers</key> <array> <string>XXXX</string> </array> <key>NSAccessorySetupBluetoothServices</key> <array> <string>181C</string> </array> <key>NSAccessorySetupBluetoothNames</key> <array> <string>BLE</string> </array> </dict> </plist> ΞυόλΠζ͞ΕΔService UUID(୹ॖUUID΋Մ)
  11. <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/

    PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>NSAccessorySetupKitSupports</key> <array> <string>Bluetooth</string> </array> <key>NSAccessorySetupBluetoothCompanyIdentifiers</key> <array> <string>XXXX</string> </array> <key>NSAccessorySetupBluetoothServices</key> <array> <string>181C</string> </array> <key>NSAccessorySetupBluetoothNames</key> <array> <string>BLE</string> </array> </dict> </plist> Bluetooth໊Λࢦఆ ෦෼Ұக
  12. AccessorySetupKit Λ࢖ͬͨϖΞϦϯάखॱ 1. Info.plist ͷઃఆ 2. ASAccessorySession ͷ activate ϝιουͰΠϕϯτͷ

    ϋϯυϦϯάΛ࣮૷ 3. ઀ଓ͢ΔσόΠε৘ใΛ ASDiscoveryDescriptor ʹࢦఆ 4. showPicker ϝιουΛݺͼग़͢
  13. // MARK: - Accessory Setup private let session = ASAccessorySession()

    private var currentAccessory: ASAccessory? // MARK: - AccessorySession session.activate(on: DispatchQueue.main) { [weak self] (event: ASAccessoryEvent) in guard let self else { return } switch event.eventType { case .activated: if let accessory = session.accessories.first { // ͢ͰʹηοτΞοϓࡁΈͰ͋Ε͹ɺͦͷΞΫηαϦΛ࢖ͬͯ઀ଓॲཧΛߦ͏ self.currentAccessory = accessory self.handleAccessoryAdded(accessory) } case .accessoryAdded: // ΞΫηαϦ͕௥Ճ͞Εͨ(ηοτΞοϓ͞Εͨ) self.currentAccessory = event.accessory case .pickerDidDismiss: // ϐοΧʔ͕ด͡ΒΕͨ guard let currentAccessory else { return } handleAccessoryAdded(currentAccessory) self.currentAccessory = nil case .pickerSetupFailed: // ԿΒ͔ͷཧ༝ʹΑΓηοτΞοϓʹࣦഊͨ͠(ύείʔυͷෆҰகͳͲ) ... } }
  14. // MARK: - Accessory Setup private let session = ASAccessorySession()

    private var currentAccessory: ASAccessory? // MARK: - AccessorySession session.activate(on: DispatchQueue.main) { [weak self] (event: ASAccessoryEvent) in guard let self else { return } switch event.eventType { case .activated: if let accessory = session.accessories.first { // ͢ͰʹηοτΞοϓࡁΈͰ͋Ε͹ɺͦͷΞΫηαϦΛ࢖ͬͯ઀ଓॲཧΛߦ͏ self.currentAccessory = accessory self.handleAccessoryAdded(accessory) } case .accessoryAdded: // ΞΫηαϦ͕௥Ճ͞Εͨ(ηοτΞοϓ͞Εͨ) self.currentAccessory = event.accessory case .pickerDidDismiss: // ϐοΧʔ͕ด͡ΒΕͨ guard let currentAccessory else { return } handleAccessoryAdded(currentAccessory) self.currentAccessory = nil case .pickerSetupFailed: // ԿΒ͔ͷཧ༝ʹΑΓηοτΞοϓʹࣦഊͨ͠(ύείʔυͷෆҰகͳͲ) ... } } activateϝιουΛݺΜͩ࣌ɺ࠷ॳʹݺ͹ΕΔ
  15. // MARK: - Accessory Setup private let session = ASAccessorySession()

    private var currentAccessory: ASAccessory? // MARK: - AccessorySession session.activate(on: DispatchQueue.main) { [weak self] (event: ASAccessoryEvent) in guard let self else { return } switch event.eventType { case .activated: if let accessory = session.accessories.first { // ͢ͰʹηοτΞοϓࡁΈͰ͋Ε͹ɺͦͷΞΫηαϦΛ࢖ͬͯ઀ଓॲཧΛߦ͏ self.currentAccessory = accessory self.handleAccessoryAdded(accessory) } case .accessoryAdded: // ΞΫηαϦ͕௥Ճ͞Εͨ(ηοτΞοϓ͞Εͨ) self.currentAccessory = event.accessory case .pickerDidDismiss: // ϐοΧʔ͕ด͡ΒΕͨ guard let currentAccessory else { return } handleAccessoryAdded(currentAccessory) self.currentAccessory = nil case .pickerSetupFailed: // ԿΒ͔ͷཧ༝ʹΑΓηοτΞοϓʹࣦഊͨ͠(ύείʔυͷෆҰகͳͲ) ... } } ΞΫηαϦ͕௥Ճ͞ΕͨΒݱࡏͷΞΫηαϦΛอ࣋ ʢ͜ͷ࣌·ͩγʔτ͸දࣔ͞Ε͍ͯΔ)
  16. // MARK: - Accessory Setup private let session = ASAccessorySession()

    private var currentAccessory: ASAccessory? // MARK: - AccessorySession session.activate(on: DispatchQueue.main) { [weak self] (event: ASAccessoryEvent) in guard let self else { return } switch event.eventType { case .activated: if let accessory = session.accessories.first { // ͢ͰʹηοτΞοϓࡁΈͰ͋Ε͹ɺͦͷΞΫηαϦΛ࢖ͬͯ઀ଓॲཧΛߦ͏ self.currentAccessory = accessory self.handleAccessoryAdded(accessory) } case .accessoryAdded: // ΞΫηαϦ͕௥Ճ͞Εͨ(ηοτΞοϓ͞Εͨ) self.currentAccessory = event.accessory case .pickerDidDismiss: // ϐοΧʔ͕ด͡ΒΕͨ guard let currentAccessory else { return } handleAccessoryAdded(currentAccessory) self.currentAccessory = nil case .pickerSetupFailed: // ԿΒ͔ͷཧ༝ʹΑΓηοτΞοϓʹࣦഊͨ͠(ύείʔυͷෆҰகͳͲ) ... } } γʔτ͕ด͡ΒΕͨͷͰ઀ଓॲཧΛߦ͏
  17. // MARK: - CoreBluetooth private var central: CBCentralManager? // AccessorySetupKit

    ͰΞΫηαϦ͕௥Ճ͞Εͨ࣌ʹݺ͹ΕΔϝιου private func handleAccessoryAdded(_ accessory: ASAccessory) { // Bluetooth IDΛऔಘ guard let bluetoothIdentifier = accessory.bluetoothIdentifier else { return } // CBCentralManager͕ະॳظԽͳΒॳظԽ͢Δ if central == nil { central = CBCentralManager(delegate: self, queue: nil) } // Bluetooth ID͔ΒCBPeripheralΛऔಘ͢Δ let peripherals = central?.retrievePeripherals(withIdentifiers: [bluetoothIdentifier]) // ֘౰ͷPeripheral͕ଘࡏ͍ͯ͠Ε͹઀ଓ͢Δ if let peripheral = peripherals?.first { bleConnect(peripheral) } }
  18. // MARK: - CoreBluetooth private var central: CBCentralManager? // AccessorySetupKit

    ͰΞΫηαϦ͕௥Ճ͞Εͨ࣌ʹݺ͹ΕΔϝιου private func handleAccessoryAdded(_ accessory: ASAccessory) { // Bluetooth IDΛऔಘ guard let bluetoothIdentifier = accessory.bluetoothIdentifier else { return } // CBCentralManager͕ະॳظԽͳΒॳظԽ͢Δ if central == nil { central = CBCentralManager(delegate: self, queue: nil) } // Bluetooth ID͔ΒCBPeripheralΛऔಘ͢Δ let peripherals = central?.retrievePeripherals(withIdentifiers: [bluetoothIdentifier]) // ֘౰ͷPeripheral͕ଘࡏ͍ͯ͠Ε͹઀ଓ͢Δ if let peripheral = peripherals?.first { bleConnect(peripheral) } }
  19. // MARK: - CoreBluetooth private var central: CBCentralManager? // AccessorySetupKit

    ͰΞΫηαϦ͕௥Ճ͞Εͨ࣌ʹݺ͹ΕΔϝιου private func handleAccessoryAdded(_ accessory: ASAccessory) { // Bluetooth IDΛऔಘ guard let bluetoothIdentifier = accessory.bluetoothIdentifier else { return } // CBCentralManager͕ະॳظԽͳΒॳظԽ͢Δ if central == nil { central = CBCentralManager(delegate: self, queue: nil) } // Bluetooth ID͔ΒCBPeripheralΛऔಘ͢Δ let peripherals = central?.retrievePeripherals(withIdentifiers: [bluetoothIdentifier]) // ֘౰ͷPeripheral͕ଘࡏ͍ͯ͠Ε͹઀ଓ͢Δ if let peripheral = peripherals?.first { bleConnect(peripheral) } }
  20. AccessorySetupKit Λ࢖ͬͨϖΞϦϯάखॱ 1. Info.plist ͷઃఆ 2. ASAccessorySession ͷ activate ϝιουͰΠϕϯτͷ

    ϋϯυϦϯάΛ࣮૷ 3. ઀ଓ͢ΔσόΠε৘ใΛ ASDiscoveryDescriptor ʹࢦఆ 4. showPicker ϝιουΛݺͼग़͢
  21. let descriptor = ASDiscoveryDescriptor() // Company IdentifierΛࢦఆ descriptor.bluetoothCompanyIdentifier = .init(0xFC94)

    // Advertising Service UUID Λࢦఆ descriptor.bluetoothServiceUUID = .init(string: "181C") // Local Name Λࢦఆ descriptor.bluetoothNameSubstring = "BLE" // ରԠ͢ΔΦϓγϣϯΛࢦఆ descriptor.supportedOptions = [.bluetoothPairingLE, .bluetoothHID]
  22. let descriptor = ASDiscoveryDescriptor() // Company IdentifierΛࢦఆ descriptor.bluetoothCompanyIdentifier = .init(0xFC94)

    // Advertising Service UUID Λࢦఆ descriptor.bluetoothServiceUUID = .init(string: "181C") // Local Name Λࢦఆ descriptor.bluetoothNameSubstring = "BLE" // ରԠ͢ΔΦϓγϣϯΛࢦఆ descriptor.supportedOptions = [.bluetoothPairingLE, .bluetoothHID] bluetoothCompanyIdenti f ier or bluetoothServiceUUID ͲͪΒ͔͸ࢦఆ͕ඞਢ(ࢦఆ͠ͳ͍ͱΫϥογϡ͢Δ)
  23. let descriptor = ASDiscoveryDescriptor() // Company IdentifierΛࢦఆ descriptor.bluetoothCompanyIdentifier = .init(0xFC94)

    // Advertising Service UUID Λࢦఆ descriptor.bluetoothServiceUUID = .init(string: "1812") // Local Name Λࢦఆ descriptor.bluetoothNameSubstring = "M5" // ରԠ͢ΔΦϓγϣϯΛࢦఆ descriptor.supportedOptions = [.bluetoothPairingLE, .bluetoothHID] ϖΞϦϯά͕ඞཁͳσόΠεͰ͋Ε͹ɺbluetoothPairingLEΛࢦఆ
  24. let descriptor = ASDiscoveryDescriptor() // Company IdentifierΛࢦఆ descriptor.bluetoothCompanyIdentifier = .init(0xFC94)

    // Advertising Service UUID Λࢦఆ descriptor.bluetoothServiceUUID = .init(string: "1812") // Local Name Λࢦఆ descriptor.bluetoothNameSubstring = "M5" // ରԠ͢ΔΦϓγϣϯΛࢦఆ descriptor.supportedOptions = [.bluetoothPairingLE, .bluetoothHID] σόΠε͕Bluetooth LE HID(ΩʔϘʔυ΍Ϛ΢εೖྗ)ʹ ରԠ͍ͯ͠Ε͹ bluetoothHID ΋ඞཁ
  25. let displayName = “BLE-Demo" let productImage = UIImage(named: “ble-device-image")! let

    displayItem = ASPickerDisplayItem( name: displayName, productImage: productImage, descriptor: descriptor ) try await session.showPicker(for: [displayItem])
  26. let displayName = “BLE-Demo" let productImage = UIImage(named: “ble-device-image")! let

    displayItem = ASPickerDisplayItem( name: displayName, productImage: productImage, descriptor: descriptor ) try await session.showPicker(for: [displayItem]) ϐοΧʔʹදࣔ͢Δ੡඼໊ͱ੡඼ͷΠϝʔδը૾Λࢦఆ લ߲Ͱઃఆͨ͠ASDiscoveryDescriptor΋౉͢
  27. let displayName = “BLE-Demo” let productImage = UIImage(named: “ble-device-image")! let

    displayItem = ASPickerDisplayItem( name: displayName, productImage: productImage, descriptor: descriptor ) try await session.showPicker(for: [displayItem]) ASAccessorySession ͷ showPicker ϝιουΛݺͼग़͢
  28. extension BluetoothManager: CBCentralManagerDelegate { func centralManagerDidUpdateState(_ central: CBCentralManager) { switch

    central.state { case .poweredOn: // Bluetooth ͕Φϯɺ͔ͭ࢖༻ڐՄ͞Ε͍ͯΔ৔߹ case .poweredOff: // Bluetooth ͕Φϑɺ΋͘͠͸ݖݶະ֬ೝͷ৔߹ case .unauthorized: // Bluetooth ͷ࢖༻ڐՄΛڋ൱ͨ͠৔߹ … } } }
  29. extension BluetoothManager: CBCentralManagerDelegate { func centralManagerDidUpdateState(_ central: CBCentralManager) { switch

    central.state { case .poweredOn: // Bluetooth ͕Φϯɺ͔ͭ࢖༻ڐՄ͞Ε͍ͯΔ৔߹ case .poweredOff: // Bluetooth ͕Φϑɺ΋͘͠͸ݖݶະ֬ೝͷ৔߹ case .unauthorized: // Bluetooth ͷ࢖༻ڐՄΛڋ൱ͨ͠৔߹ … } } } AccessorySetupKit ͰηοτΞοϓࡁΈͷσόΠε͕͋Ε͹ poweredOn ͕ฦΔ
  30. extension BluetoothManager: CBCentralManagerDelegate { func centralManagerDidUpdateState(_ central: CBCentralManager) { switch

    central.state { case .poweredOn: // Bluetooth ͕Φϯɺ͔ͭ࢖༻ڐՄ͞Ε͍ͯΔ৔߹ case .poweredOff: // Bluetooth ͕Φϑɺ΋͘͠͸ݖݶະ֬ೝͷ৔߹ case .unauthorized: // Bluetooth ͷ࢖༻ڐՄΛڋ൱ͨ͠৔߹ … } } } ηοτΞοϓࡁΈͷσόΠε͕࡟আ͞ΕΔͱ poweredO ff ͕ฦΔ
  31. func migratePairedDevice() async throws { let descriptor = ASDiscoveryDescriptor() descriptor.supportedOptions

    = [.bluetoothPairingLE, .bluetoothHID] let displayName = “BLE-Demo" let productImage = UIImage(named: "ble-device-image")! let displayItem = ASMigrationDisplayItem( name: displayName, productImage: productImage, descriptor: descriptor ) // AccessorySetupKit ʹҠߦ͍ͨ͠ Peripheral Bluetooth Identifier Λࢦఆ͢Δ let bluetoothIdentifier = UUID(uuidString: "XXXXXXX-5CBD-9BFC-BC4C-4BBDE6436185") displayItem.peripheralIdentifier = bluetoothIdentifier try await session.showPicker(for: [displayItem]) }
  32. func migratePairedDevice() async throws { let descriptor = ASDiscoveryDescriptor() descriptor.supportedOptions

    = [.bluetoothPairingLE, .bluetoothHID] let displayName = “BLE-Demo" let productImage = UIImage(named: "ble-device-image")! let displayItem = ASMigrationDisplayItem( name: displayName, productImage: productImage, descriptor: descriptor ) // AccessorySetupKit ʹҠߦ͍ͨ͠ Peripheral Bluetooth Identifier Λࢦఆ͢Δ let bluetoothIdentifier = UUID(uuidString: "XXXXXXX-5CBD-9BFC-BC4C-4BBDE6436185") displayItem.peripheralIdentifier = bluetoothIdentifier try await session.showPicker(for: [displayItem]) } peripheralIdenti f ier ʹ ϖΞϦϯάࡁΈBluetoothσόΠεͷUUIDΛ౉͓ͯ͘͠
  33. ·ͱΊ • AccessorySetupKit Λར༻͢Δ͜ͱͰɺBluetooth LE σόΠεͱͷϖΞϦϯ ά΍ηοτΞοϓϑϩʔΛҰମԽ͞ΕͨϢʔβʔମݧͰఏڙͰ͖Δ • ηοτΞοϓ͞Εͨ Bluetooth

    LE σόΠεΛʮΞΫηαϦʯͱͯ͠ iOS ্Ͱ ؅ཧͰ͖ΔΑ͏ʹͳΔ • ϖΞϦϯάࡁΈͷσόΠεΛҠߦ͢ΔAPI͕͋ΓɺطଘΞϓϦʹ΋ద༻Մೳ
  34. ࢀߟ • WWDC24: Meet AccessorySetupKit • https://developer.apple.com/videos/play/wwdc2024/10203/ • AccessorySetupKit |

    Apple Developer Documentation • https://developer.apple.com/documentation/AccessorySetupKit • iOS 18 AccessorySetupKit: Everything BLE Developers Need To Know • https://punchthrough.com/ios18-accessorysetupkit-everything-ble- developers-need-to-know/