> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ones1ght.com/llms.txt
> Use this file to discover all available pages before exploring further.

# SDK Quickstart

> OneS1ght SDK의 빠른 설치 방법을 안내합니다.

## 측위정보 설정하기

측위 정보에 대한 항목은 [SDK 설치 전 체크리스트](/sdk/overview#sdk-설치-전-체크리스트) 를 참조해주세요.

## SDK 및 시스템 요구사항

OneS1ght SDK 는 다음의 환경에서 동작합니다.

<Tabs>
  <Tab title="iOS (iPhone)">
    | 항목        | 요구사항                            |
    | --------- | ------------------------------- |
    | 기기        | iPhone 12 이상(UWB 모듈 탑재 모델)      |
    | iOS       | 27.0 이상 (Nearby Interaction 지원) |
    | 개발 환경     | Xcode 27 이상                     |
    | 패키지 최소 타깃 | iOS 15 이상                       |
  </Tab>

  <Tab title="Android">
    준비중입니다.
  </Tab>
</Tabs>

<Note>
  SDK 지원되지 않는 기기 또는 OS인 경우 측위와 데이터 수집은 중단되지만 기존 앱은 정상적으로 동작합니다.
</Note>

### 1. SDK 설치하기

OneS1ght iOS SDK 를 설치합니다.

<Tabs>
  <Tab title="Swift Package Manager">
    <Steps>
      <Step title="패키지 추가 열기">
        Xcode 에서 **\[File] → \[Add Package Dependencies…]** 를 클릭해 주세요.
      </Step>

      <Step title="저장소 URL 입력">
        검색창에 아래 주소를 입력하고 **\[Add Package]** 를 클릭해 주세요.

        ```
        https://github.com/onecheck-inc/OneS1ght-iOS-SDK
        ```

        **Dependency Rule** 은 `Up to Next Major Version` — `0.1.0` 으로 둡니다.
      </Step>

      <Step title="타깃에 추가">
        **\[Add Package]** 를 계속해서 클릭해 `OneS1ght` 라이브러리를 앱 타깃에 추가합니다.
      </Step>

      <Step title="확인">
        Xcode 의 **\[Package Dependencies]** 에서 OneS1ght iOS SDK 추가를 확인할 수 있습니다.
      </Step>
    </Steps>

    `Package.swift` 로 붙이는 경우:

    ```swift theme={null}
    dependencies: [
        .package(url: "https://github.com/onecheck-inc/OneS1ght-iOS-SDK", from: "0.1.0"),
    ],
    targets: [
        .target(name: "YourApp", dependencies: [
            .product(name: "OneS1ght", package: "OneS1ght-iOS-SDK"),
        ])
    ]
    ```
  </Tab>

  <Tab title="Android">
    **준비 중입니다.** → [Android SDK](/sdk/integration/android)
  </Tab>
</Tabs>

### 2. 위치 권한 요청

사용자의 동선을 수집하려면 UWB 모듈의 권한 승인이 필요합니다.

<Tabs>
  <Tab title="iOS">
    먼저 앱 타깃의 **Info** 탭(또는 `Info.plist`)에 권한 설명 두 개를 추가합니다.

    ```xml theme={null}
    <key>NSLocationWhenInUseUsageDescription</key>
    <string>매장 내 위치 확인을 위해 사용합니다.</string>
    <key>NSNearbyInteractionUsageDescription</key>
    <string>UWB 정밀 측위를 위해 사용합니다.</string>
    ```

    | 키                                     | 용도                             | 요청 시점                                |
    | ------------------------------------- | ------------------------------ | ------------------------------------ |
    | `NSLocationWhenInUseUsageDescription` | 위치 권한 — **UWB 측위의 전제 조건**      | 앱이 직접 요청                             |
    | `NSNearbyInteractionUsageDescription` | UWB 정밀 측위 (Nearby Interaction) | `permissions()` 호출 시 또는 첫 측위 세션 시작 시 |

    위치 권한을 먼저 받습니다.

    ```swift theme={null}
    import CoreLocation

    let locationManager = CLLocationManager()
    locationManager.requestWhenInUseAuthorization()
    ```

    Nearby Interaction 권한은 SDK 로 확인합니다.

    ```swift theme={null}
    switch await OneS1ght.permissions() {
    case .authorized:  break
    case .denied:      showSettingsGuide()      // 재요청 불가 — 설정 앱으로 안내
    case .unsupported: showUnsupportedNotice()
    }
    ```

    <Warning>
      Nearby Interaction 권한이 없으면 **UWB 모듈과 통신할 수 없습니다.**
      `CLLocationManagerDelegate` 의 `locationManagerDidChangeAuthorization` 으로 허용 상태를 확인해주세요.
    </Warning>

    <Warning>
      UWB 모듈 권한이 한 번 거부되면 **정책상 앱에서 직접적으로 요청할 수 없습니다.** 재요청시 **설정** 으로 유도해주세요.

      ```swift theme={null}
      UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!)
      ```
    </Warning>
  </Tab>

  <Tab title="Android">
    **준비 중입니다.**
  </Tab>
</Tabs>

### 3. SDK 초기화하기

앱에서 사용하는 생명주기에 따라 **앱이 열릴 때** SDK 를 초기화하세요.
`YOUR_SDK_API_KEY` OneS1ght 콘솔에서 제공하는 SDK API 키이며, `YOUR_GEOSPACE_SDK_KEY` 는 Geospace 의 SDK 키입니다.

<Tabs>
  <Tab title="SwiftUI (Swift)">
    루트 뷰의 `.task` 또는 `App` 의 `init` 에서 호출합니다.

    ```swift theme={null}
    import SwiftUI
    import OneS1ght

    @main
    struct MyApp: App {
        init() {
            Task { @MainActor in
                do {
                    try await OneS1ght.initialize(sdkKey: "YOUR_SDK_API_KEY",
                                                  geoSdkKey: "YOUR_GEOSPACE_SDK_KEY")
                } catch {
                    print("OneS1ght 초기화 실패: \(error)")   // 앱 실행은 막지 않음
                }
            }
        }
        var body: some Scene { WindowGroup { ContentView() } }
    }
    ```
  </Tab>

  <Tab title="AppDelegate (Swift)">
    `didFinishLaunchingWithOptions` 에서 호출합니다.

    ```swift theme={null}
    import UIKit
    import OneS1ght

    @main
    class AppDelegate: UIResponder, UIApplicationDelegate {
        func application(_ application: UIApplication,
                         didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
            Task { @MainActor in
                do {
                    try await OneS1ght.initialize(sdkKey: "YOUR_SDK_API_KEY",
                                                  geoSdkKey: "YOUR_GEOSPACE_SDK_KEY")
                } catch {
                    print("OneS1ght 초기화 실패: \(error)")
                }
            }
            return true
        }
    }
    ```
  </Tab>

  <Tab title="SceneDelegate (Swift)">
    씬 단위로 붙이는 앱이라면 `willConnectTo` 에서 호출합니다. `initialize` 는 **멱등**이라
    씬이 여러 번 연결돼도 안전합니다.

    ```swift theme={null}
    import UIKit
    import OneS1ght

    class SceneDelegate: UIResponder, UIWindowSceneDelegate {
        func scene(_ scene: UIScene,
                   willConnectTo session: UISceneSession,
                   options connectionOptions: UIScene.ConnectionOptions) {
            Task { @MainActor in
                do {
                    try await OneS1ght.initialize(sdkKey: "YOUR_SDK_API_KEY",
                                                  geoSdkKey: "YOUR_GEOSPACE_SDK_KEY")
                } catch {
                    print("OneS1ght 초기화 실패: \(error)")
                }
            }
        }
    }
    ```
  </Tab>

  <Tab title="Android">
    **준비 중입니다.**
  </Tab>
</Tabs>

**초기화 성공 로그**

`YOUR_TENANT_NAME` 은 OneS1ght 에서 부여하는 조직의 이름입니다.

```
[I1001] 초기화 완료 — tenant=YOUR_TENANT_NAME
```

### 4. 프로필 연결

레포트 필터링 및 세그먼트 설정을 위해 **지표 대상**을 설정합니다. 지표 대상의 속성에는 제한이 없으며,
최초 지표 대상을 설정하면 서버가 **고유 프로필 ID**(`profileId`)를 반환합니다.

<Tabs>
  <Tab title="iOS (Swift)">
    ```swift theme={null}
     let profileId: String

     if let saved = savedProfileId {
         profileId = saved                       // 매 실행 때 마다 저장해 둔 값을 재사용합니다. 
     } else {
         profileId = try await OneS1ght.createProfile([   // 프로필 정보가 없을때 실행합니다. 
             "gender":   "F",
             "ageBand":  "20s",        // 세그먼트 지정하고 싶은 데이터를 자유롭게 지정해주세요.
             "interest": "cosmetics",
         ])
         save(profileId)                         // 프로필 생성시 고유 ID가 발급되며 다음 방문시 필요합니다. 
     }

     OneS1ght.identify(profileId: profileId)    // 발급된 프로필 아이디를 인증합니다. 
    ```
  </Tab>

  <Tab title="Android">
    **준비 중입니다.**
  </Tab>
</Tabs>

<Warning>
  **프로필 ID 를 분실하면 되돌릴 수 없습니다.** 서버는 발급한 값을 돌려줄 뿐 앱을 대신해 보관하지
  않으니 별도의 저장소를 사용하여 저장해 주세요.
</Warning>

<Warning>
  프로필을 연결하지 않고 측위를 시작하는 경우 `.notIdentified`(`E1004`)가 발생되며 측위를 시작하지 않습니다.
</Warning>

### 5. 측위 공간 설정

사용자에게 측위하고자 하는 공간을 지정합니다.
OneS1ght 에서는 테넌트(조직)에 다수의 **건물**이 존재할 수 있고 하나의 건물에는 다수의 **층**이 있습니다.
즉 측위를 지정하기 위해서는 하나의 층이 선택되어야 합니다.

<Tabs>
  <Tab title="iOS (Swift)">
    ```swift theme={null}
     let buildings = try await OneS1ght.buildings()      // 테넌트(조직)의 빌딩을 조회합니다.
     let floors    = try await OneS1ght.floors(buildings[0].id) // 특정 빌딩의 층을 조회합니다. 

     try await OneS1ght.setFloorMap(floors[0], buildingID: buildings[0].id) // 빌딩 ID와 층 ID를 통해 대상의 지도를 수신합니다. 
     
     try await OneS1ght.refreshZones() // 지도 내 구역정보를 새로 갱신합니다. 
    ```
  </Tab>

  <Tab title="Android">
    **준비 중입니다.**
  </Tab>
</Tabs>

<Note>
  층의 내용을 변경시 콘솔 페이지의 [공간 관리](/locator/areas)에서 변경 가능합니다. <br />
  변경된 내용은 refreshZones 호출로 갱신 가능합니다.
</Note>

### 6. 진입 콜백 등록

층에 있는 특정 구역의 이벤트를 지정할 수 있습니다. 이벤트 형태는 커스텀으로 구현 가능합니다.

<Tabs>
  <Tab title="iOS (Swift)">
    ```swift theme={null}
       let session = try OneS1ght.floorSession() 

       // 구역 진입·이탈·체류
       session.onZoneEnter = { zone in showCoupon(zone) }   // 특정 구역을 진입했을 때 콜백 이벤트입니다. 
       session.onZoneExit  = { zone in hideCoupon(zone) }   // 특정 구역을 이탈했을 때 콜백 이벤트입니다. 
       session.onZoneDwell = { zone, seconds in print("체류 \(Int(seconds))초 — \(zone.name)") } // 특정 구역을 체류했을 때 콜백 이벤트입니다. 

       // 실시간 좌표 콜백 이벤트입니다. (x,y 좌표는 도면 로컬 미터로 반환합니다. )
       session.onPosition = { coord in
           print("📍 x: \(coord.x), y: \(coord.y)")
       }

       // 특정 구역의 이벤트 트리거입니다. 
       session.onTriggers = { zoneId, triggers in handle(triggers) }

       // SDK 디버그용입니다. (개발용으로 사용되며 Production 환경에서 사용할 수 없습니다.)
       OneS1ght.onDebugLog = { level, line in print("[OneS1ght][\(level)] \(line)") }
    ```
  </Tab>

  <Tab title="Android">
    **준비 중입니다.**
  </Tab>
</Tabs>

<Warning>
  onDebugLog 는 initialize 보다 먼저 호출되어야 초기화 단계부터 디버깅을 로그를 확인하실 수 있습니다.
</Warning>

### 7. 측위 시작

층 진입준비(세션)가 완료되었을 경우 측위를 시작합니다.

<Tabs>
  <Tab title="iOS (Swift)">
    ```swift theme={null}
    try await session.begin() // 측위 시작
    ```
  </Tab>

  <Tab title="Android">
    **준비 중입니다.**
  </Tab>
</Tabs>

### 8. 측위 종료

구역을 이탈했을 경우 측위를 멈춥니다.
이때 초기화·층 설정은 유지되어 `begin` 재호출로 언제든 재시작할 수 있습니다.

<Tabs>
  <Tab title="iOS (Swift)">
    ```swift theme={null}
      await session.end() // 측위 종료
    ```
  </Tab>

  <Tab title="Android">
    **준비 중입니다.**
  </Tab>
</Tabs>

## 전체 예시 코드

<Tabs>
  <Tab title="iOS (Swift)">
    ```swift theme={null}
    import SwiftUI
    import CoreLocation
    import OneS1ght

    @main
    struct MyApp: App {
        var body: some Scene {
            WindowGroup { StoreView() }
        }
    }

    struct StoreView: View {
        @State private var status = "대기"
        private let locationManager = CLLocationManager()

        var body: some View {
            Text(status)
                .task {
                    do {
                        // ① 초기화 (앱 시작 시 1회)
                        try await OneS1ght.initialize(sdkKey: "YOUR_SDK_API_KEY",
                                                      geoSdkKey: "YOUR_GEOSPACE_SDK_KEY")

                        // ② 권한 — 실제 앱에서는 delegate 로 '허용' 응답을 확인한 뒤
                        //    측위를 시작하세요 (첫 실행 시 팝업 응답 전 시작은 실패합니다)
                        locationManager.requestWhenInUseAuthorization()
                        guard await OneS1ght.permissions() == .authorized else {
                            status = "측위 권한이 필요합니다"
                            return
                        }

                        // ③ 프로필 연결 (발급받은 값을 앱이 보관해 재사용)
                        let profileId = try await OneS1ght.createProfile(["ageBand": "20s"])
                        OneS1ght.identify(profileId: profileId)

                        // ④ 측위 공간 설정 — 이걸 안 하면 좌표가 나오지 않습니다
                        let buildings = try await OneS1ght.buildings()
                        let floors = try await OneS1ght.floors(buildings[0].id)
                        try await OneS1ght.setFloorMap(floors[0], buildingID: buildings[0].id)

                        // ⑤ 진입 콜백 등록
                        let session = try OneS1ght.floorSession()
                        session.onPosition = { coord in
                            status = String(format: "x %.2f · y %.2f", coord.x, coord.y)
                        }
                        session.onZoneEnter = { zone in print("진입: \(zone.name)") }
                        session.onZoneExit  = { zone in print("이탈: \(zone.name)") }

                        // ⑥ 측위 시작
                        try await session.begin()
                    } catch {
                        status = "실패: \(error)"
                    }
                }
                .onDisappear {
                    // ⑦ 측위 종료
                    Task { @MainActor in
                        if let session = try? OneS1ght.floorSession() { await session.end() }
                    }
                }
        }
    }
    ```
  </Tab>

  <Tab title="Android">
    **준비 중입니다.**
  </Tab>
</Tabs>
