• Overview

    • 블루투스와 Wi-Fi를 사용해서 유려하면서도 개인정보 보호도 되는 형태로 악세사리를 설정하고 관리할 수 있다.

    • API를 호출하면 새로운 근처에서 연결하려는 악세사리에 대한 피커가 뜬다.

      • 탭 한번이면 설정이 끝나고, 블루투스와 Wi-Fi 모두 설정이 된다.
      • 기존에 쓰던 Core Bluetooth와 NetworkExtension API를 모두 쓸 수 있다.ㅈ

      스크린샷 2024-06-21 오전 10.59.44.png

    • 개인정보 및 보안에 AccessorySetupKit으로 추가한 모든 악세사리를 관리할 수 있는 항목도 추가

      • 지원 기술에 따라서 블루투스나 Wi-Fi 페이지에서도 확인할 수 있다.

      스크린샷 2024-06-21 오전 11.02.12.png

      스크린샷 2024-06-21 오전 11.02.42.png

    • 기존에는 블루투스와 Wi-Fi를 따로 설정해야 했다.

      스크린샷 2024-06-21 오전 11.06.11.png

    • 앱은 검색 규칙과 에셋을 설정하고 모든 처리와 피커 UI는 별도 프로세스에서 처리된다.

      스크린샷 2024-06-21 오전 11.09.07.png

  • AccessorySetupKit API

    • info.plist 추가

      • c - Supports: 지원 기술

        • Bluetooth, Wi-Fi
      • AccessorySetupKit - Bluetooth Services

      • AccessorySetupKit - Bluetooth Names

      • AccessorySetupKit - Bluetooth Company Identifiers

        스크린샷 2024-06-21 오전 11.31.44.png

    • 악세사리를 사용하기 위해서는 발견 - 인증 - 상호 작용의 3단계가 필요하다.

      • AccessorySetupKit은 발견 - 인증 단계를 다루고, 상호작용은 기존 버전과 같이 CoreBluetooth와 NetworkExtension을 사용한다.
    • ASAccessorySession

      • 중심이 되는 개체
      • 피커르 띄우고 이벤트를 알려주고 악세사리를 관리한다.
    • 피커를 띄우려면 ASPickerDisplayItem 배열을 넘겨야 한다.

      • 이름, 이미지, Discovery Descriptor를 정의한다.
      • ASDiscoveryDescriptor는 모든 필요한 검색 규칙을 명시할 수 있도록 해준다.
        • Bluetooth service UUID, Wi-Fi SSID등을 지정할 수 있는 매우 유연한 구조
        • 자세한 것은 문서 참조
    • 검색이 되면 ASAccessoryEvent가 전달되고 여기에 새롭게 추가된 악세사리 정보가 전달된다.

      스크린샷 2024-06-21 오전 11.51.10.png

    import AccessorySetupKit
    
    // Create a session
    var session = ASAccessorySession()
    
    // Activate session with event handler
    session.activate(on: DispatchQueue.main, eventHandler: handleSessionEvent(event:))
    
    // Handle event
    func handleSessionEvent(event: ASAccessoryEvent) {  
        switch event.eventType {
        case .activated:
            print("Session is activated and ready to use")
            print(session.accessories)
        default:
            print("Received event type \\(event.eventType)")
        }
    }
    
    • ASPickerDisplayItem 만들기

      • 이 외에도 커스텀 할 수 있는 것들은 문서 참조
      // Create descriptor for pink dice
      let pinkDescriptor = ASDiscoveryDescriptor()
      pinkDescriptor.bluetoothServiceUUID = pinkUUID
      // Create descriptor for blue dice
      let blueDescriptor = ASDiscoveryDescriptor()
      blueDescriptor.bluetoothServiceUUID = blueUUID
      
      // Create picker display items
      let pinkDisplayItem = ASPickerDisplayItem(
          name: "Pink Dice",
          productImage: UIImage(named: "pink")!,
          descriptor: pinkDescriptor
      )
      let blueDisplayItem = ASPickerDisplayItem(
          name: "Blue Dice",
          productImage: UIImage(named: "blue")!,
          descriptor: blueDescriptor
      )
      
    • 피커 띄우기

      // Invoke accessory picker
      Button {
          session.showPicker(for: [pinkDisplayItem, blueDisplayItem]) { error in
              if let error {
                  // Handle error
              }
          }
      } label: {
          Text("Add Dice")
      }
      
    • 페어링할 때 추가적인 보안 절차를 필요로 한다면 그것도 반영된다.

      스크린샷 2024-06-21 오전 11.58.22.png

    • 이벤트 처리

      // Handle event
      func handleSessionEvent(event: ASAccessoryEvent) {  
          switch event.eventType {
      
          case .accessoryAdded:
              let newDice: ASAccessory = event.accessory!
      
          case .accessoryChanged:
              print("Accessory properties changed")
      
          case .accessoryRemoved:
              print("Accessory removed from system")
      
          default:
              print("Received event with type: \\(event.eventType)")
          }
      }
      
    • ASAccessory: 실제 악세사리를 나타냄

      • 인증 상태, 이름, discovery descriptor,
      • 네트워크 인터페이스
        • Bluetooth Identifier(CBPheriperal)
        • Wi-Fi SSID
    • 실제 악세사리 연결은 기존처럼 CoreBluetoot를 쓴다.

      // Connect to accessory using CoreBluetooth
      let central = CBCentralManager(delegate: self, queue: nil)
      
      func centralManagerDidUpdateState(_ central: CBCentralManager) {
          switch central.state {
          case .poweredOn:
             // state will only be updated to poweredOn when you have paired accessories
              let peripheral = central.retrievePeripherals(withIdentifiers: [newDice.bluetoothIdentifier]).first
              central.connect(peripheral)
          default:
              print("Received event type \\(event.eventType)")
          }
      }
      
      func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
          peripheral.delegate = self
          peripheral.discoverServices(pinkUUID)
      }
      
    • 기존에 따로 연결했던 것도 AccessorySetupKit을 쓰도록 업그레이드 할 수 있다.

      • 이러면 새로운 Accessory설정에서 이를 볼 수 있다.

        // Create migration items
        let pinkMigration = ASMigrationDisplayItem(name: "Pink Dice", productImage: UIImage(named: "pink")!, descriptor: pinkDescriptor)
        pinkMigration.peripheralIdentifier = pinkPeripheral.identifier
        
        // Present picker with migration items
        session.showPicker(for: [pinkMigration]) { error in
            if let error {
                // Handle error
            }
        }
        
      • 마이그레이션 아이템만 담아서 피커를 띄우면 정보 페이지가 뜨면서 마이그레이션 됐음을 알려준다.

        • 이때 마이그레이션 아이템이 아닌 걸 같이 넣으면 새로운 아이템이 발견되고 설정됐을 때만 마이그레이션이 일어난다.

        스크린샷 2024-06-21 오후 12.09.10.png

  • Design for AccessorySetupKit

    • 이미지 컨테이너 크기는 180x120pt, 이에 맞게 디자인 할 것

      스크린샷 2024-06-21 오후 12.10.27.png

    • 다크모드/라이트모드 모두에서 잘보이는지 확인할 것

      스크린샷 2024-06-21 오후 12.14.16.png

    • 투명 경계선 때문에 이미지 스케일업 했을 때 너무 작게 보일 수 있으니 주의

      스크린샷 2024-06-21 오후 12.14.26.png

    • 피커 때문에 UI가 가려지기 때문에 악세사리 설정할 때는 안보이는 곳에서 UI 업데이트 하지 말기

      • pickerDidPresent와 pickerDidDismiss 이벤트를 통해서 피커가 뜨는지 내려가는지 알 수 있다.

      스크린샷 2024-06-21 오후 12.15.18.png

    • 피커 띄우는 API는 언제든지 호출할 수 있지만 갑자기 피커가 뜨는 것보다는 맥락을 좀 더 제공하는 것을 권장한다.

      • 버튼을 통해서 사용자가 직접 호출하게 하라

      스크린샷 2024-06-21 오후 12.16.58.png