Skip to main content
Version: 0.3.0

Screens

You place one SDK screen, the entry point, in your own UINavigationController. Everything below it is the SDK's: it pushes and pops its screens on that same navigation controller, and reports what happened through one onEvent closure. The factories are async (they read your settings), @MainActor, and each one starts the SDK if you have not — so both can throw storageUnavailable(reason:).

Nothing is presented over your UI, and nothing asks you to navigate. A handler that ignores every event still has a working flow.

Entry points​

PropertyManagement, on tours.properties.

MethodStarts at
makePropertyListViewController(subtitle:onEvent:)The list of properties on the device
makePropertyViewController(property:onEvent:)One property, for a host that already knows what it is capturing

Both report PropertyFlowEvent.

let list = try await tours.properties.makePropertyListViewController(
subtitle: "Acme Ltd",
onEvent: { event in
if case .failed(let error) = event { present(error) }
}
)
navigationController.setViewControllers([list], animated: false)

subtitle is an optional line under the title. The returned screen is yours to decorate — bar-button items, a title view — and yours to place, as the root of your stack or pushed onto it.

The entry screen must be inside a navigation controller

The SDK pushes onto the navigation controller the entry screen is in. Outside one, the entry screen shows but opens nothing.

Plus four non-screen calls:

MethodPurpose
createProperty(name:floorCount:metadata:)Create a property, returns a Property handle
property(id:)Look one up; nil if it no longer exists
status(of:)One-shot PropertyStatus
statusUpdates(for:)AsyncStream<PropertyStatus>

Inside a flow​

From the list, selecting a property or creating one with + opens its property screen. From there the user maps floors and captures panoramas.

Every screen comes in the UI/UX version your SettingsProviderType.uiVersion() returns. In .v0, the default, the property screen lists the floors and opens floors and rooms on screens of their own, and + on the property list asks for a name and floor count. In .v1 the property screen shows every floor and room on one scrolling tree, and + asks only for a name. Both versions report the same events.

Structure capture​

Maps a floor's plan, then shows it for review. Confirming returns to the screen that started the capture and reports structureCaptureFinished; rescanning starts a fresh capture of the same floor. A floor that already has a plan can also have rooms added to it: the camera relocalizes into the floor's saved world map first ("Finding position…"), the rooms already on the floor keep their identity and their panoramas, and that review is confirm-only.

A RoomPlan or ARKit failure mid-scan does not end the flow. The active room is discarded, the session relocalizes, and the screen offers Retry, Build with N rooms or Go back — the rooms already finished are kept either way.

Panorama capture​

Captures panoramas across a floor, or in one room. Finishing returns to the screen that started it and reports panoramaCaptureFinished with how many panoramas that visit added. Backing out keeps anything captured before it.

Where a panorama is stitched​

SettingsProviderType.panoramaProcessing() chooses, with PanoramaProcessing as its value. The SDK reads it once as a panorama capture opens, so a change applies to the next capture, not to the one in progress.

ValueRecordedStitchedUploaded
.server, the defaultA movie, at your recordingResolution()By the platform, after the uploadThe recording
.onDeviceA 4K sweep, whatever recordingResolution() returnsOn the phoneThe finished panorama
extension MySettingsStore: SettingsProviderType {
func panoramaProcessing() async -> PanoramaProcessing { await processing }
}

Each case has a title, already localized, so a picker in your settings screen needs no strings of its own.

With .onDevice the user does not wait for a stitch. Each sweep joins a queue and is stitched in the background while the user captures the next one, and the capture screen counts what is still stitching. The queue runs only while your app is in the foreground: it pauses when the app is backgrounded and carries on where it stopped when the app returns. A stitch cut short by the app being killed starts again at the next launch.

A panorama stitched on the phone is uploaded once its stitch has finished — see Progress and sync.

In .v0, the Room screen shows a thumbnail and the status of each panorama, opens a stitched one in a full-screen viewer, and offers Retry stitch on one that failed. In .v1, the project tree lists each panorama with its sync state.

Panoramas the SDK discards​

A panorama is graded while it records. Tilt, sweep speed, shake, reversing the sweep and low light each warn on screen first, and a bad enough breach discards the scan: its folder is deleted, nothing reaches local storage or the upload queue, and the user gets the reason and a Try again. In very low light — ISO above 2000, or a shutter slower than 1/15s — the shutter is blocked before recording starts.

A discard is not reported to the host. It shows up only as a smaller capturedInSession than the user may describe, so count panoramas from that value or from PropertyStatus.

The screens title themselves

They know whether a floor is "Ground floor" or "Floor 2". You own the entry screen's place in your app and anything you add to its navigation bar.