Skip to content

Asynchronous Timeline data

iOS overview · Gantt · Shared foundations

The SwiftUI integration supports an optional async data provider. Your app supplies all resources up front and owns the backend, authentication, persistence and saved entry data. Without a provider, the existing setData integration stays synchronous.

Configure a provider

Create and retain a model on the main actor, initially with resources and no entries:

swift
// In a throwing @MainActor setup function; resources are consumer-owned.
let initial = try TimelineData(resources: resources, entries: [], assignments: [])
let timeline = try TimelineModel(start: CivilDate("2026-09-09"), data: initial, days: 90)
timeline.setDataProvider { query in
    // This Sendable async closure can call your actor/service.
    // Return a complete response for query.start..<query.end and query.resourceIDs.
    try await service.loadTimeline(query)
}
// In your view, retaining the same model:
TimelineView(model: timeline, today: today)

TimelineDataProvider is @Sendable (TimelineQuery) async throws -> TimelinePage. An actor or other concurrency-safe service should own mutable provider state. The library contains no HTTP client or server URL. The sample service above is supplied by your app, with a loadTimeline method returning TimelinePage.

setDataProvider(nil) disables loading and retains confirmed data. Replacing a provider cancels pending work and reloads the current query, when available. Call reload() to retry or explicitly refresh an otherwise identical query.

Request and response contract

TimelineQuery contains floating start, exclusive end and ordered, unique resourceIDs. Its initializer validates the interval and resource identifiers. Return all assignments for the requested resources whose entries overlap the query, including entries starting before its start. Keep original entry intervals; do not clip the response's event times to the viewport.

swift
return TimelinePage(entries: matchingEntries, assignments: matchingAssignments)

The page deliberately has no resource list. Before publication, IDs/references are validated against the requested subset of the consumer's resources. Every returned entry must overlap the requested interval. Assignments must reference returned entries and requested resources. Duplicate IDs and invalid references fail the load atomically. An empty successful page means no assignments in that interval/resource subset; throwing means the existing data must be retained.

Viewport and lifecycle

The grid requests the visible time columns with one column of buffer on either side, clipped to the selected seven-column window. Resources come from the canvas viewport with three leading/trailing resource rows of buffer. Collapsed resources are excluded. The large-text resource list requests all expanded resources for the selected window. The resources themselves are not server-paginated.

Requests are coalesced for 80 ms; consecutive equal queries do not load again. Superseded work receives Swift task cancellation, and a generation check also rejects providers that complete after cancellation. Your provider should propagate cancellation and check it when doing additional work. No resources means no call.

A valid window/grouping change invalidates pending reads. Invalid navigation preserves current state. TimelineView requests on appearance/query changes and cancels on disappearance. Retaining the model across component switches retains confirmed data, and reappearance can load again. For a headless/custom renderer, call requestData(query) with your visible subset and suspendLoading() when it is no longer active. Supply a fresh query after changing the selected window. Queries outside that window or containing unknown resources fail without invoking the provider.

Loading, errors and cache

Read loadState: .idle, .loading or .failed. The native view shows a localized loading indicator or error with Retry. Loading/error states preserve confirmed entries; cancellation is silent. A failed request does not automatically retry.

Successful pages replace assignments overlapping the query for its resource subset, retain other loaded portions of the current window and prune orphaned entries. Incoming assignment IDs replace their cached versions, including entries that have moved into the requested interval. Shared entry IDs remain one object across their assignments. This bounded window cache keeps previously loaded rows from repeatedly shrinking while scrolling. It is not persistent storage.

The first successful response for a different window or provider atomically replaces the previous window cache. There is no accumulating cache across navigated windows. An old visible entry while loading is previously confirmed data, not proof that the latest request has completed.

Confirmed edits and read freshness

Your app still owns edit confirmation and saving. Validate previous, persist or update your authoritative store, then publish the confirmed snapshot using setData. That call invalidates pending reads before updating the display, so a response started before confirmation cannot overwrite the edit. It does not silently reissue the identical query. A later viewport change or explicit reload may read again.

Future reads must return your updated authoritative data. The package cannot infer server freshness or resolve eventually consistent writes without a version contract. There is no automatic optimistic merge or asynchronous save protocol. Keep the provider and your confirmation path backed by the same authoritative store. The demo does this in memory; it does not persist after restart.

Try it in the demo

Enable Daten nachladen / Load data asynchronously. Choose a simulated delay and use Ladefehler simulieren / Simulate loading failure, then Retry. The demo shows request count, resource count and requested interval. Scroll, change scale, navigate and switch components; confirmed edits remain in the simulated backing store. Disable loading to return to the full local snapshot.

Finite horizons use start plus days; provider queries still expose exact start/end bounds for buffered visible columns, clipped to the configured exclusive end.

ChronoSketch · Calendar and scheduling components