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

# iOS SDK

> SDK Version 0.2.3

## インポート

<CodeGroup>
  ```swift Swift icon="swift" theme={null}
  import OneS1ght
  ```

  ```objc Objective-C icon="apple" theme={null}
  #import "MyApp-Swift.h"   // MyApp = Product Module Name
  ```
</CodeGroup>

## 初期化

### .initialize

OneS1ght API キーを検証し、テナント設定を取得します。測位エンジンの起動や端末の対応確認は行いません。

<CodeGroup>
  ```swift Swift icon="swift" theme={null}
  try await OneS1ght.initialize(sdkKey: "YOUR_SDK_KEY")
  ```

  ```objc Objective-C icon="apple" theme={null}
  [OneS1ghtBridge initializeWithSdkKey:@"YOUR_SDK_KEY" completion:^(NSError *error) { }];
  ```
</CodeGroup>

### .requestPermission

測位エンジンの利用に必要なアプリ権限をリクエストします。

| 結果 | 意味 |
| - | - |
| `authorized` | 測位可能 |
| `denied` | 権限拒否 |
| `unsupported` | 測位非対応端末 |

<CodeGroup>
  ```swift Swift icon="swift" theme={null}
  let status = await OneS1ght.requestPermission()
  ```

  ```objc Objective-C icon="apple" theme={null}
  [OneS1ghtBridge requestPermission:^(NSString *status) { }];   // @"authorized" · @"denied" · @"unsupported"
  ```
</CodeGroup>

### .setLanguage

SDK のログ文言の言語を設定します。<br />
`ko`: 韓国語、  `ja`: 日本語、  `en`: 英語、 `nil`: システム設定の言語

<CodeGroup>
  ```swift Swift icon="swift" theme={null}
  OneS1ght.setLanguage("ja")
  ```

  ```objc Objective-C icon="apple" theme={null}
  [OneS1ghtBridge setLanguage:@"ja"];
  ```
</CodeGroup>

### .onDebugLog

SDK のログを受信します。<br />
`level`: ログレベル、 `message`: ログメッセージ

<CodeGroup>
  ```swift Swift icon="swift" theme={null}
  OneS1ght.onDebugLog = { level, message in
      if level >= .warn { print("[OneS1ght] \(message)") }
  }
  ```

  ```objc Objective-C icon="apple" theme={null}
  [OneS1ghtBridge onDebugLog:^(NSString *level, NSString *message) { }];
  ```
</CodeGroup>

### .reset

セッションを破棄します。以降の API は `initialize` を再度呼ぶまで `notInitialized` エラーになります（別のキーでも可）。

<CodeGroup>
  ```swift Swift icon="swift" theme={null}
  await OneS1ght.reset()
  ```

  ```objc Objective-C icon="apple" theme={null}
  [OneS1ghtBridge reset:^{ }];
  ```
</CodeGroup>

## 状態の取得

### .isInitialized

初期化に成功したかを返します。

<CodeGroup>
  ```swift Swift icon="swift" theme={null}
  let ready = OneS1ght.isInitialized
  ```

  ```objc Objective-C icon="apple" theme={null}
  BOOL ready = OneS1ghtBridge.isInitialized;
  ```
</CodeGroup>

### .deviceAvailability

測位可能な端末かを判定し、非対応の場合はその理由を返します。

| 値 | 意味 |
| - | - |
| `available` | 測位可能 |
| `osVersionTooLow` | iOS 27 未満 |
| `deviceNotSupported` | UWB 非対応端末 |

<CodeGroup>
  ```swift Swift icon="swift" theme={null}
  switch OneS1ght.deviceAvailability {
  case .available:          break
  case .osVersionTooLow:    break
  case .deviceNotSupported: break
  @unknown default:         break
  }
  ```

  ```objc Objective-C icon="apple" theme={null}
  NSString *availability = OneS1ghtBridge.deviceAvailability;   // @"available" · @"osVersionTooLow" · @"deviceNotSupported"
  ```
</CodeGroup>

### .googleMapKey

図面を表示するための Google Maps API キーを返します。

