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.
| Method | Starts 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 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:
| Method | Purpose |
|---|---|
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.
| Value | Recorded | Stitched | Uploaded |
|---|---|---|---|
.server, the default | A movie, at your recordingResolution() | By the platform, after the upload | The recording |
.onDevice | A 4K sweep, whatever recordingResolution() returns | On the phone | The 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.
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.