> ## 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.

# 시작하기

> OneS1ght SDK for iOS

<Info>
  설치 및 초기화는 [SDK Quickstart](/sdk/quick-start)를 참고해 주세요.<br />
</Info>

## 실시간 측위정보 수신

세션의 `onPosition` 콜백으로 앱 사용자의 좌표를 수신합니다. <br />
서버 전송과 무관하게 호출되며 초당 4회 좌표를 수신합니다.<br />
좌표 단위는 **미터(m)** 단위로 측정합니다.

<Tabs>
  <Tab title="SwiftUI">
    ```swift theme={null}
    struct StoreMapView: View {
        @State private var me: Coordinates?

        var body: some View {
            MapCanvas(marker: me)
                .task {
                    guard let session = try? OneS1ght.floorSession() else { return }
                    // coord.x, coord.y: 도면 로컬 좌표 (미터, 좌하단 원점) · coord.z: 높이
                    session.onPosition = { coord in me = coord }
                }
        }
    }
    ```
  </Tab>

  <Tab title="UIKit">
    ```swift theme={null}
    final class StoreMapViewController: UIViewController {
        override func viewDidLoad() {
            super.viewDidLoad()

            guard let session = try? OneS1ght.floorSession() else { return }
            // coord.x, coord.y: 도면 로컬 좌표 (미터, 좌하단 원점) · coord.z: 높이
            session.onPosition = { [weak self] coord in
                self?.updateMyLocationMarker(x: coord.x, y: coord.y)
            }
        }
    }
    ```
  </Tab>
</Tabs>

## 도면 표시

사용자에게 측위중인 도면 내용을 보여줍니다. <br />도면을 보여주기 위해서는 건물과 층 ID가 필요합니다.
도면 위에 사용자 위치를 표시하려면 `onPosition` 좌표를 도면에 매핑해야 합니다.

<Tabs>
  <Tab title="SwiftUI">
    ```swift theme={null}
    struct FloorPlanView: View {
        let building: Building
        let floorId: String
        @State private var plan: UIImage?

        var body: some View {
            Group {
                if let plan { Image(uiImage: plan).resizable().scaledToFit() }
                else { ProgressView() }
            }
            .task {
                guard let floor = try? await OneS1ght.floor(building.id, floorId) else { return }
                plan = floor.image.flatMap(UIImage.init(data:))
                setBounds(minX: floor.minX, minY: floor.minY,
                          maxX: floor.maxX, maxY: floor.maxY)
            }
        }
    }
    ```
  </Tab>

  <Tab title="UIKit">
    ```swift theme={null}
    final class FloorPlanViewController: UIViewController {
        private let planView = UIImageView()

        override func viewDidLoad() {
            super.viewDidLoad()

            Task { @MainActor in
                let floor = try await OneS1ght.floor(building.id, floorId)
                planView.image = floor.image.flatMap(UIImage.init(data:))
                setBounds(minX: floor.minX, minY: floor.minY,
                          maxX: floor.maxX, maxY: floor.maxY)
            }
        }
    }
    ```
  </Tab>
</Tabs>

<Note>
  `hasPlan == false` 인 경우 도면이 구성되지 않은 것을 간주합니다.<br />
  [가이드문서 - 도면 배치 · 클러스터](/geospace/placement)를 참고해 주세요.
</Note>

로케이터 배치와 UWB 세션 ID 가 궁금하면 `locators(_:_:)` 로 따로 볼 수 있습니다.
`positioningReady` 가 `false` 면 이 층은 로케이터·세션 정보가 없어 측위를 시작할 수 없는 상태입니다.

## 구역 이벤트

진입·이탈·체류가 각각 별도 콜백으로 옵니다. 판정은 **기기 안에서** 이루어져 서버 왕복 지연이 없습니다.

<Tabs>
  <Tab title="SwiftUI">
    ```swift theme={null}
    @State private var banner: String?

    session.onZoneEnter = { zone in banner = "진입: \(zone.name)" }
    session.onZoneExit  = { zone in banner = nil }
    session.onZoneDwell = { zone, seconds in
        banner = "체류: \(zone.name) — \(Int(seconds))초"
    }
    ```
  </Tab>

  <Tab title="UIKit">
    ```swift theme={null}
    session.onZoneEnter = { [weak self] zone in self?.showBanner("진입: \(zone.name)") }
    session.onZoneExit  = { [weak self] _    in self?.hideBanner() }
    session.onZoneDwell = { [weak self] zone, seconds in
        self?.showBanner("체류: \(zone.name) — \(Int(seconds))초")
    }
    ```
  </Tab>
</Tabs>

| 콜백            | 발생 시점                       |
| ------------- | --------------------------- |
| `onZoneEnter` | 특정 구역 진입 시 발생됩니다.           |
| `onZoneExit`  | 특정 구역 이탈 시 발생됩니다.           |
| `onZoneDwell` | 특정 구역 체류 시 발생됩니다. (기본값: 5초) |

각 층에 대한 구역은 [공간 관리](/locator/areas)에서 설정하실 수 있습니다.

<Warning>
  층에 구역이 하나도 없으면 좌표는 쌓이지만 진입·이탈이 발생하지 않습니다(`E3004`).
</Warning>

### 구역정보 업데이트

콘솔에서 구역정보를 변경 후 저장한 경우 코드 내에서 새로고침을 해야 합니다.

```swift theme={null}
let zones = await OneS1ght.refreshZones()
```

