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

## Import

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

## Initialization

### .initialize

Verifies your OneS1ght API key and loads your tenant settings. It does not start the positioning engine or check device support.

<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

Requests the app permissions needed to use the positioning engine.

| Result | Meaning |
| - | - |
| `authorized` | Positioning available |
| `denied` | Permission denied |
| `unsupported` | Device without positioning support |

<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

Sets the language of SDK log messages.<br />
`ko`: Korean,  `ja`: Japanese,  `en`: English, `nil`: system language

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

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

### .onDebugLog

Receives SDK logs.<br />
`level`: log level, `message`: log 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

Discards the session. APIs then throw `notInitialized` until `initialize` is called again (another key is allowed).

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

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

## State

### .isInitialized

Returns whether initialization succeeded.

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

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

### .deviceAvailability

Tells whether the device supports positioning and, if not, why.

| Value | Meaning |
| - | - |
| `available` | Positioning available |
| `osVersionTooLow` | Below iOS 27 |
| `deviceNotSupported` | Device without 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

Returns the Google Maps API key for displaying the floor plan.

<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

Returns the SDK version in use.

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

## Profiles

### .createProfile

Creates a new personalization profile. Returns a unique profile ID on success.

<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

Links the profile that collected positioning data is grouped under. Set `nil` to unlink.

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

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

### .fetchProfile

Fetches the profile's attributes.

<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

Replaces the profile's attributes. The previous data is deleted.

<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

Permanently deletes the profile.

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

## Space lookup

### .buildings

Fetches every building that belongs to the tenant.

<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

Fetches the floors of a building.

<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

Fetches the details of the selected 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

Fetches every zone on the selected floor.

<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

Fetches the locators on the selected floor.

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

## Setting the floor

### .setFloorMap

Sets the floor to start positioning on.

<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 = clear
  ```
</CodeGroup>

### .refreshZones

Fetches the latest zone data.

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

## Positioning session

### .floorSession

Gets the positioning session. It is one app-wide instance (singleton), not tied to a floor.

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

  ```objc Objective-C icon="apple" theme={null}
  // The bridge wraps the session — there is nothing to fetch separately.
  ```
</CodeGroup>

### .begin

Starts positioning the user.

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

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

### .end

Stops positioning the user.

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

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

### .pause · .resume

Pauses and resumes positioning. Unlike `end`, the positioning engine inside the SDK keeps running.

<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

Returns whether positioning is running and whether it is paused.

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

## Session events

### .onPosition

Fires with the user's coordinates in real time, at the engine's rate. The 4-per-second default applies only to server uploads.

<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

Fires when the user enters or exits a zone.

<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

Fires when the dwell time in a zone exceeds the threshold (`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

Fires when an in-space event (campaign) is triggered.

<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

Fires with the floor ID when the floor is found from locator BLE signals, and with `nil` when the floor is lost.

<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

Fires when positioning stops.

| Reason | Meaning |
| - | - |
| `ended` | `end()` · `reset` · key change |
| `engineFailed` | The positioning engine failed to run |

<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

Fires when settings change in the console.

| Signal | Meaning |
| - | - |
| `zonesChanged(floorId)` | Zones changed |
| `planChanged(floorId)` | Floor plan changed |
| `rulesChanged(zoneId)` | Campaigns changed |
| `sdkConfigChanged` | Remote config changed |
| `resyncNeeded` | Reconnected · events missed |

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

## Data upload

Coordinates are uploaded automatically every 300 entries or 60 seconds, as well as when the app goes to the background and on `end()`.

### .uploadPendingPositions

Uploads buffered coordinates immediately.

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

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

### .discardPendingPositions

Discards buffered coordinates.

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

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

## Error handling

Failures arrive as `SdkError` (SDK state) and `ApiError` (communication). See [Error codes](/en/sdk/troubleshooting/error-codes) for what each code means. In Objective-C the code is in `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" · reason
  }
  ```

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

## Models

Lists are `[T]`, bytes are `Data`.

### Coordinates

| Field | Type | Description |
| - | - | - |
| `x` · `y` | `Double` | Meters, local to the floor plan |
| `z` | `Double` | Height |

### Building · Floor

| Type | Field | Description |
| - | - | - |
| `Building` | `id` · `name` | Building ID · name |
| | `floorCount: Int?` | Number of floors |
| `Floor` | `id` · `name` | Floor ID · name |
| | `image: Data?` | Floor plan PNG bytes |
| | `hasPlan: Bool` | Whether a floor plan is registered |
| | `originX` · `originY` | Floor plan origin (meters) |
| | `widthM` · `heightM` | Floor plan size (meters) |
| | `minX` · `minY` · `maxX` · `maxY` | Placement bounds |

### Locator · FloorLocators

| Type | Field | Description |
| - | - | - |
| `Locator` | `address: Int` | Last 2 bytes of the UWB MAC |
| | `x` · `y` · `z` | Meters, local to the floor plan |
| `FloorLocators` | `locators` | Locators on the floor |
| | `sessionId: Int?` | UWB session — differs per floor |
| | `positioningReady: Bool` | Whether both locators and a session exist |

### Zone

| Field | Type | Description |
| - | - | - |
| `id` · `name` | `String` | Zone ID · name |
| `polygon` | `[Position]` | Vertices (`x`, `y` in meters) |
| `dwellSeconds` | `Int?` | Dwell event threshold (seconds) |

### Trigger

| Field | Type | Description |
| - | - | - |
| `triggerId` | `String` | Trigger ID |
| `type` | `String` | `signage` · `coupon` · `tracking` · `merch` · `generic` |
| `payload` | `[String: String]?` | Extra data per type |

## Objective-C bridge

Add the file below to your app target and build once; `<Product Module Name>-Swift.h` is generated.

```swift OneS1ghtBridge.swift theme={null}
// OneS1ghtBridge.swift: bridge for calling OneS1ght from Objective-C
import Foundation
import OneS1ght

@MainActor @objc public final class OneS1ghtBridge: NSObject {

    // MARK: Initialization

    @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: State

    @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: Profiles

    @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: Space lookup — models are passed as 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: Setting the floor

    /// A nil floorId clears the floor
    @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: Positioning session

    @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: Session events

    /// Meters, local to the floor plan
    @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: floor ID for zonesChanged·planChanged, zone ID for rulesChanged
    @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: Data upload

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

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

    // MARK: Conversion

    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 error → 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])
    }
}
```

## Limitations

* UWB positioning is foreground only — it stops in the background and resumes when the app returns.
* The coordinate buffer is in memory — unsent coordinates are lost if the app is force-quit.
* Positioning does not work on the simulator.


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