This walkthrough covers Offline Reading in a SwiftUI App. A SwiftUI catalogue app with a working API client. This first version caches read-only records; offline mutations and synchronization are a separate design.
The video is an illustrated code walkthrough with AI narration. Code blocks are integration excerpts, not a complete tested application. Helper names such as stores, middleware and clients describe responsibilities you must implement in your existing project. No live server, payment provider or device execution is claimed.
Before you begin
Use a separate development environment, fictional data and test credentials. Record dependency versions and follow the official references below for your installed versions. Keep a working baseline and add one workflow at a time.
1. Choose an honest offline scope
Start with read-only offline access. A cached catalogue can remain useful when the network disappears without pretending that every server feature still works. Define which records are safe to keep on the device, how long they remain valid, and what must be removed on logout. Show the last successful refresh time so users can distinguish current data from an older local copy.
Online: fetch -> validate -> display -> cache
Offline: load last valid cache -> mark stale
No cache: explain unavailable content
Writes: disabled until a separate sync design exists2. Version the stored envelope
Store metadata alongside the cached records. A schema version lets future app releases reject or migrate incompatible files. An account identifier prevents one person's data from being loaded for another account. Save the successful refresh time rather than the last attempted request. Validate the envelope when reading it, and have a clear fallback for corruption instead of crashing during application startup.
struct CacheEnvelope: Codable {
let schemaVersion: Int
let savedAt: Date
let accountID: String
let records: [Kit]
}3. Write atomically
Write a complete new cache atomically so interruption doesn't leave a partially written file as the only copy. Keep the path inside an application-controlled storage location and choose a directory appropriate to whether the data is disposable or essential. Apply suitable platform data protection for sensitive records. This file is a cache, not a replacement for the authoritative server database.
let data = try JSONEncoder().encode(envelope)
try data.write(to: cacheURL, options: [.atomic])
// cacheURL is inside an app-controlled directory.
// Apply suitable file protection for the data.4. Load local data before refreshing
Load a valid local snapshot promptly, then attempt a network refresh. This avoids an empty screen while waiting for connectivity. Don't overwrite useful cached content with a loading placeholder unless the product requires it. If no valid cache exists, show a distinct first-load state. Account for cancellation and account changes so a response from an earlier session can't populate the current user's interface.
func start() async {
if let cached = try? cache.load(accountID: accountID) {
records = cached.records
lastUpdated = cached.savedAt
}
await refresh()
}5. Replace only after successful decoding
Treat a successful, validated server response as the point at which new data becomes authoritative. Decide how to handle a disk-write failure: the interface may still display fresh data while warning that offline storage wasn't updated. Don't turn a server error page into an empty catalogue. Preserve the previous snapshot when the request or decoding fails, and make that stale state visible.
let fresh = try await client.fetchKits()
try cache.save(fresh, accountID: accountID)
records = fresh
lastUpdated = Date()
errorMessage = nil6. Treat reachability as a hint
A network-path signal can suggest when to retry, but it doesn't prove that the API is reachable or authorized. Let the actual request determine the result. Debounce reconnect-triggered refreshes and avoid overlapping loads. Respect cancellation and use bounded retry behaviour. A quiet stale-content banner is often clearer than repeatedly presenting the same failure alert while the device remains offline.
Network path changed -> consider refresh
Actual request result -> decide success or failure
Avoid endless retries and repeated alerts
Use one coordinated load operation7. Protect account boundaries
Offline storage extends the lifetime of data on the device. Apply the same account boundaries as the online application. Clear or segregate cached records when users switch accounts, and consider whether some sensitive data should never be cached at all. If you later add offline edits, introduce identifiers, an outbox, retry rules, and conflict resolution deliberately. A read cache alone doesn't solve synchronization.
onLogout:
cancel pending loads
clear account-specific cache
clear in-memory records
reset refresh metadataIntegration checks
Test airplane mode with and without cache, corrupt files, schema changes, disk failure, stale responses after logout, reconnection and cancellation. Verify on simulator and physical device.
These are verification instructions, not reported test results. The excerpts have not been executed as a complete integrated application. Review authentication, database schema, dependency versions and operational settings before deployment.
Official references
Explore CodeKits development services.

