🌐 KO

πŸ”— AccessorySetupKit 2

⭐ Difficulty: ⭐⭐⭐ ⏱️ Est. Time: 2h πŸ“‚ iOS 26

Bluetooth & Wi-Fi Accessory Easy Pairing

iOS 18+πŸ†• 2024

✨ What is AccessorySetupKit 2?

AccessorySetupKit 2 is a greatly improved accessory setup framework in iOS 18 that lets you connect Bluetooth and Wi-Fi devices easily without complex configuration. QR code scanning, Matter protocol support, and HomeKit integration have been enhanced.

πŸ’‘ Key Features: Easy Pairing Β· QR Code Authentication Β· Matter Support Β· Wi-Fi Provisioning Β· HomeKit Integration Β· Secure Connection Β· Auto Reconnect

🎯 1. Basic Setup

Create an AccessorySetupKit session and discover accessories.

AccessoryManager.swift β€” Basic Setup
import AccessorySetupKit
import SwiftUI

@Observable
class AccessorySetupManager {
    var discoveredAccessories: [ASKAccessory] = []
    var connectedAccessory: ASKAccessory?
    private var session: ASKSession?

    // μ„Έμ…˜ μ‹œμž‘
    func startSession() async {
        // πŸ†• iOS 18+ μƒˆλ‘œμš΄ μ„Έμ…˜ 생성 방식
        let configuration = ASKSessionConfiguration()
        configuration.supportedProtocols = [.bluetooth, .wifi]
        configuration.enableBackgroundRefresh = true

        session = ASKSession(configuration: configuration)

        // μ•‘μ„Έμ„œλ¦¬ 검색 μ‹œμž‘
        do {
            for try await accessory in session!.discoverAccessories() {
                discoveredAccessories.append(accessory)
            }
        } catch {
            print("검색 μ‹€νŒ¨: \(error)")
        }
    }

    // μ•‘μ„Έμ„œλ¦¬ νŽ˜μ–΄λ§
    func pair(accessory: ASKAccessory) async throws {
        guard let session else { return }

        // μ‹œμŠ€ν…œ νŽ˜μ–΄λ§ UI ν‘œμ‹œ
        try await session.pair(accessory)
        connectedAccessory = accessory
    }

    // μ—°κ²° ν•΄μ œ
    func disconnect() async {
        guard let accessory = connectedAccessory,
              let session else { return }

        await session.disconnect(accessory)
        connectedAccessory = nil
    }

    // μ„Έμ…˜ μ’…λ£Œ
    func endSession() {
        session?.invalidate()
        session = nil
        discoveredAccessories.removeAll()
    }
}

πŸ“± 2. QR Code Pairing

Scan QR codes to quickly connect accessories.

QRPairing.swift β€” QR Code Pairing
import AccessorySetupKit
import AVFoundation

@Observable
class QRPairingManager: NSObject {
    var scannedAccessory: ASKAccessory?
    private var session: ASKSession?

    // QR μ½”λ“œ μŠ€μΊ” 및 νŽ˜μ–΄λ§
    func scanAndPair() async throws {
        let configuration = ASKSessionConfiguration()
        configuration.pairingMethod = .qrCode // πŸ†• QR μ½”λ“œ λͺ¨λ“œ

        session = ASKSession(configuration: configuration)

        // μ‹œμŠ€ν…œ QR μŠ€μΊλ„ˆ ν‘œμ‹œ
        if let accessory = try await session?.scanQRCode() {
            scannedAccessory = accessory
            try await session?.pair(accessory)
        }
    }

    // Matter λ””λ°”μ΄μŠ€ QR μ½”λ“œ νŽ˜μ–΄λ§
    func pairMatterDevice(qrCode: String) async throws {
        let configuration = ASKSessionConfiguration()
        configuration.supportedProtocols = [.matter] // πŸ†• Matter 지원

        session = ASKSession(configuration: configuration)

        // QR μ½”λ“œμ—μ„œ λ””λ°”μ΄μŠ€ 정보 νŒŒμ‹±
        let accessory = try await session?.parseQRCode(qrCode)
        try await session?.pair(accessory!)
    }
}

πŸ“‘ 3. Wi-Fi Provisioning

Deliver Wi-Fi credentials to accessories for network connection.

WiFiProvisioning.swift β€” Wi-Fi Setup
import AccessorySetupKit
import NetworkExtension

@Observable
class WiFiProvisioningManager {
    private var session: ASKSession?
    var provisioningStatus: String = ""

    // Wi-Fi ν”„λ‘œλΉ„μ €λ‹ μ‹œμž‘
    func provisionWiFi(
        accessory: ASKAccessory,
        ssid: String,
        password: String
    ) async throws {
        let configuration = ASKSessionConfiguration()
        configuration.supportedProtocols = [.wifi]

        session = ASKSession(configuration: configuration)

        // πŸ†• Wi-Fi 자격 증λͺ… 전달
        let credentials = ASKWiFiCredentials(
            ssid: ssid,
            password: password,
            securityType: .wpa2
        )

        provisioningStatus = "μ—°κ²° 쀑..."

        try await session?.provisionWiFi(
            for: accessory,
            credentials: credentials
        )

        provisioningStatus = "μ—°κ²° μ™„λ£Œ"
    }

