The Company App: Offline-First Operations Platform
How a single Core Data schema with CloudKit sync replaced 4–5 disconnected tools for small business teams — without a custom backend, without a server, and without any third-party infrastructure.
Stack
SwiftUI · Core Data · CloudKit
Platform
iOS 16+ · iPadOS 16+
Architecture
Offline-First · Zero Backend
Outcome
4–5 tools → 1 system
Context
Small businesses operating with 8–25 employees across warehouse and office environments face a structural coordination problem: the data they need to run operations — inventory levels, order status, dispatch assignments — lives in multiple disconnected systems. Warehouse staff update a spreadsheet. Office staff check a different one. Dispatch logs are handwritten. Customer communication happens over email.
No single source of truth exists. Teams routinely act on stale data. Fulfillment errors are a predictable consequence of the architecture, not a failure of individual effort.
Problem
The team operated across 4–5 tools: spreadsheets for inventory, messaging apps for team coordination, paper dispatch logs, and email for client communication. No tool communicated with another. Warehouse and office staff frequently worked from stale data — a fulfillment pick might reference inventory that had already been committed to a different order.
The instinct is to solve this with a web platform and a shared database. The problem: warehouse connectivity is unreliable. A system that requires a server connection to function would fail at the point where it matters most — on the warehouse floor during a stock count or dispatch run.
Constraints
- —Warehouse connectivity is unreliable — fully offline operation was required, with automatic sync on reconnection
- —Multi-user concurrent writes required conflict resolution without a custom backend
- —Sensitive business data (supplier contacts, pricing, order volumes) could not transit third-party services
- —No IT department on the client side — zero-maintenance sync infrastructure was a hard requirement
- —iPad form factor needed to work in warehouse scanning contexts: large tap targets, minimal navigation depth
Architecture Approach
The constraint that shaped everything: warehouse staff cannot wait for a server response. Offline-first was not a feature request — it was the design premise. Every architectural decision flows from that.
Data Layer
NSPersistentCloudKitContainer was the clear choice: a single class that wraps Core Data, automatically mirrors to CloudKit, and requires no backend infrastructure. Writes go to local Core Data first — the app is fully functional offline. Sync happens transparently when connectivity is available.
The schema covers the full operational surface: inventory items, stock movements, orders, order line items, dispatch assignments, customer contacts, and team tasks — all in one unified Core Data model. This means any view in the app can compose across domains without network calls.
CloudKit Store Design
Two CloudKit stores run in parallel. The private store holds per-user settings and preferences — it syncs only to the authenticated user's iCloud account. The shared store holds the operational data — inventory, orders, dispatch — accessible to all team members via CloudKit Sharing participant roles.
Role-based access control is enforced at the persistence layer through CloudKit participant roles (owner, contributor, viewer), not at the UI layer. A warehouse worker cannot modify order pricing; the data model makes it structurally impossible, not just hidden.
Conflict Resolution
Concurrent offline writes from multiple users are resolved through Core Data's merge policy. The app uses NSMergeByPropertyObjectTrumpMergePolicy for most records — last-writer-by-timestamp wins at the property level. For inventory quantities (where concurrent increments/decrements must not overwrite each other), stock movements are recorded as individual events rather than mutating a single quantity field. The current quantity is derived by summing all movement records — a log-structured approach that makes concurrent writes commutative.
Implementation: Offline-Aware UI State
Every view that shows synced data needs to communicate its sync state to the user — not as a banner, but inline. The pattern used across the app surfaces pending changes count and last-sync timestamp as lightweight metadata attached to each list item:
// Observe CloudKit sync events via NotificationCenter
// and surface pending record count to UI without blocking
class SyncMonitor: ObservableObject {
@Published var pendingCount: Int = 0
@Published var lastSyncDate: Date?
init(container: NSPersistentCloudKitContainer) {
NotificationCenter.default.addObserver(
forName: NSPersistentCloudKitContainer
.eventChangedNotification,
object: container,
queue: .main
) { [weak self] notification in
guard let event = notification.userInfo?[
NSPersistentCloudKitContainer.eventNotificationUserInfoKey
] as? NSPersistentCloudKitContainer.Event else { return }
if event.succeeded {
self?.lastSyncDate = event.endDate
}
}
}
}
// In SwiftUI view: show offline badge when pending > 0
struct InventoryRowView: View {
let item: InventoryItem
@ObservedObject var syncMonitor: SyncMonitor
var body: some View {
HStack {
VStack(alignment: .leading) {
Text(item.name)
Text("\(item.quantity) units")
.foregroundStyle(.secondary)
}
Spacer()
if syncMonitor.pendingCount > 0 {
Image(systemName: "arrow.triangle.2.circlepath")
.foregroundStyle(.orange)
.imageScale(.small)
}
}
}
}Solution Highlights
- •NSPersistentCloudKitContainer — zero-backend sync through Apple infrastructure, no server costs
- •Separate private/shared CloudKit stores for role-based data isolation at the persistence layer
- •Log-structured stock movements for conflict-safe concurrent inventory updates
- •Partial sync: field staff receive only their assigned dispatch subset via CloudKit record zones
- •iPad split-view layouts with large tap targets and offline-aware status indicators
- •Zero third-party dependencies — every SDK is a first-party Apple framework
Outcome
Deployed across teams of 8–15 employees managing inventory at multiple warehouse locations. The system handles offline operation for hours during connectivity outages and syncs automatically on reconnection — no manual intervention required.
- →4–5 operational tools consolidated into one system with a single source of truth
- →Stock discrepancies from stale data dropped in the first month of deployment
- →Offline operation during warehouse connectivity outages: hours of uninterrupted use
- →Zero infrastructure to maintain — CloudKit handles sync, Apple handles availability
- →No server costs: CloudKit storage is included in iCloud subscriptions the business already pays
"The constraint that shaped the entire architecture: warehouse staff cannot wait for a server response. Offline-first was not a feature — it was the design premise."
Key Technical Learnings
Model mutations as events, not state
For any field that multiple users might change concurrently (stock quantities, order status), record the delta events rather than mutating a single value. Events are commutative; property overwrites are not.
NSPersistentCloudKitContainer is not a magic sync layer
Conflict resolution requires deliberate schema design. Records with large blob properties (images, PDFs) should live in separate entities to avoid CloudKit's record size limits becoming merge-time failures.
Offline-aware UI is not optional
Users who don't know they're offline will assume the data they see is current. A subtle sync-state indicator on every list item — not a banner — keeps the mental model accurate without adding cognitive overhead.
Partial sync requires CloudKit zones
CloudKit's record zone model is the right primitive for scoping data to individual users. Design your zone structure before your entity structure — retrofitting zones into an existing schema is painful.
Technical FAQ
How does offline-first sync work with NSPersistentCloudKitContainer?↓
Why choose NSPersistentCloudKitContainer over a custom backend?↓
What is the performance cost of offline-first Core Data?↓
Building something with similar constraints?
If you need an offline-first iOS system with multi-user sync, an architecture audit can identify the right data layer approach before you commit to a design.