<CodeGroup>
  ```swift Swift icon="swift" theme={null}
  let mapKey = OneS1ght.googleMapKey   // String?
  ```

  ```objc Objective-C icon="apple" theme={null}
  NSString *mapKey = OneS1ghtBridge.googleMapKey;
  ```
</CodeGroup>

### .sdkVersion

使用中の SDK バージョンを返します。

<CodeGroup>
  ```swift Swift icon="swift" theme={null}
  let version = OneS1ght.sdkVersion   // "0.2.3"
  ```

  ```objc Objective-C icon="apple" theme={null}
  NSString *version = OneS1ghtBridge.sdkVersion;
  ```
</CodeGroup>

## プロフィール

### .createProfile

新しいパーソナライズプロフィールを作成します。成功すると一意のプロフィール ID を返します。

<CodeGroup>
  ```swift Swift icon="swift" theme={null}
  let profileId = try await OneS1ght.createProfile(["gender": "F", "ageBand": "20s"])
  ```

  ```objc Objective-C icon="apple" theme={null}
  [OneS1ghtBridge createProfile:@{ @"gender": @"F", @"ageBand": @"20s" }
                      completion:^(NSString *profileId, NSError *error) { }];
  ```
</CodeGroup>

### .identify

収集する測位データを紐付けるプロフィールを設定します。解除する場合は `nil` を設定してください。

<CodeGroup>
  ```swift Swift icon="swift" theme={null}
  OneS1ght.identify(profileId: profileId)
  ```

  ```objc Objective-C icon="apple" theme={null}
  [OneS1ghtBridge identifyWithProfileId:profileId];
  ```
</CodeGroup>

### .fetchProfile

プロフィールの属性を取得します。

<CodeGroup>
  ```swift Swift icon="swift" theme={null}
  let attributes = try await OneS1ght.fetchProfile(profileId)
  ```

  ```objc Objective-C icon="apple" theme={null}
  [OneS1ghtBridge fetchProfile:profileId
                     completion:^(NSDictionary<NSString *, NSString *> *attributes, NSError *error) { }];
  ```
</CodeGroup>

### .replaceProfile

プロフィールの属性を置き換えます。以前のデータは削除されます。

<CodeGroup>
  ```swift Swift icon="swift" theme={null}
  try await OneS1ght.replaceProfile(profileId, attributes: ["gender": "F", "ageBand": "30s"])
  ```

  ```objc Objective-C icon="apple" theme={null}
  [OneS1ghtBridge replaceProfile:profileId
                      attributes:@{ @"gender": @"F", @"ageBand": @"30s" }
                      completion:^(NSError *error) { }];
  ```
</CodeGroup>

### .deleteProfile

プロフィール情報を完全に削除します。

<CodeGroup>
  ```swift Swift icon="swift" theme={null}
  try await OneS1ght.deleteProfile(profileId)
  ```

  ```objc Objective-C icon="apple" theme={null}
  [OneS1ghtBridge deleteProfile:profileId completion:^(NSError *error) { }];
  ```
</CodeGroup>

## 空間の取得

### .buildings

テナントに紐付くすべての建物一覧を取得します。

<CodeGroup>
  ```swift Swift icon="swift" theme={null}
  let buildings = try await OneS1ght.buildings()
  let building = try await OneS1ght.building(id: buildingId)
  ```

  ```objc Objective-C icon="apple" theme={null}
  [OneS1ghtBridge buildings:^(NSArray<NSDictionary *> *buildings, NSError *error) { }];   // id · name · floorCount
  ```
</CodeGroup>

### .floors

建物のフロア一覧を取得します。

<CodeGroup>
  ```swift Swift icon="swift" theme={null}
  let floors = try await OneS1ght.floors(buildingId: buildingId)
  ```

  ```objc Objective-C icon="apple" theme={null}
  [OneS1ghtBridge floorsWithBuildingId:buildingId
                           completion:^(NSArray<NSDictionary *> *floors, NSError *error) { }];
  ```
</CodeGroup>

### .floor

選択したフロアの詳細情報を取得します。

