The Company App: One Business System Across Every Apple Device
Inventory, sales, purchasing, accounting, contacts, calendar, team chat, and operations—connected in one offline-first workspace for iPhone, iPad, and Mac.
Stack
SwiftUI · Core Data · CloudKit
Platform
iOS 17+ · iPadOS 17+ · macOS 14+
Architecture
Offline-First · Zero Backend
Outcome
6–8 tools → 1 system
Context
Small businesses operating with 8–25 employees across warehouse, office, and field environments face a compounding coordination problem: operations, finances, and team communication live in separate systems that do not talk to each other. Warehouse staff track stock in a spreadsheet. Finance runs another for accounting. Orders live somewhere else. Team communication happens in messaging apps. Dispatch is logged on paper.
The result is stale data, fulfilment errors, missed payments, and a management layer that spends its time reconciling systems instead of running the business. No single source of truth exists.
Problem
The team operated across 6–8 tools: spreadsheets for inventory and accounting, messaging apps for team coordination, a separate purchase-order system, paper dispatch logs, and email for customer communication. No tool communicated with another. Finance could not see order status, warehouse staff could not see what was on order, and dispatch did not know what had been received.
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 with participant-role enforcement at the persistence layer
- •Unified schema for inventory, sales, purchase orders, accounting, dispatch, contacts, calendar, and chat
- •Accounting figures derived automatically from sales, purchase, receipt, and expense records
- •Team Chat and shared calendar synced through the same CloudKit container as operational data
- •Operations hub for cargo, warehouse assignments, dispatches, returns, defects, and expenses
- •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
CloudKit Sharing and Role Enforcement
Team members join through an iCloud sharing invitation. Their access level—contributor or viewer—is enforced through CloudKit participant roles at the persistence layer, not only by hiding controls in the interface.
The owner’s private store and shared company store use separate NSPersistentCloudKitContainer configurations. Operational data lives in the shared store while personal preferences remain private. Once a participant accepts, the shared zone mirrors to their device automatically, with no separate account system or sign-in flow beyond iCloud.
Implementation detail
UICloudSharingController must be presented from the topmost view controller. Presenting from a root controller that already owns a sheet can fail silently, so the app walks the presentedViewController chain before presentation.
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.
- →6–8 operational tools consolidated into one system with a single source of truth
- →Accounting figures derived from sales and purchase records with no duplicate entry
- →Communication, calendar, and operational data synced across iPhone, iPad, and Mac
- →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.
Accounting is a view over operational data
Receivables, payables, cash position, and profit and loss are derived from the sales and purchase records that already drive operations. A normalized model prevents accounting totals drifting away from business reality.
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.