> For the complete documentation index, see [llms.txt](https://docs.multiset.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.multiset.ai/native-sdk/ios-swift-native/sample-views/arlocalizationview.md).

# ARLocalizationView

The iOS AR localization view, covering single and multi frame modes, auto and background localization, recovery after a tracking break, run progress, GPS hints and mesh display.

## Overview

The `ARLocalizationView` provides AR-based localization using the MultiSetSDK framework and the `MultiSetAR` adapter. This view supports both single-frame and multi-frame localization modes, capturing frames from the AR camera and sending them to the MultiSet backend for pose estimation.

## Description

The AR localization view handles both localization modes:

**Single-Frame Localization** is ideal for scenarios where:

* Quick localization is needed
* The environment has distinct visual features
* Network bandwidth is limited
* Lower accuracy is acceptable

**Multi-Frame Localization** is ideal for scenarios where:

* Higher accuracy is required
* The environment has repetitive or sparse visual features
* The user can move the device slightly during capture
* More robust localization is needed

The view handles:

* AR session management through `MultiSetARView` (ARKit and RealityKit)
* Frame capture and processing (single or multi-frame)
* Run progress: the capture and server-wait overlays and the run status card
* Pose calculation and gizmo positioning
* Mesh visualization, with a loading bar and a visibility toggle
* Background localization and recovery after a tracking break
* GPS hint integration
* False-positive alerts and map corrections

***

## How It Works

### 1. Initialization Flow

```
ARLocalizationView appears
  └─> setupCallbacks()
        └─> Configure SDK callbacks for results and run stages
              └─> startGpsUpdates() (if GPS enabled)
                    └─> startAutoLocalizationIfNeeded()
```

### 2. Localization Flow

```
User taps Capture / Auto-localization triggers
  └─> MultiSet.shared.localize()
        └─> onLocalizationStageChanged: .capturing → .uploading → .awaitingResponse → .idle
              └─> onLocalizationSuccess / onLocalizationFailure / onLocalizationFalsePositive
                    └─> Gizmo moves, mesh loads
```

***

## Key Features

### Auto-Localization

When `MultiSetConfig.autoLocalize` is enabled, localization starts automatically after the AR session is ready (with a 1.5 second delay to ensure tracking stability).

### Background Localization

When `MultiSetConfig.backgroundLocalization` is enabled, the SDK periodically sends localization requests to refine positioning at the interval specified by `bgLocalizationDurationSeconds`. Background runs show only on the run status card.

### Recovery after a Tracking Break

When `MultiSetConfig.relocalization` is enabled, a tracking break after a fix hides the map mesh. Once ARKit tracks normally again the SDK re-localizes, retrying every second without alerts, and the mesh reappears with the new fix. Returning from the background starts a fresh world frame and recovers the same way. See [Recovery after a tracking break](/native-sdk/ios-swift-native/api-reference/lifecycle-and-runtimes.md#recovery-after-a-tracking-break).

### Run Progress

A run the user is waiting on (a tap, the first fix and its silent retries) covers the scene with `FrameCaptureOverlay` while frames are captured, then `ServerWaitOverlay` ("Uploading…", then "Matching against the map…"). Both sit under the controls, so the close button and the status card stay reachable. Every run, background ones included, also shows on the `RunStatusCard` at the top left.

### GPS Hint

When `MultiSetConfig.passGeoPose` is enabled, GPS coordinates are captured and sent as a hint to improve localization accuracy for large-scale maps. A `GpsIndicator` shows at the top left.

### Mesh Visualization

When `MultiSetConfig.meshVisualization` is enabled, a 3D mesh overlay with a radial reveal is rendered after successful localization. `MeshLoadingBar` shows while the mesh loads, and `MeshVisibilityToggle` hides or shows it once it is in the scene.

### Multi-Frame Capture

For multi-frame mode, captures multiple frames (configured via `numberOfFrames`) with intervals between captures (configured via `frameCaptureIntervalMs`). Each frame includes:

* Image data (JPEG compressed)
* Camera position (X, Y, Z)
* Camera rotation (quaternion)

***

## SDK Methods Used

### Localization Control

| Method                                            | Description                                                                                                          |
| ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `MultiSet.shared.localize()`                      | Triggers the localization process                                                                                    |
| `MultiSet.shared.stopLocalization()`              | Stops ongoing localization and cancels a pending retry                                                               |
| `MultiSet.shared.resetSession()`                  | Starts over: stops localization, removes every mesh, drops the pose consistency anchor and restarts the world origin |
| `MultiSet.shared.setMeshVisible(_:)`              | Shows or hides the map mesh (from the `MultiSetAR` folder)                                                           |
| `MultiSet.shared.setLocalizationMode(_:)`         | Changes localization mode at runtime                                                                                 |
| `MultiSet.shared.setQueryMode(_:)`                | Changes the matching pipeline (VPS-1 or VPS-2)                                                                       |
| `MultiSet.shared.resetPoseConsistencyReference()` | Forgets the anchor the false-positive check compares against                                                         |

### GPS Control

| Method                              | Description                 |
| ----------------------------------- | --------------------------- |
| `MultiSet.shared.startGpsUpdates()` | Starts GPS location updates |
| `MultiSet.shared.stopGpsUpdates()`  | Stops GPS location updates  |

### State Properties

| Property                             | Type              | Description                                                  |
| ------------------------------------ | ----------------- | ------------------------------------------------------------ |
| `MultiSet.shared.isLocalizing`       | `Bool`            | Whether localization is in progress or a silent retry is due |
| `MultiSet.shared.hasLocalized`       | `Bool`            | Whether localization has succeeded at least once             |
| `MultiSet.shared.isShowingOverlay`   | `Bool`            | Whether a run the user is waiting on is in progress          |
| `MultiSet.shared.hasMapMesh`         | `Bool`            | Whether a map mesh is in the scene                           |
| `MultiSet.shared.isMapMeshLoading`   | `Bool`            | Whether a map mesh is being fetched or built                 |
| `MultiSet.shared.watermarkClearance` | `CGFloat`         | Height to lift bottom-left controls above the watermark      |
| `MultiSet.shared.config`             | `MultiSetConfig?` | Current SDK configuration                                    |

***

## State Management

### View State Properties

| Property                      | Type                | Description                                                |
| ----------------------------- | ------------------- | ---------------------------------------------------------- |
| `isTrackingNormal`            | `Bool`              | Whether AR tracking state is normal                        |
| `hasLocalized`                | `Bool`              | Whether localization has succeeded                         |
| `stage`                       | `LocalizationStage` | The current run's stage, from `onLocalizationStageChanged` |
| `isForegroundRun`             | `Bool`              | A run the user is waiting on is in progress                |
| `isLocalizing`                | `Bool`              | Polled from SDK: localization in progress                  |
| `isShowingOverlay`            | `Bool`              | Polled from SDK: showing the run overlay                   |
| `hasMapMesh`, `isMeshLoading` | `Bool`              | Polled from SDK: mesh in the scene, mesh loading           |
| `showCloseConfirmation`       | `Bool`              | Controls close confirmation alert                          |
| `showToast`                   | `Bool`              | Controls toast message visibility                          |

### State Polling

The view polls SDK state every 0.1 seconds to update UI:

```swift
let stateTimer = Timer.publish(every: 0.1, on: .main, in: .common).autoconnect()

.onReceive(stateTimer) { _ in
    isLocalizing = MultiSet.shared.isLocalizing
    isShowingOverlay = MultiSet.shared.isShowingOverlay
    hasMapMesh = MultiSet.shared.hasMapMesh
    isMeshLoading = MultiSet.shared.isMapMeshLoading
    watermarkClearance = MultiSet.shared.watermarkClearance
}
```

***

## Callbacks

### onLocalizationStageChanged

Drives the overlays and the run status card:

```swift
sdkDelegate.onLocalizationStageChanged = { newStage, isBackground in
    stage = newStage
    isForegroundRun = newStage != .idle && !isBackground
    isShowingOverlay = MultiSet.shared.isShowingOverlay
    runStatus.receive(RunStatusState(newStage))
}
```

### onLocalizationSuccess

Handles successful localization result:

```swift
sdkDelegate.onLocalizationSuccess = { result in
    hasLocalized = true
    runStatus.receive(.succeeded)
    if MultiSet.shared.config?.showAlerts == true {
        showToastMessage("Localization successful", success: true)
    }
}
```

### onLocalizationFailure

Handles localization failure with an alert carrying a user-friendly message:

```swift
sdkDelegate.onLocalizationFailure = { error in
    runStatus.receive(.failed)
    if MultiSet.shared.config?.showAlerts == true {
        failureAlertMessage = userFriendlyMessage(for: error)
        showFailureAlert = true
    }
}
```

### onLocalizationFalsePositive

Handles a response the pose consistency check discarded. This fires instead of `onLocalizationSuccess`, and only when `poseConsistencyCheck` is enabled. The AR scene is left untouched, so the gizmo and mesh keep the last accepted pose:

```swift
sdkDelegate.onLocalizationFalsePositive = { info in
    runStatus.receive(.failed)
    guard MultiSet.shared.config?.showAlerts == true else { return }
    showToastMessage("Localization response received", success: true)
    falsePositiveMessage = falsePositiveText(for: info)
    showFalsePositiveAlert = true
}
```

The toast reports that a response came back; the alert explains what was done with it. On `.suspectedAliasing` it says that localizing again will not help, otherwise it asks the user to move to a distinctive area, and after three rejections in a row it points to the reset. Its buttons are **Localize Again**, **Reset & Start Over** (which calls `MultiSet.shared.resetSession()`, the exit from a wrong first fix) and **Dismiss**. See [MultiSetCallback](/native-sdk/ios-swift-native/api-reference/multisetcallback.md#falsepositiveinfo).

### onLocalizationCorrected

Agreeing fixes moved the anchor. The gizmo and mesh follow it automatically; the view shows how far:

```swift
sdkDelegate.onLocalizationCorrected = { from, to, trust in
    guard MultiSet.shared.config?.showAlerts == true else { return }
    showToastMessage(
        String(format: "Map re-anchored %.1f m (%@)", simd_distance(from.position, to.position), String(describing: trust)),
        success: true
    )
}
```

### onTrackingChanged

Handles AR tracking state changes:

```swift
sdkDelegate.onTrackingChanged = { state in
    isTrackingNormal = (state == .tracking)
}
```

***

## Actions

### startLocalization()

Manually triggers the localization process:

```swift
private func startLocalization() {
    MultiSet.shared.localize()
}
```

### resetWorldOrigin()

Starts the session over and restarts auto-localization:

```swift
private func resetWorldOrigin() {
    MultiSet.shared.resetSession()
    hasLocalized = false
    showToastMessage("World origin reset", success: true)

    // Restart auto-localization if enabled
    startAutoLocalizationIfNeeded()
}
```

### cleanup()

Cleans up resources when view disappears:

```swift
private func cleanup() {
    MultiSet.shared.stopLocalization()
    MultiSet.shared.stopGpsUpdates()
}
```

***

## Configuration

The view reads configuration from `MultiSet.shared.config`:

```swift
// Example configuration settings
config.localizationMode = .multiFrame       // or .singleFrame
config.queryMode = .vps1                    // .vps1 or .vps2, both modes
config.autoLocalize = true
config.backgroundLocalization = true
config.bgLocalizationDurationSeconds = 30.0 // 10 - 180
config.relocalization = true
config.numberOfFrames = 4                   // For multi-frame
config.frameCaptureIntervalMs = 500         // For multi-frame
config.confidenceCheck = true
config.confidenceThreshold = 0.3
config.firstLocalizationUntilSuccess = true // up to 5 silent retries
config.showAlerts = true
config.meshVisualization = true
config.passGeoPose = false
config.geoCoordinatesInResponse = false
config.imageQuality = 90
```

***

## Usage Example

### Basic Usage

```swift
import SwiftUI
import MultiSetSDK

struct MyARView: View {
    @ObservedObject var sdkDelegate: MultiSetSDKDelegate
    let localizationMode: LocalizationMode

    var body: some View {
        ZStack {
            // AR View from the MultiSetAR folder
            MultiSetARView()
                .ignoresSafeArea()

            // Capture button
            VStack {
                Spacer()
                Button("Localize") {
                    MultiSet.shared.localize()
                }
                .padding()
            }
        }
        .onAppear {
            setupCallbacks()
        }
    }

    private func setupCallbacks() {
        sdkDelegate.onLocalizationSuccess = { result in
            print("Localized at: \(result.position)")
        }
    }
}
```

### Navigating from LandingView

```swift
NavigationStack {
    // ... LandingView content ...
}
.navigationDestination(isPresented: $navigateToAR) {
    ARLocalizationView(
        sdkDelegate: sdkDelegate,
        localizationMode: selectedMode
    )
    .navigationBarBackButtonHidden(true)
}
```

***

## Comparison: Single-Frame vs Multi-Frame

| Feature                 | Single-Frame       | Multi-Frame         |
| ----------------------- | ------------------ | ------------------- |
| Frames captured         | 1                  | 4-6 (configurable)  |
| Capture time            | Instant            | \~2-3 seconds       |
| Accuracy                | Good               | Better              |
| Network usage           | Lower              | Higher              |
| Query mode              | VPS-1 or VPS-2     | VPS-1 or VPS-2      |
| Movement during capture | Not required       | Recommended         |
| Best for                | Quick localization | Precise positioning |

***

## Lifecycle

### onAppear

* Sets up SDK callbacks for results and run stages
* Starts GPS updates if enabled
* Triggers auto-localization if enabled

### onDisappear

* Stops localization
* Stops GPS updates
* Cleans up resources

### Timer

* Polls SDK state every 0.1 seconds for UI updates

***

## Overlay Components

### FrameCaptureOverlay

Shows while a run the user is waiting on captures frames:

* Semi-transparent black background
* Background AR illustration with an animated phone icon
* A scanning instruction
* Status text showing capture progress, for example "Capturing image 2 of 4"

### ServerWaitOverlay

Shows while the query is with the server:

* Semi-transparent black background
* Circular progress spinner
* "Uploading…", then "Matching against the map…"

### RunStatusCard

A small card at the top left for every run, background ones included: capturing, uploading, waiting on the server, then a tick or a red mark that fades.

***

## Related

* [LandingView](/native-sdk/ios-swift-native/sample-views/landingview.md)
* [MultiSetConfig](/native-sdk/ios-swift-native/api-reference/multisetconfig.md)
* [MultiSetCallback](/native-sdk/ios-swift-native/api-reference/multisetcallback.md)
* [LocalizationResult](/native-sdk/ios-swift-native/api-reference/localizationresult.md)
* [Lifecycle and AR Runtimes](/native-sdk/ios-swift-native/api-reference/lifecycle-and-runtimes.md)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.multiset.ai/native-sdk/ios-swift-native/sample-views/arlocalizationview.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