<CodeGroup>
  ```swift Swift icon="swift" theme={null}
  let floor = try await OneS1ght.floor(buildingId: buildingId, floorId: floorId)
  let png = floor.image   // Data?
  ```

  ```objc Objective-C icon="apple" theme={null}
  [OneS1ghtBridge floorWithBuildingId:buildingId floorId:floorId
                          completion:^(NSDictionary *floor, NSError *error) {
      NSData *png = floor[@"image"];
  }];
  ```
</CodeGroup>

### .zones

選択したフロアのすべてのゾーンを取得します。

<CodeGroup>
  ```swift Swift icon="swift" theme={null}
  let zones = try await OneS1ght.zones(buildingId: buildingId, floorId: floorId)
  let zone = try await OneS1ght.zone(buildingId: buildingId, floorId: floorId, zoneId: zoneId)
  ```

  ```objc Objective-C icon="apple" theme={null}
  [OneS1ghtBridge zonesWithBuildingId:buildingId floorId:floorId
                          completion:^(NSArray<NSDictionary *> *zones, NSError *error) { }];   // id · name · polygon · dwellSeconds
  ```
</CodeGroup>

### .locators

選択したフロアのロケーター情報を取得します。

<CodeGroup>
  ```swift Swift icon="swift" theme={null}
  let floorLocators = try await OneS1ght.locators(buildingId: buildingId, floorId: floorId)
  let ready = floorLocators.positioningReady
  ```

  ```objc Objective-C icon="apple" theme={null}
  [OneS1ghtBridge locatorsWithBuildingId:buildingId floorId:floorId
                             completion:^(NSDictionary *floorLocators, NSError *error) { }];   // locators · sessionId
  ```
</CodeGroup>

## フロアの指定

### .setFloorMap

測位を開始するフロアを指定します。

<CodeGroup>
  ```swift Swift icon="swift" theme={null}
  let floor = try await OneS1ght.floor(buildingId: buildingId, floorId: floorId)
  try await OneS1ght.setFloorMap(floor, buildingId: buildingId)
  ```

  ```objc Objective-C icon="apple" theme={null}
  [OneS1ghtBridge setFloorMapWithBuildingId:buildingId floorId:floorId
                                 completion:^(NSError *error) { }];   // floorId nil = 解除
  ```
</CodeGroup>

### .refreshZones

最新のゾーン情報を取得します。

<CodeGroup>
  ```swift Swift icon="swift" theme={null}
  let zones = await OneS1ght.refreshZones()
  ```

  ```objc Objective-C icon="apple" theme={null}
  [OneS1ghtBridge refreshZones:^(NSArray<NSDictionary *> *zones) { }];
  ```
</CodeGroup>

## 測位セッション

### .floorSession

測位セッションを取得します。フロアに関係なく、アプリ全体で同じインスタンス（シングルトン）です。

<CodeGroup>
  ```swift Swift icon="swift" theme={null}
  let session = try OneS1ght.floorSession()
  ```

  ```objc Objective-C icon="apple" theme={null}
  // ブリッジがセッションを内包します — 個別に取得しません。
  ```
</CodeGroup>

### .begin

ユーザーの測位を開始します。

<CodeGroup>
  ```swift Swift icon="swift" theme={null}
  try await session.begin()
  ```

  ```objc Objective-C icon="apple" theme={null}
  [OneS1ghtBridge beginWithCompletion:^(NSError *error) { }];
  ```
</CodeGroup>

### .end

ユーザーの測位を終了します。

<CodeGroup>
  ```swift Swift icon="swift" theme={null}
  await session.end()
  ```

  ```objc Objective-C icon="apple" theme={null}
  [OneS1ghtBridge endWithCompletion:^{ }];
  ```
</CodeGroup>

### .pause · .resume

ユーザーの測位を一時停止・再開します。終了とは異なり、SDK 内の測位エンジンは停止しません。

<CodeGroup>
  ```swift Swift icon="swift" theme={null}
  session.pause()
  session.resume()
  ```

  ```objc Objective-C icon="apple" theme={null}
  [OneS1ghtBridge pause];
  [OneS1ghtBridge resume];
  ```
</CodeGroup>

### .isRunning · .isPaused

測位中かどうかと、一時停止中かどうかを返します。

