Hi Ziqiao, thanks for looking into this. I'll answer your question about our share flow, then share some significant findings from today's debugging.
We use a custom token-based invite flow rather than the standard system share URL tap → userDidAcceptCloudKitShareWith path. Here's the full flow:
How we deliver the share URL
Owner creates an invite token — stored as an InviteToken record in the public CloudKit database. The token record contains the CKShare.url string, the household ID, and a 7-day expiration.
Owner sends a deep link — the invite UI generates a mesa://invite?token=<token> URL and presents a share sheet (Messages, AirDrop, etc.). This is a custom URL scheme, not the CKShare.url directly.
Participant opens the deep link — the mesa:// URL opens our app. The app extracts the token, queries the public database for the InviteToken record, and reads the shareURL field to get the CKShare.url.
How we accept the share (programmatic path)
Once we have the CKShare.url from the token lookup:
// 1. Fetch share metadata
let operation = CKFetchShareMetadataOperation(shareURLs: [shareURL])
operation.perShareMetadataResultBlock = { _, result in
// Store metadata on success
}
container.add(operation)
// 2. Accept the share
try await container.accept(metadata)
We do not route through UICloudSharingController or the system share URL handler. The participant never taps the raw CKShare.url — they tap our mesa://invite deep link, and we handle acceptance programmatically.
We do implement userDidAcceptCloudKitShareWith
We have CKSharingSupported = YES in Info.plist and implement userDidAcceptCloudKitShareWith in our AppDelegate:
func application(_ application: UIApplication,
userDidAcceptCloudKitShareWith cloudKitShareMetadata: CKShare.Metadata) {
Task { @MainActor in
let success = await syncEngine.handleShareAcceptance(metadata: cloudKitShareMetadata)
// ...
}
}
However, this delegate method is not triggered in our flow because the participant never taps the CKShare.url directly — they tap our custom mesa://invite URL instead.
Why we use this flow
We need to pass additional context with the invite (household name, inviter name, linked member preferences) that can't be embedded in a CKShare.url
We want invites to be revocable (we can delete the InviteToken record)
We need invites to work even when the participant doesn't have the app installed yet (the mesa://invite URL has a universal link fallback to the App Store)
The key question
Is the programmatic CKFetchShareMetadataOperation → container.accept(metadata) path fully supported for zone-wide CKShare(recordZoneID:) shares? Or does zone-wide sharing require the system-level authentication that happens when a user taps the CKShare.url directly (triggering userDidAcceptCloudKitShareWith)?
If the system-level authentication is required, that would explain why our flow fails — we're bypassing it entirely. We could potentially switch to delivering the raw CKShare.url as a universal link and relying on userDidAcceptCloudKitShareWith, but I wanted to confirm whether that's the expected fix before restructuring the invite system.
Additional context from today's debugging
Since posting, I've made significant progress. I built a minimal test app (zone + share + accept, no SwiftData, no sync logic) against the same container, and the zone survived using this exact programmatic acceptance path. This told me the issue was in my app code, not the acceptance flow itself.
After systematically adding features back one at a time, I found the primary trigger: an orphan zone cleanup function that runs on every app launch. It enumerates private DB zones, compares against a cached "active zone name" in UserDefaults, and deletes non-matching zones. On multi-device setups (iPhone + iPad), each device independently caches the zone name. When the iPad woke from a background push notification (triggered by share activity on the iPhone), its stale cache caused it to delete the active zone as an "orphan."
I've fixed this by checking for an active CKRecordNameZoneWideShare before any zone deletion — zones with active CKShares are never deleted.
However, after 20+ zone create/delete cycles during debugging, the container now appears to be in a degraded state — new zones (even with completely fresh names never used before) are deleted by the server within seconds of creation, on a single device with no other apps running. I'm waiting for the container to stabilize (tombstone TTL) before further testing.
Thank you,
Josh
Topic:
App & System Services
SubTopic:
iCloud & Data
Tags: