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

# Getting Started

> OneS1ght SDK for iOS

<Info>
  For installation and initialization, see the [SDK Quickstart](/en/sdk/quick-start).<br />
</Info>

## Receiving live positions

The session's `onPosition` callback delivers the user's coordinates. <br />
It fires independently of server uploads, up to 4 times per second.<br />
Coordinates are in **meters (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: floor-plan local coordinates (meters, origin at bottom-left) · coord.z: height
                    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: floor-plan local coordinates (meters, origin at bottom-left) · coord.z: height
            session.onPosition = { [weak self] coord in
                self?.updateMyLocationMarker(x: coord.x, y: coord.y)
            }
        }
    }
    ```
  </Tab>
</Tabs>

## Displaying the floor plan

Show the user the floor plan they are being positioned on. <br />You need a building ID and a floor ID to render it.
To draw the user's position on top of it, map the `onPosition` coordinates onto the plan.

<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` means no floor plan has been set up for that floor.<br />
  See [Guide — Floor plan placement · clusters](/en/geospace/placement).
</Note>

If you need the locator layout and the UWB session ID, look them up separately with `locators(_:_:)`.
When `positioningReady` is `false`, that floor has no locator or session data and positioning cannot start.

## Zone events

Enter, exit and dwell arrive as separate callbacks. The decision is made **on the device**, so there is no server round trip.

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

    session.onZoneEnter = { zone in banner = "enter: \(zone.name)" }
    session.onZoneExit  = { zone in banner = nil }
    session.onZoneDwell = { zone, seconds in
        banner = "dwell: \(zone.name) — \(Int(seconds))s"
    }
    ```
  </Tab>

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

| Callback      | When it fires                                 |
| ------------- | --------------------------------------------- |
| `onZoneEnter` | When the user enters a zone.                  |
| `onZoneExit`  | When the user leaves a zone.                  |
| `onZoneDwell` | While the user stays in a zone. (default: 5s) |

Zones for each floor are configured in [Space management](/en/locator/areas).

<Warning>
  If a floor has no zones at all, coordinates still accumulate but no enter or exit fires (`E3004`).
</Warning>

### Refreshing zone data

After you change and save zones in the console, refresh them from your code.

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

## Creating and connecting a profile

To collect a customer's movement, issue a profile for each account and keep the ID in your own storage.

```swift theme={null}
// 1. Issue a profile on the first visit.
// Profile attributes are used later to build segments in console reports — there is no restriction on the key-value pairs.
let profileId = try await OneS1ght.createProfile([
    "gender":   "F",
    "ageBand":  "20s",
    "interest": "cosmetics",
])

// 2. On a later visit, authenticate with the profile ID you already issued.
OneS1ght.identify(profileId: savedProfileId)

// 3. Stop collecting for this user and clear the profile.
OneS1ght.identify(profileId: nil)
```

| Method                 | Purpose                                                                  |
| ---------------------- | ------------------------------------------------------------------------ |
| `createProfile(_:)`    | Creates a new profile.                                                   |
| `getProfile(_:)`       | Reads the current profile attributes.                                    |
| `putProfile(_:_:)`     | Replaces the profile with new attributes — the previous data is removed. |
| `deleteProfile(_:)`    | Deletes the profile.                                                     |
| `identify(profileId:)` | Verifies that the profile is valid.                                      |

<Note>
  The issued profile ID must be stored by you. A lost profile ID cannot be recovered, so handle it with care.
</Note>

## Selecting a building and floor

To collect positioning data you must set a building and a floor.
One building can have several floors.

`setFloorMap` sets the locator data used for positioning and the map together with the zone data shown to the user.

```swift theme={null}
// Look up buildings and floors.
let buildings = try await OneS1ght.buildings()
let floors    = try await OneS1ght.floors(buildings[0].id)

// Set the initial map with the floor and building ID.
try await OneS1ght.setFloorMap(floors[0], buildingID: buildings[0].id)

// Switch to another floor during a live session to swap the map.
try await OneS1ght.setFloorMap(floors[1], buildingID: buildings[0].id)

// Clear the map once collection is finished.
try await OneS1ght.setFloorMap(nil)
```

<Note>
  Building and floor data requires `geoSdkKey` at initialization — without it you cannot look them up.
</Note>

## Handling unsupported devices

Using the OneS1ght SDK requires the device to meet the minimum requirements. [SDK Quickstart](/en/sdk/quick-start)<br />
Even when it is installed on an unsupported device, the app keeps working without any impact from the SDK.

<Note>
  `isDeviceAvailable` is the short form for checking whether the device can use the SDK, and it returns a Boolean (true/false).
</Note>

## Controlling data upload

The OneS1ght SDK uploads collected positioning data on its own schedule, but you can flush or drop the buffer immediately when you need to.

```swift theme={null}
await OneS1ght.send()      // Upload the positioning data now.
OneS1ght.empty()           // Drop the collected positioning data without uploading.
```

## Resetting the session

Discards the accumulated session and starts a new one.

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

## Using API keys per environment

In the OneS1ght console you can issue keys as Development or Production. <br />
The two environments are isolated and cannot reach into each other. <br />
Set the API key per environment so that test traffic does not mix into production data.

```swift theme={null}
#if DEBUG   // debug build
let sdkKey = "YOUR_DEVELOPMENT_API_KEY"
#else
let sdkKey = "YOUR_PRODUCTION_API_KEY"
#endif
try await OneS1ght.initialize(sdkKey: sdkKey, geoSdkKey: geoSdkKey)
```

<Note>
  Because of an iOS platform restriction, real positioning cannot be verified in the simulator. Build to a real device and test there.
</Note>

## Troubleshooting

<CardGroup cols={2}>
  <Card title="How to collect logs" icon="wrench" href="/en/sdk/faq/logging">
    How to work with logs during development.
  </Card>

  <Card title="SDK error codes" icon="triangle-exclamation" href="/en/sdk/faq/error-code">
    What the errors you hit during development mean.
  </Card>
</CardGroup>