## 프로필 생성 및 연결

고객의 동선 수집을 하려면 각 계정에 해당하는 프로필을 발급하고 ID를 별도 저장소에 보관합니다.

```swift theme={null}
// 1. 최초 1회 접속시 프로필을 발급받습니다.
// 프로필 내용은 향후 콘솔에서 레포트를 발행할 경우 세그먼트를 구성하기 위함이며 Key-Value 의 제한은 없습니다. 
let profileId = try await OneS1ght.createProfile([
    "gender":   "F",
    "ageBand":  "20s",        
    "interest": "cosmetics",
])

// 2. 프로필을 발급한 상태에서 재연결할 경우 프로필 ID를 통해 인증 절차를 거칩니다. 
OneS1ght.identify(profileId: savedProfileId)

// 3. 사용자의 측위 수집을 종료하고 프로필 설정을 해제합니다.
OneS1ght.identify(profileId: nil)
```

| 메서드                    | 용도                                 |
| ---------------------- | ---------------------------------- |
| `createProfile(_:)`    | 새로운 프로필을 생성합니다.                    |
| `getProfile(_:)`       | 현재 프로필 정보를 조회합니다.                  |
| `putProfile(_:_:)`     | 새로운 프로필 정보로 업데이트하며 이전의 데이터는 삭제됩니다. |
| `deleteProfile(_:)`    | 프로필 정보를 삭제합니다.                     |
| `identify(profileId:)` | 유효한 프로필인지 검증합니다.                   |

<Note>
  발급된 프로필 ID는 고객사에서 별도 관리하여야 합니다. 프로필 정보 분실시 다시 찾을 수 없으니 관리에 주의해 주세요.
</Note>

## 건물·층 선택

측위정보를 수집하기 위해서는 건물과 층을 설정하여야 합니다.
하나의 건물은 여러개의 층을 가질 수 있습니다.

`setFloorMap` 은 측위 정보와 지도를 설정하기 위한 로케이터 정보와 사용자가 보여질 구역정보를 함께 설정합니다.

```swift theme={null}
// 건물과 층을 조회합니다.
let buildings = try await OneS1ght.buildings()
let floors    = try await OneS1ght.floors(buildings[0].id)

// 층과 빌딩 ID로 최초 지도를 설정합니다.
try await OneS1ght.setFloorMap(floors[0], buildingID: buildings[0].id)

// 세션 유지 중 다른 층으로 변경하여 지도의 내용을 교체합니다. 
try await OneS1ght.setFloorMap(floors[1], buildingID: buildings[0].id)

// 모든 측위 수집이 끝났다면 지도설정을 해제합니다. 
try await OneS1ght.setFloorMap(nil)
```

<Note>
  빌딩과 층 정보는 초기화 시 `geoSdkKey` 를 포함하여야 하며 포함하지 않을 시 조회하실 수 없습니다.
</Note>

## 미지원 기기 예외처리

OneS1ght SDK를 사용하기 위해서는 사용자의 기기가 최소 지원 사양을 충족해야 합니다. [SDK Quickstart](/sdk/quick-start)<br />
지원되지 않는 기기에 설치가 되어도 SDK 영향 없이 앱을 사용할 수 있습니다.

<Note>
  `isDeviceAvailable`은 실제 기기가 SDK를 사용 가능한지 확인할 수 있는 간략한 형태이며 반환은 Boolean(True/False)로 반환합니다.
</Note>

## 데이터 전송 제어

OneS1ght SDK 내부 로직에 의해 수집된 측위정보를 전송할 수 있으나 특정 예외 상황 발생시 수집된 측위정보를 즉시 전송하거나 비울 수 있습니다.

```swift theme={null}
await OneS1ght.send()      // 측위 정보를 수동으로 전송합니다.
OneS1ght.empty()           // 수집된 측위정보를 삭제하고 전송하지 않습니다.
```

## 세션 재설정

누적된 세션 정보를 초기화하고 새로운 세션으로 초기화합니다.

```swift theme={null}
await OneS1ght.reset()
```

## 환경별 API 키 활용법

OneS1ght 콘솔에서는 새로운 API 발급시 개발용(Development)과 운영용(Production)으로 구분하여 키를 발급할 수 있습니다. <br />
두 환경은 격리되어 있으며 다른 영역으로 간섭할 수 없습니다. <br />
테스트 중 운영 데이터의 혼입을 막기 위해 환경별로 API 키를 설정하실 수 있습니다.

```swift theme={null}
#if DEBUG   // 개발 모드일 시
let sdkKey = "YOUR_DEVELOPMENT_API_KEY"
#else
let sdkKey = "YOUR_PRODUCTION_API_KEY"
#endif
try await OneS1ght.initialize(sdkKey: sdkKey, geoSdkKey: geoSdkKey)
```

<Note>
  실제 측위 내용은 iOS 정책상 에뮬레이터에서 확인이 불가합니다. 실기기에서 배포 후 테스트 해주세요.
</Note>

## 문제 해결하기

<CardGroup cols={2}>
  <Card title="로그 사용법" icon="wrench" href="/sdk/faq/logging">
    개발 중 로그 사용법을 안내합니다.
  </Card>

  <Card title="에러 코드 안내" icon="triangle-exclamation" href="/sdk/faq/error-code">
    개발 중 발생하는 오류 내용을 안내합니다.
  </Card>
</CardGroup>