<CodeGroup>
  ```swift Swift icon="swift" theme={null}
  let running = session.isRunning
  let paused = session.isPaused
  let current = session.floor   // Floor?
  ```

  ```objc Objective-C icon="apple" theme={null}
  BOOL running = OneS1ghtBridge.isRunning;
  BOOL paused = OneS1ghtBridge.isPaused;
  ```
</CodeGroup>

## セッションイベント

### .onPosition

ユーザーの座標をリアルタイムで返すイベントが発生します。エンジンの頻度のまま届き、毎秒 4 回（デフォルト）はサーバー送信分にのみ適用されます。

<CodeGroup>
  ```swift Swift icon="swift" theme={null}
  session.onPosition = { coordinates in print(coordinates.x, coordinates.y) }
  ```

  ```objc Objective-C icon="apple" theme={null}
  [OneS1ghtBridge onPosition:^(double x, double y, double z) { }];
  ```
</CodeGroup>

### .onZoneEnter · .onZoneExit

ゾーンへの進入・退出時にイベントが発生します。

<CodeGroup>
  ```swift Swift icon="swift" theme={null}
  session.onZoneEnter = { zone in print("IN \(zone.name)") }
  session.onZoneExit  = { zone in print("OUT \(zone.name)") }
  ```

  ```objc Objective-C icon="apple" theme={null}
  [OneS1ghtBridge onZoneEnter:^(NSDictionary *zone) { }];
  [OneS1ghtBridge onZoneExit:^(NSDictionary *zone) { }];
  ```
</CodeGroup>

### .onZoneDwell

特定ゾーンの滞在時間が基準（`dwellSeconds`）を超えるとイベントが発生します。

<CodeGroup>
  ```swift Swift icon="swift" theme={null}
  session.onZoneDwell = { zone, seconds in print("DWELL \(zone.name) \(seconds)s") }
  ```

  ```objc Objective-C icon="apple" theme={null}
  [OneS1ghtBridge onZoneDwell:^(NSDictionary *zone, double seconds) { }];
  ```
</CodeGroup>

### .onTriggers

空間内のイベント（施策）が動作するとイベントが発生します。

<CodeGroup>
  ```swift Swift icon="swift" theme={null}
  session.onTriggers = { zoneId, triggers in
      for trigger in triggers { print(trigger.type, trigger.payload ?? [:]) }
  }
  ```

  ```objc Objective-C icon="apple" theme={null}
  [OneS1ghtBridge onTriggers:^(NSString *zoneId, NSArray<NSDictionary *> *triggers) { }];   // triggerId · type · payload
  ```
</CodeGroup>

### .onFloorDetected

ロケーターの BLE 信号でフロアを見つけるとフロア ID で、フロアを見失うと `nil` でイベントが発生します。

<CodeGroup>
  ```swift Swift icon="swift" theme={null}
  session.onFloorDetected = { floorId in
      guard let floorId else { return }
      Task {
          let floor = try await OneS1ght.floor(buildingId: buildingId, floorId: floorId)
          try await OneS1ght.setFloorMap(floor, buildingId: buildingId)
      }
  }
  ```

  ```objc Objective-C icon="apple" theme={null}
  [OneS1ghtBridge onFloorDetected:^(NSString *floorId) {
      if (!floorId) { return; }
      [OneS1ghtBridge setFloorMapWithBuildingId:buildingId floorId:floorId completion:^(NSError *error) { }];
  }];
  ```
</CodeGroup>

### .onStopped

ユーザーの測位が停止するとイベントが発生します。

| 理由 | 意味 |
| - | - |
| `ended` | `end()`・`reset`・キーの変更 |
| `engineFailed` | 測位エンジンの起動失敗 |

<CodeGroup>
  ```swift Swift icon="swift" theme={null}
  session.onStopped = { reason in
      switch reason {
      case .ended:        break
      case .engineFailed: break
      @unknown default:   break
      }
  }
  ```

  ```objc Objective-C icon="apple" theme={null}
  [OneS1ghtBridge onStopped:^(NSString *reason) { }];   // @"ended" · @"engineFailed"
  ```
</CodeGroup>

### .onConfigChanged

コンソール画面で設定が変更されるとイベントが発生します。

