CloudKit ships a data-sharing API, so users can share a whole record zone with other users. Put related records into one custom zone in the private database, then share the zone: every record inside travels with it.
Create the Zone and Save Records Into It
A zone is a named bucket inside the current user's private database. Save the zone first, then save records whose recordID points at that zone.
private enum SharedZone {
static let name = "SharedZone"
static let ID = CKRecordZone.ID(
zoneName: name,
ownerName: CKCurrentUserDefaultName
)
}
extension Fasting {
var asRecord: CKRecord {
let record = CKRecord(
recordType: "Fasting",
recordID: .init(zoneID: SharedZone.ID)
)
record["goal"] = goal.rawValue
record["startDate"] = startDate
record["endDate"] = endDate
return record
}
}
final class CloudKitService {
static let container = CKContainer(identifier: "iCloud.com.example.app")
func save(_ fasting: Fasting) async throws {
_ = try await Self.container.privateCloudDatabase.modifyRecordZones(
saving: [CKRecordZone(zoneName: SharedZone.name)],
deleting: []
)
_ = try await Self.container.privateCloudDatabase.modifyRecords(
saving: [fasting.asRecord],
deleting: []
)
}
}
Share the Zone With CKShare
Create one CKShare for the zone and save it into the private database. The default publicPermission is .none, which blocks access until the owner approves a participant.
func shareFastingRecords() async throws -> CKShare {
let share = CKShare(recordZoneID: SharedZone.ID)
share.publicPermission = .readOnly
let result = try await Self.container.privateCloudDatabase.save(share)
return result as! CKShare
}
A zone can hold only one CKShare. Check for an existing share and fetch it before creating a new one.
Present the share with UICloudSharingController, wrapped in UIViewControllerRepresentable for SwiftUI. Its availablePermissions set (allowReadOnly, allowPrivate, allowReadWrite, allowPublic) controls what the owner can grant.
struct CloudKitShareView: UIViewControllerRepresentable {
let share: CKShare
func makeUIViewController(context: Context) -> UICloudSharingController {
let controller = UICloudSharingController(
share: share,
container: CloudKitService.container
)
controller.availablePermissions = [.allowReadOnly, .allowPrivate]
return controller
}
func updateUIViewController(_ vc: UIViewControllerType, context: Context) {}
}
Accept the Share
Add the CKSharingSupported boolean key with value YES to Info.plist, or the invite link does nothing. When the invited user opens the link, the system calls the scene delegate, and you accept the metadata.
func windowScene(
_ windowScene: UIWindowScene,
userDidAcceptCloudKitShareWith metadata: CKShare.Metadata
) {
Task { try await CloudKitService.container.accept(metadata) }
}
Fetch Shared Records
Shared content lives in the participant's sharedCloudDatabase, not the private one. Enumerate every zone there and query each: two zones can carry the same name but different owners, so never fetch by name alone.
let sharedZones = try await Self.container.sharedCloudDatabase.allRecordZones()
for zone in sharedZones {
let response = try await Self.container.sharedCloudDatabase.records(
matching: query,
inZoneWith: zone.zoneID,
desiredKeys: nil,
resultsLimit: CKQueryOperation.maximumResults
)
}