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

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

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

## Initialization

### .initialize

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

```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) { }];
```

### .requestPermission

Requests the app permissions needed to use the positioning engine.

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

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

### .setLanguage

Sets the language of SDK log messages.\

`ko`: Korean,  `ja`: Japanese,  `en`: English, `nil`: system language

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

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

### .onDebugLog

Receives SDK logs.\

`level`: log level, `message`: log message

```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) { }];
```

### .reset

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

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

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

## State

### .isInitialized

Returns whether initialization succeeded.

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

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

### .deviceAvailability

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

| Value | Meaning |
| - | - |
| `available` | Positioning available |
| `osVersionTooLow` | Below iOS 27 |
| `deviceNotSupported` | Device without UWB |

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

### .googleMapKey

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

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

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

### .sdkVersion

Returns the SDK version in use.

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

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

## Profiles

### .createProfile

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

```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) { }];
```

### .identify

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

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

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

### .fetchProfile

Fetches the profile's attributes.

```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) { }];
```

### .replaceProfile

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

```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) { }];
```

### .deleteProfile

Permanently deletes the profile.

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

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

## Space lookup

### .buildings

Fetches every building that belongs to the tenant.

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

### .floors

Fetches the floors of a building.

```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) { }];
```

### .floor

Fetches the details of the selected floor.

```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"];
}];
```

### .zones

Fetches every zone on the selected floor.

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

### .locators

Fetches the locators on the selected floor.

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

## Setting the floor

### .setFloorMap

Sets the floor to start positioning on.

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

### .refreshZones

Fetches the latest zone data.

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

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

## Positioning session

### .floorSession

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

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

### .begin

Starts positioning the user.

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

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

### .end

Stops positioning the user.

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

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

### .pause · .resume

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

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

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

### .isRunning · .isPaused

Returns whether positioning is running and whether it is paused.

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

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

```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) { }];
```

### .onZoneEnter · .onZoneExit

Fires when the user enters or exits a zone.

```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) { }];
```

### .onZoneDwell

Fires when the dwell time in a zone exceeds the threshold (`dwellSeconds`).

```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) { }];
```

### .onTriggers

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

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

### .onFloorDetected

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

```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) { }];
}];
```

### .onStopped

Fires when positioning stops.

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

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

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

```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) { }];
    }
}];
```

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

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

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

### .discardPendingPositions

Discards buffered coordinates.

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

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

## Error handling

Failures arrive as `SdkError` (SDK state) and `ApiError` (communication). See [Error codes](/en/developers/sdk/troubleshooting/error-codes) for what each code means. In Objective-C the code is in `NSError.userInfo[@"sdkCode"]`.

```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"
}];
```

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