Progress and sync
The screens report what the user did. What the capture came to — the backend's tour id, how much was captured, how much has uploaded — is a separate, non-screen read, for a host that shows progress in its own UI or reconciles against its own job record.
Reading status
let status = try await tours.properties.status(of: property)
PropertyStatus:
| Property | Type | Meaning |
|---|---|---|
property | Property | The handle this status is for |
tourId | UUID? | nil until the property has synced for the first time |
sync | SyncState | .pending / .syncing / .synced / .failed |
floorCount | Int | Floors captured |
roomCount | Int | Rooms captured |
panoramaCount | Int | Panoramas captured |
panoramasUploaded | Int | Of those, how many reached the backend |
panoramasFailed | Int | Retried by the SDK on its own — only alarming if it stays non-zero |
isComplete | Bool | Synced, and every captured panorama uploaded |
Everything comes from local storage, so it is cheap and works offline.
Following it
To follow status rather than poll it, iterate statusUpdates(for:). It yields
the current value immediately and again on every change, driven by the
database's own change notifications, and finishes on its own when the property
is deleted or the task is cancelled:
statusTask = Task {
for await status in tours.properties.statusUpdates(for: property) {
jobRecord.tourId = status.tourId
progressView.update(uploaded: status.panoramasUploaded, of: status.panoramaCount)
}
}
Uploads retry themselves
Captured artifacts go into a durable queue that survives the app being suspended or killed. Each item is retried automatically up to three times, resuming on the next launch or background wake-up.
An item that exhausts its attempts stays failed — and stays visible as such in the screens the SDK vends — until the user taps sync again, which forgives the counters and re-drains the queue.
isRetryable says whether a call could succeed on a second attempt. It is not
a cue to retry uploads: retrying on top of the queue only duplicates work
already in flight.
Background upload
Uploads continue after your app is suspended, and iOS may relaunch the app to finish them. Two things are required of the host.
Forward the delegate call verbatim, unconditionally — this is the one entry
point that works before start() has finished:
func application(
_ application: UIApplication,
handleEventsForBackgroundURLSession identifier: String,
completionHandler: @escaping () -> Void
) {
tours.handleEventsForBackgroundURLSession(identifier: identifier, completionHandler: completionHandler)
}
It returns true if the session was the SDK's; false means the events are
still yours.
And register the background task:
tours.registerBackgroundSync()
The identifier the SDK uses is NoughtyTours.backgroundSyncTaskIdentifier — add
it to BGTaskSchedulerPermittedIdentifiers in your Info.plist.