Skip to main content

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:

PropertyTypeMeaning
propertyPropertyThe handle this status is for
tourIdUUID?nil until the property has synced for the first time
syncSyncState.pending / .syncing / .synced / .failed
floorCountIntFloors captured
roomCountIntRooms captured
panoramaCountIntPanoramas captured
panoramasUploadedIntOf those, how many reached the backend
panoramasFailedIntRetried by the SDK on its own — only alarming if it stays non-zero
isCompleteBoolSynced, 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.

Do not build your own retry loop

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.