| シグナル | 意味 |
| - | - |
| `zonesChanged(floorId)` | ゾーン変更 |
| `planChanged(floorId)` | 図面変更 |
| `rulesChanged(zoneId)` | 施策変更 |
| `sdkConfigChanged` | リモート設定変更 |
| `resyncNeeded` | 再接続・イベントの取りこぼし |

<CodeGroup>
  ```swift Swift icon="swift" theme={null}
  session.onConfigChanged = { change in
      switch change {
      case .zonesChanged, .resyncNeeded: Task { _ = await OneS1ght.refreshZones() }
      case .planChanged(let floorId):    print("redraw", floorId ?? "")
      default:                           break
      }
  }
  ```

  ```objc Objective-C icon="apple" theme={null}
  [OneS1ghtBridge onConfigChanged:^(NSString *kind, NSString *targetId) {
      if ([kind isEqualToString:@"zonesChanged"]) {
          [OneS1ghtBridge refreshZones:^(NSArray<NSDictionary *> *zones) { }];
      }
  }];
  ```
</CodeGroup>

## データ送信

座標は 300 件または 60 秒ごと、およびバックグラウンド移行時と `end()` 時に自動で送信されます。

### .uploadPendingPositions

溜めた座標を即時送信します。

<CodeGroup>
  ```swift Swift icon="swift" theme={null}
  await OneS1ght.uploadPendingPositions()
  ```

  ```objc Objective-C icon="apple" theme={null}
  [OneS1ghtBridge uploadPendingPositions:^{ }];
  ```
</CodeGroup>

### .discardPendingPositions

溜めた座標を破棄します。

<CodeGroup>
  ```swift Swift icon="swift" theme={null}
  OneS1ght.discardPendingPositions()
  ```

  ```objc Objective-C icon="apple" theme={null}
  [OneS1ghtBridge discardPendingPositions];
  ```
</CodeGroup>

## エラー処理

失敗は `SdkError`（SDK の状態）と `ApiError`（通信）で届きます。コードの意味は[エラーコード](/ja/sdk/troubleshooting/error-codes)を参照してください。Objective-C では `NSError.userInfo[@"sdkCode"]` に入ります。

<CodeGroup>
  ```swift Swift icon="swift" theme={null}
  do {
      try await OneS1ght.initialize(sdkKey: key)
  } catch let e as SdkError {
      print(e.code.rawValue)                  // "E1003"
  } catch let e as ApiError {
      print(e.code.rawValue, e.description)   // "E5005" · 理由
  }
  ```

  ```objc Objective-C icon="apple" theme={null}
  [OneS1ghtBridge initializeWithSdkKey:key completion:^(NSError *error) {
      NSString *code = error.userInfo[@"sdkCode"];   // @"E1003"
  }];
  ```
</CodeGroup>

## モデル

リストは `[T]`、バイト列は `Data` です。

### Coordinates

| フィールド | 型 | 説明 |
| - | - | - |
| `x`・`y` | `Double` | 図面ローカルのメートル |
| `z` | `Double` | 高さ |

### Building · Floor

| 型 | フィールド | 説明 |
| - | - | - |
| `Building` | `id`・`name` | 建物 ID・名前 |
| | `floorCount: Int?` | フロア数 |
| `Floor` | `id`・`name` | フロア ID・名前 |
| | `image: Data?` | 図面の PNG バイト |
| | `hasPlan: Bool` | 図面登録の有無 |
| | `originX`・`originY` | 図面原点（メートル） |
| | `widthM`・`heightM` | 図面サイズ（メートル） |
| | `minX`・`minY`・`maxX`・`maxY` | 配置範囲 |

### Locator · FloorLocators

| 型 | フィールド | 説明 |
| - | - | - |
| `Locator` | `address: Int` | UWB MAC の下位 2 バイト |
| | `x`・`y`・`z` | 図面ローカルのメートル |
| `FloorLocators` | `locators` | フロアのロケーター一覧 |
| | `sessionId: Int?` | UWB セッション — フロアごとに異なる |
| | `positioningReady: Bool` | ロケーターとセッションの両方があるか |

### Zone

| フィールド | 型 | 説明 |
| - | - | - |
| `id`・`name` | `String` | ゾーン ID・名前 |
| `polygon` | `[Position]` | 頂点（`x`、`y` メートル） |
| `dwellSeconds` | `Int?` | 滞在イベントの基準（秒） |