    // ν˜„μž¬ λ„€νŠΈμ›Œν¬ 정보 μžλ™ 전달
    func provisionCurrentNetwork(accessory: ASKAccessory) async throws {
        let configuration = ASKSessionConfiguration()
        configuration.supportedProtocols = [.wifi]
        configuration.autoProvisionCurrentNetwork = true // πŸ†• μžλ™ μ„€μ •

        session = ASKSession(configuration: configuration)

        // μ‹œμŠ€ν…œμ΄ μžλ™μœΌλ‘œ ν˜„μž¬ Wi-Fi 정보 전달
        try await session?.pair(accessory)
    }

    // Wi-Fi μ„€μ • μƒνƒœ λͺ¨λ‹ˆν„°λ§
    func monitorProvisioningStatus(
        accessory: ASKAccessory
    ) async {
        guard let session else { return }

        for await status in session.provisioningStatusUpdates(for: accessory) {
            switch status {
            case .connecting:
                provisioningStatus = "λ„€νŠΈμ›Œν¬ μ—°κ²° 쀑"
            case .connected:
                provisioningStatus = "μ—°κ²° μ™„λ£Œ"
            case .failed(let error):
                provisioningStatus = "μ—°κ²° μ‹€νŒ¨: \(error.localizedDescription)"
            }
        }
    }
}

🏠 4. HomeKit Integration

Set up smart home accessories with HomeKit integration.

HomeKitIntegration.swift β€” HomeKit Integration
import AccessorySetupKit
import HomeKit

@Observable
class HomeKitAccessoryManager: NSObject, HMHomeManagerDelegate {
    private let homeManager = HMHomeManager()
    private var session: ASKSession?
    var homes: [HMHome] = []

    override init() {
        super.init()
        homeManager.delegate = self
    }

    // HomeKit μ•‘μ„Έμ„œλ¦¬ μΆ”κ°€
    func addHomeKitAccessory(
        to home: HMHome,
        accessory: ASKAccessory
    ) async throws {
        let configuration = ASKSessionConfiguration()
        configuration.supportedProtocols = [.homeKit, .matter] // πŸ†• HomeKit + Matter
        configuration.homeKitHome = home

        session = ASKSession(configuration: configuration)

        // νŽ˜μ–΄λ§ 및 HomeKit μΆ”κ°€
        try await session?.pair(accessory)

        // HomeKit μ„€μ • μžλ™ μ™„λ£Œ
        try await session?.configureHomeKit(for: accessory, in: home)
    }

    // Matter λ””λ°”μ΄μŠ€λ₯Ό HomeKit에 μΆ”κ°€
    func addMatterDevice(
        qrCode: String,
        to home: HMHome
    ) async throws {
        let configuration = ASKSessionConfiguration()
        configuration.supportedProtocols = [.matter]
        configuration.homeKitHome = home
        configuration.pairingMethod = .qrCode

        session = ASKSession(configuration: configuration)

        // QR μ½”λ“œλ‘œ Matter λ””λ°”μ΄μŠ€ νŽ˜μ–΄λ§
        if let accessory = try await session?.parseQRCode(qrCode) {
            try await session?.pair(accessory)
            try await session?.configureHomeKit(for: accessory, in: home)
        }
    }

    // HomeManager 델리게이트
    func homeManagerDidUpdateHomes(_ manager: HMHomeManager) {
        homes = manager.homes
    }
}

πŸ” 5. Secure Pairing

Implement secure pairing and permission management.

SecurePairing.swift β€” Secure Pairing
import AccessorySetupKit
import CryptoKit

@Observable
class SecurePairingManager {
    private var session: ASKSession?
    var authenticationStatus: String = ""

    // 인증 μ½”λ“œ 기반 νŽ˜μ–΄λ§
    func pairWithAuthCode(
        accessory: ASKAccessory,
        code: String
    ) async throws {
        let configuration = ASKSessionConfiguration()
        configuration.requiresAuthentication = true // πŸ†• 인증 ν•„μˆ˜

        session = ASKSession(configuration: configuration)

        // 인증 μ½”λ“œ 검증
        let isValid = try await session?.validateAuthCode(code, for: accessory)

        if isValid == true {
            authenticationStatus = "인증 성곡"
            try await session?.pair(accessory)
        } else {
            authenticationStatus = "인증 μ‹€νŒ¨"
            throw PairingError.authenticationFailed
        }
    }

