Search code examples
iosswifticloudcloudkit

CloudKit - How to share multiple records and retrieve shared records?


I watched last WWDC 2016 What's New with CloudKit to understand how to share records with other users using CKShare

Single record sharing:

I am able to share and retrieve a single record

i.e if [email protected] has created and shared a single record to [email protected]

Multiple records sharing:

let's say there are 10 records and [email protected] wants to share to [email protected]. I am facing the issue when user [email protected] shares multiple records to user [email protected]

What I have tried so far:

First I created 3 note records:

Note1

Note2 ( set parent as Note1 )

Note3 ( set parent as Note1 )

I shared Note1 ( Parent record ) with below code:

CODE - Share record

let controller = UICloudSharingController { controller,
        preparationCompletionHandler in

        let share = CKShare(rootRecord: self.parentRecord!)
        share[CKShareTitleKey] = "note" as CKRecordValue
        share.publicPermission = .readOnly

        let operation = CKModifyRecordsOperation(
              recordsToSave: [self.parentRecord!, share],
              recordIDsToDelete: nil)

        operation.perRecordCompletionBlock = { (record, error) in
            if let error = error {
                print("CloudKit error: \(error.localizedDescription)")
            }
        }

        operation.modifyRecordsCompletionBlock = { records, recordIDs, error in

            if error != nil {
                print(error?.localizedDescription ?? "Error")
            } else{
                print("Success")
                preparationCompletionHandler(share,CKContainer.default(), error)
            }
        }

        CKContainer.default().privateCloudDatabase.add(operation)

    }

    controller.availablePermissions = [.allowPrivate, .allowReadOnly]
    controller.delegate = self
    present(controller, animated: true)

and retrieved shared-note with below code:

CODE - Read data from shared-note

func application(_ application: UIApplication, userDidAcceptCloudKitShareWith cloudKitShareMetadata: CKShareMetadata) {
    let acceptSharesOperation = CKAcceptSharesOperation(
        shareMetadatas: [cloudKitShareMetadata])

    acceptSharesOperation.perShareCompletionBlock = {
        metadata, share, error in
        if error != nil {
            print(error?.localizedDescription)
        } else {

            let operation = CKFetchRecordsOperation(
                recordIDs: [cloudKitShareMetadata.rootRecordID])

            operation.perRecordCompletionBlock = { record, _, error in

                if error != nil {
                    print(error?.localizedDescription)
                }

                if let shareRecord = record {
                    DispatchQueue.main.async() {
                        // Shared record successfully fetched. Update user
                        // interface here to present to user.
                        print("\(shareRecord["id"]!)") // id of note
                        print("\(shareRecord["text"])") // text of note
                        print("Shared record successfully fetched")
                    }
                }
            }

            operation.fetchRecordsCompletionBlock = { (recordsWithRecordIDs,error) in
                if error != nil {
                    print(error?.localizedDescription)
                }else {
                    if let recordsWithRecordIDs = recordsWithRecordIDs {
                        print("Count \(recordsWithRecordIDs.count)")
                    }
                }
            }
            CKContainer.default().sharedCloudDatabase.add(operation)
        }
    }

    CKContainer(identifier: cloudKitShareMetadata.containerIdentifier)
        .add(acceptSharesOperation)
}

Above method gives only parent note data ( root/parent only )

Queries:

1) How to fetch other children note records? ( I used term children for understanding purpose )

2) Do I need to check every time whether there are new shared records or not

As I haven't found any good tutorials and the source from official Apple docs. Would you suggest me the approach to share and retrieve multiple records?

Thanks in advance..!


Solution

  • I believe Thunk's answer is incorrect, since you should not have to fetch any records with the parent reference to a given record. First, where would you search? The child records of the CKShare root record do not appear in your shared database and you don't have access to the other user's private zone. Luckily, CloudKit will push to you all child records referenced by the CKShare's root record. The following approach will also solve the second problem, you will be notified of any and all future changes automatically!

    The key is to simply add a CKDatabaseSubscription to the shared database in the CloudKit container. Let's assume you are the client. Anytime another user share's a record with you, the CKShare record and the underlying root record referenced by the share are both situated in a custom zone in that user's private database. When you accept the share (triggering an application delegate method userDidAcceptCloudKitShareWith), the same CKShare and underlying record are now visible to you, except that they are both visible in your shared database. If you have subscribed to the shared database, the subscription will kick in and notify you right after you accept the share. Now you can fetch all changes in the shared database using CKFetchDatabaseChangesOperation. In the recordZoneWithIDChangedBlock, the CKRecordZoneID of the sharing user's custom zone will be given. You use this to trigger a CKFetchRecordZoneChangesOperation (be sure to perform this operation in the shared database!), which returns the CKShare, the underlying root record, and all records that have a parent reference to the underlying referenced record.

    So with a simple database subscription you can automatically trigger and download all shares, root records, and children!