### Trigger

| フィールド | 型 | 説明 |
| - | - | - |
| `triggerId` | `String` | トリガー ID |
| `type` | `String` | `signage`・`coupon`・`tracking`・`merch`・`generic` |
| `payload` | `[String: String]?` | 種類ごとの付加情報 |

## Objective-C ブリッジ

以下のファイルをアプリターゲットに追加して 1 回ビルドすると、`<Product Module Name>-Swift.h` が生成されます。

```swift OneS1ghtBridge.swift theme={null}
// OneS1ghtBridge.swift: Objective-C から OneS1ght を呼ぶためのブリッジ
import Foundation
import OneS1ght

@MainActor @objc public final class OneS1ghtBridge: NSObject {

    // MARK: 初期化

    @objc public static func initialize(sdkKey: String, completion: @escaping (NSError?) -> Void) {
        Task {
            do { try await OneS1ght.initialize(sdkKey: sdkKey); completion(nil) }
            catch { completion(nsError(error)) }
        }
    }

    /// status: "authorized" | "denied" | "unsupported"
    @objc public static func requestPermission(_ completion: @escaping (String) -> Void) {
        Task { completion(String(describing: await OneS1ght.requestPermission())) }
    }

    @objc public static func setLanguage(_ code: String?) {
        OneS1ght.setLanguage(code)
    }

    @objc public static func reset(_ completion: @escaping () -> Void) {
        Task { await OneS1ght.reset(); completion() }
    }

    /// level: "log" | "info" | "warn" | "error"
    @objc public static func onDebugLog(_ handler: @escaping (String, String) -> Void) {
        OneS1ght.onDebugLog = { level, message in handler(String(describing: level), message) }
    }

    // MARK: 状態

    @objc public static var isInitialized: Bool { OneS1ght.isInitialized }

    /// "available" | "osVersionTooLow" | "deviceNotSupported"
    @objc public static var deviceAvailability: String { String(describing: OneS1ght.deviceAvailability) }

    @objc public static var isDeviceAvailable: Bool { OneS1ght.isDeviceAvailable }

    @objc public static var googleMapKey: String? { OneS1ght.googleMapKey }

    @objc public static var sdkVersion: String { OneS1ght.sdkVersion }

    // MARK: プロフィール

    @objc public static func createProfile(_ attributes: [String: String],
                                           completion: @escaping (String?, NSError?) -> Void) {
        Task {
            do { completion(try await OneS1ght.createProfile(attributes), nil) }
            catch { completion(nil, nsError(error)) }
        }
    }

    @objc public static func fetchProfile(_ profileId: String,
                                          completion: @escaping ([String: String]?, NSError?) -> Void) {
        Task {
            do { completion(try await OneS1ght.fetchProfile(profileId), nil) }
            catch { completion(nil, nsError(error)) }
        }
    }

    @objc public static func replaceProfile(_ profileId: String, attributes: [String: String],
                                            completion: @escaping (NSError?) -> Void) {
        Task {
            do { try await OneS1ght.replaceProfile(profileId, attributes: attributes); completion(nil) }
            catch { completion(nsError(error)) }
        }
    }

    @objc public static func deleteProfile(_ profileId: String, completion: @escaping (NSError?) -> Void) {
        Task {
            do { try await OneS1ght.deleteProfile(profileId); completion(nil) }
            catch { completion(nsError(error)) }
        }
    }

    @objc public static func identify(profileId: String?) {
        OneS1ght.identify(profileId: profileId)
    }

    // MARK: 空間の取得 — モデルは NSDictionary で渡す

    @objc public static func buildings(_ completion: @escaping ([[String: Any]]?, NSError?) -> Void) {
        Task {
            do { completion(try await OneS1ght.buildings().map(dict), nil) }
            catch { completion(nil, nsError(error)) }
        }
    }

    @objc public static func building(id: String, completion: @escaping ([String: Any]?, NSError?) -> Void) {
        Task {
            do { completion(dict(try await OneS1ght.building(id: id)), nil) }
            catch { completion(nil, nsError(error)) }
        }
    }

    @objc public static func floors(buildingId: String,
                                    completion: @escaping ([[String: Any]]?, NSError?) -> Void) {
        Task {
            do { completion(try await OneS1ght.floors(buildingId: buildingId).map(dict), nil) }
            catch { completion(nil, nsError(error)) }
        }
    }

    @objc public static func floor(buildingId: String, floorId: String,
                                   completion: @escaping ([String: Any]?, NSError?) -> Void) {
        Task {
            do { completion(dict(try await OneS1ght.floor(buildingId: buildingId, floorId: floorId)), nil) }
            catch { completion(nil, nsError(error)) }
        }
    }

    @objc public static func zones(buildingId: String, floorId: String,
                                   completion: @escaping ([[String: Any]]?, NSError?) -> Void) {
        Task {
            do { completion(try await OneS1ght.zones(buildingId: buildingId, floorId: floorId).map(dict), nil) }
            catch { completion(nil, nsError(error)) }
        }
    }

    @objc public static func zone(buildingId: String, floorId: String, zoneId: String,
                                  completion: @escaping ([String: Any]?, NSError?) -> Void) {
        Task {
            do {
                let zone = try await OneS1ght.zone(buildingId: buildingId, floorId: floorId, zoneId: zoneId)
                completion(dict(zone), nil)
            } catch { completion(nil, nsError(error)) }
        }
    }

    @objc public static func locators(buildingId: String, floorId: String,
                                      completion: @escaping ([String: Any]?, NSError?) -> Void) {
        Task {
            do { completion(dict(try await OneS1ght.locators(buildingId: buildingId, floorId: floorId)), nil) }
            catch { completion(nil, nsError(error)) }
        }
    }

    // MARK: フロアの指定

    /// floorId が nil ならフロア指定を解除する
    @objc public static func setFloorMap(buildingId: String?, floorId: String?,
                                         completion: @escaping (NSError?) -> Void) {
        Task {
            do {
                var floor: Floor?
                if let buildingId, let floorId {
                    floor = try await OneS1ght.floor(buildingId: buildingId, floorId: floorId)
                }
                try await OneS1ght.setFloorMap(floor, buildingId: buildingId)
                completion(nil)
            } catch { completion(nsError(error)) }
        }
    }

    @objc public static func refreshZones(_ completion: @escaping ([[String: Any]]) -> Void) {
        Task { completion(await OneS1ght.refreshZones().map(dict)) }
    }

    // MARK: 測位セッション

    @objc public static func begin(completion: @escaping (NSError?) -> Void) {
        Task {
            do { try await OneS1ght.floorSession().begin(); completion(nil) }
            catch { completion(nsError(error)) }
        }
    }

    @objc public static func end(completion: @escaping () -> Void) {
        Task {
            if let session = try? OneS1ght.floorSession() { await session.end() }
            completion()
        }
    }

    @objc public static func pause() { (try? OneS1ght.floorSession())?.pause() }

    @objc public static func resume() { (try? OneS1ght.floorSession())?.resume() }

    @objc public static var isRunning: Bool { (try? OneS1ght.floorSession())?.isRunning ?? false }

    @objc public static var isPaused: Bool { (try? OneS1ght.floorSession())?.isPaused ?? false }

    // MARK: セッションイベント

    /// 図面ローカルのメートル
    @objc public static func onPosition(_ handler: @escaping (Double, Double, Double) -> Void) {
        (try? OneS1ght.floorSession())?.onPosition = { c in handler(c.x, c.y, c.z) }
    }

    @objc public static func onZoneEnter(_ handler: @escaping ([String: Any]) -> Void) {
        (try? OneS1ght.floorSession())?.onZoneEnter = { zone in handler(dict(zone)) }
    }

    @objc public static func onZoneExit(_ handler: @escaping ([String: Any]) -> Void) {
        (try? OneS1ght.floorSession())?.onZoneExit = { zone in handler(dict(zone)) }
    }

    @objc public static func onZoneDwell(_ handler: @escaping ([String: Any], Double) -> Void) {
        (try? OneS1ght.floorSession())?.onZoneDwell = { zone, seconds in handler(dict(zone), seconds) }
    }

    /// triggers: [{ triggerId, type, payload }]
    @objc public static func onTriggers(_ handler: @escaping (String, [[String: Any]]) -> Void) {
        (try? OneS1ght.floorSession())?.onTriggers = { zoneId, triggers in
            handler(zoneId, triggers.map { t in
                var d: [String: Any] = ["triggerId": t.triggerId, "type": t.type]
                if let payload = t.payload { d["payload"] = payload }
                return d
            })
        }
    }

    @objc public static func onFloorDetected(_ handler: @escaping (String?) -> Void) {
        (try? OneS1ght.floorSession())?.onFloorDetected = { floorId in handler(floorId) }
    }

    /// reason: "ended" | "engineFailed"
    @objc public static func onStopped(_ handler: @escaping (String) -> Void) {
        (try? OneS1ght.floorSession())?.onStopped = { reason in handler(String(describing: reason)) }
    }

    /// kind: "zonesChanged" | "planChanged" | "rulesChanged" | "sdkConfigChanged" | "resyncNeeded"
    /// targetId: zonesChanged・planChanged はフロア ID、rulesChanged はゾーン ID
    @objc public static func onConfigChanged(_ handler: @escaping (String, String?) -> Void) {
        (try? OneS1ght.floorSession())?.onConfigChanged = { change in
            switch change {
            case .zonesChanged(let floorId): handler("zonesChanged", floorId)
            case .planChanged(let floorId):  handler("planChanged", floorId)
            case .rulesChanged(let zoneId):  handler("rulesChanged", zoneId)
            case .sdkConfigChanged:          handler("sdkConfigChanged", nil)
            case .resyncNeeded:              handler("resyncNeeded", nil)
            @unknown default:                handler("unknown", nil)
            }
        }
    }

    // MARK: データ送信

    @objc public static func uploadPendingPositions(_ completion: @escaping () -> Void) {
        Task { await OneS1ght.uploadPendingPositions(); completion() }
    }

    @objc public static func discardPendingPositions() {
        OneS1ght.discardPendingPositions()
    }

    // MARK: 変換

    private static func dict(_ b: Building) -> [String: Any] {
        var d: [String: Any] = ["id": b.id, "name": b.name]
        if let count = b.floorCount { d["floorCount"] = count }
        return d
    }

    private static func dict(_ f: Floor) -> [String: Any] {
        var d: [String: Any] = ["id": f.id, "name": f.name, "hasPlan": f.hasPlan,
                                "originX": f.originX, "originY": f.originY,
                                "widthM": f.widthM, "heightM": f.heightM]
        if let image = f.image { d["image"] = image }
        return d
    }

    private static func dict(_ z: Zone) -> [String: Any] {
        var d: [String: Any] = ["id": z.id, "name": z.name,
                                "polygon": z.polygon.map { [$0.x, $0.y] }]
        if let dwell = z.dwellSeconds { d["dwellSeconds"] = dwell }
        return d
    }

    private static func dict(_ l: FloorLocators) -> [String: Any] {
        var d: [String: Any] = ["locators": l.locators.map {
            ["address": $0.address, "x": $0.x, "y": $0.y, "z": $0.z] as [String: Any]
        }]
        if let sessionId = l.sessionId { d["sessionId"] = sessionId }
        return d
    }

    /// Swift のエラー → NSError。domain "OneS1ght"、code 3001、userInfo["sdkCode"] = "E3001" の形
    private static func nsError(_ error: Error) -> NSError {
        let code: SdkErrorCode
        let message: String
        switch error {
        case let e as SdkError: code = e.code; message = e.code.summary
        case let e as ApiError: code = e.code; message = e.description
        default: return error as NSError
        }
        return NSError(domain: "OneS1ght",
                       code: Int(code.rawValue.dropFirst()) ?? 0,
                       userInfo: [NSLocalizedDescriptionKey: message,
                                  "sdkCode": code.rawValue])
    }
}
```

## 制約

* UWB 測位はフォアグラウンド専用 — バックグラウンドで停止し、復帰すると測位が再開されます。
* 座標バッファはメモリ上 — 強制終了すると未送信の座標は失われます。
* シミュレーターでは測位できません。


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.