    // PIN 기반 νŽ˜μ–΄λ§
    func pairWithPIN(
        accessory: ASKAccessory,
        pin: String
    ) async throws {
        let configuration = ASKSessionConfiguration()
        configuration.pairingMethod = .pin // πŸ†• PIN λͺ¨λ“œ

        session = ASKSession(configuration: configuration)

        // PIN 제곡 및 νŽ˜μ–΄λ§
        try await session?.pair(accessory, pin: pin)
    }

    // μ•”ν˜Έν™”λœ μ—°κ²° 확인
    func verifyEncryptedConnection(accessory: ASKAccessory) async -> Bool {
        guard let session else { return false }

        // μ—°κ²° λ³΄μ•ˆ μƒνƒœ 확인
        let securityLevel = await session.securityLevel(for: accessory)
        return securityLevel == .encrypted
    }
}

enum PairingError: Error {
    case authenticationFailed
    case invalidPIN
    case connectionLost
}

πŸ“± SwiftUI Integration

AccessorySetupView.swift β€” Complete Example
import SwiftUI
import AccessorySetupKit

struct AccessorySetupView: View {
    @State private var manager = AccessorySetupManager()
    @State private var qrManager = QRPairingManager()
    @State private var wifiManager = WiFiProvisioningManager()
    @State private var showQRScanner = false

    var body: some View {
        NavigationStack {
            List {
                Section("κ²€μƒ‰λœ μ•‘μ„Έμ„œλ¦¬") {
                    if manager.discoveredAccessories.isEmpty {
                        ContentUnavailableView(
                            "μ•‘μ„Έμ„œλ¦¬ μ—†μŒ",
                            systemImage: "antenna.radiowaves.left.and.right.slash",
                            description: Text("검색을 μ‹œμž‘ν•˜μ„Έμš”")
                        )
                    } else {
                        ForEach(manager.discoveredAccessories, id: \.self.identifier) { accessory in
                            HStack {
                                VStack(alignment: .leading) {
                                    Text(accessory.name)
                                        .font(.headline)
                                    Text(accessory.category.rawValue)
                                        .font(.caption)
                                        .foregroundStyle(.secondary)
                                }

                                Spacer()

                                Button("μ—°κ²°") {
                                    Task {
                                        try? await manager.pair(accessory: accessory)
                                    }
                                }
                                .buttonStyle(.bordered)
                            }
                        }
                    }
                }

                if let connected = manager.connectedAccessory {
                    Section("μ—°κ²°λœ μ•‘μ„Έμ„œλ¦¬") {
                        HStack {
                            Image(systemName: "checkmark.circle.fill")
                                .foregroundStyle(.green)
                            Text(connected.name)
                            Spacer()
                            Button("μ—°κ²° ν•΄μ œ") {
                                Task {
                                    await manager.disconnect()
                                }
                            }
                        }
                    }
                }

                Section("μ„€μ • 방법") {
                    Button {
                        Task {
                            await manager.startSession()
                        }
                    } label: {
                        Label("Bluetooth 검색", systemImage: "dot.radiowaves.left.and.right")
                    }

                    Button {
                        showQRScanner = true
                    } label: {
                        Label("QR μ½”λ“œ μŠ€μΊ”", systemImage: "qrcode.viewfinder")
                    }
                }

                if !wifiManager.provisioningStatus.isEmpty {
                    Section("Wi-Fi μƒνƒœ") {
                        Text(wifiManager.provisioningStatus)
                            .foregroundStyle(.secondary)
                    }
                }
            }
            .navigationTitle("μ•‘μ„Έμ„œλ¦¬ μ„€μ •")
            .sheet(isPresented: $showQRScanner) {
                QRScannerView { qrCode in
                    Task {
                        try? await qrManager.pairMatterDevice(qrCode: qrCode)
                        showQRScanner = false
                    }
                }
            }
        }
    }
}

struct QRScannerView: View {
    let onScan: (String) -> Void
    @Environment(\.dismiss) var dismiss

    var body: some View {
        VStack {
            Text("μ•‘μ„Έμ„œλ¦¬μ˜ QR μ½”λ“œλ₯Ό μŠ€μΊ”ν•˜μ„Έμš”")
                .font(.headline)
                .padding()

            // QR μŠ€μΊλ„ˆ κ΅¬ν˜„
            Rectangle()
                .fill(Color.gray.opacity(0.3))
                .frame(width: 300, height: 300)
                .overlay {
                    Image(systemName: "qrcode.viewfinder")
                        .font(.system(size: 100))
                        .foregroundStyle(.white)
                }

            Button("μ·¨μ†Œ") {
                dismiss()
            }
            .padding()
        }
    }
}

πŸ’‘ HIG Guidelines

🎯 Practical Applications

πŸ“š Learn More

⚑️ Performance Tips: During Wi-Fi provisioning, autoProvisionCurrentNetwork to automatically forward current network info without requiring the user to enter a password.

πŸ“Ž Apple Official Resources

πŸ“˜ Documentation πŸ’» Sample Code 🎬 WWDC Sessions