How to update data in TableView without the delay using CloudKit when Creating new Records

后端 未结 1 1575
北恋
北恋 2020-12-06 19:24

There are 2 View Controllers in my App.

The first \"MainViewController\" displays a tableView with CKRecords fields fetched from the private CloudKit database. Insid

相关标签:
1条回答
  • 2020-12-06 19:57

    There is no guarantee as to when the record would be available in a query but there is something you can do. You can stitch the new record back in. Because when you create and save a record you have the record id you can make a ckfetchrecordsoperation and pass the id from the new record and you are guaranteed to get it back immediately. The indexing sometimes can take a while and this is frustrating with CloudKit. So basically the best way to guarantee a speedy database is make a query and if the new record id is not in there make a fetch with the id and append it to your results. Hope this makes sense.

    I had to do this before and since I have not been too keen on CK. Here is the link to the operation to stitch the record back in. https://developer.apple.com/reference/cloudkit/ckfetchrecordsoperation also if you are using images check out this library I made that allows you to exclude the image data keys and download and cache on demand that could speed up your queries. https://github.com/agibson73/AGCKImage

    Edit after comment:

    I think the part you are not getting is the record may or may not come down with the query in viewcontroller 1 because of the way the indexing works. You even mention in your question it fetches old data. This is due to the server indexing. The same would happen if you deleted a record. It could still show up for some time in the query. In that case you keep track of the recently deleted record ids and remove them after the query. Again this manually adding and removing I am talking about is the only way to guarantee what the users see and the results from the query stay in sync with what the user would expect.

    Here is some code although completely untested that I hope will help you visualize what I am saying above.

       func loadRecordsFromiCloud() {
    
        // Get a private Database
        let privateDatabase = CKContainer.default().privateCloudDatabase
        let predicate = NSPredicate(value: true)
        let query = CKQuery(recordType: "MyRecords", predicate: predicate)
    
        privateDatabase.perform(query, inZoneWith: nil) { (results, error) in
            if ((error) != nil) {
                // Error handling for failed fetch from public database
                print("error loading : \(error)")
    
            }
            else {
                //check for a newRecord ID that might be missing from viewcontroller 2 that was passed back
                if self.passedBackNewRecordID != nil{
                    let newResults = results?.filter({$0.recordID == self.passedBackNewRecordID})
                    //only excute if there is a new record that is missing from the query
                    if newResults?.count == 0{
                        //houston there is a problem
                        let additionalOperation = CKFetchRecordsOperation(recordIDs: [self.passedBackNewRecordID!])
                        additionalOperation.fetchRecordsCompletionBlock = { recordsDict,fetchError in
                            if let newRecords = recordsDict?.values as? [CKRecord]{
                                //stitch the missing record back in
                                let final = newRecords.flatMap({$0}) + results!.flatMap({$0})
                                self.reloadWithResults(results: final)
                                self.passedBackNewRecordID = nil
    
                            }else{
                                self.reloadWithResults(results: results)
                                self.passedBackNewRecordID = nil
                            }
    
                        }
                        privateDatabase.add(additionalOperation)
                     }else{
                        //the new record is already in the query result
                        self.reloadWithResults(results: results)
                        self.passedBackNewRecordID = nil
                    }
                }else{
                    //no new records missing to do additional check on
                    self.reloadWithResults(results: results)
                }
    
            }
        }
    }
    
    
    func reloadWithResults(results:[CKRecord]?){
           self.tableViewDataArray = results!
            DispatchQueue.main.async {
                print("DispatchQueue.main.sync")
                 self.tableView.reloadData()
            }
    
        }
    }
    

    It's a bit of a mess but you can see that I am stitching the missing recordID if not nil back into the query that you are doing because that query is not guaranteed in real time to give you your expected new records. In this case self.passedBackNewRecordID is set based on the new recordID from Viewcontroller 2. How you set this or track this variable is up to you but you probably need an entire queue system because what I am telling you applies for changes to the record as well as deletes. So in a production app I had to track the records that had changes, deletes and additions and get the fresh version of each of those so you can imagine the complexity of a list of objects. Since I stopped using CloudKit because the tombstoning or indexing takes too long to show changes in queries.

    To test your saved code could look like this.

     CloudKitManager.sharedInstance.privateDatabase.save(myRecord) { (savedRecord, error) -> Void in
    
                if error == nil {
    
    
            print("successfully saved record code: \(savedRecord)")
            //save temporarily to defaults
            let recordID = "someID"
            UserDefaults.standard.set(recordID, forKey: "recentlySaved")
            UserDefaults.standard.synchronize()
            //now we can dismiss
    
    
                }
                else {
                    // Insert error handling
                    print("error Saving Data to iCloud: \(error.debugDescription)")
                }
            }
    

    And in the code where you call the query in view controller 1 possibly viewWillAppear you could call this

    func startQuery(){
        UserDefaults.standard.synchronize()
        if let savedID = UserDefaults.standard.value(forKey: "recentlySaved") as? String{
            passedBackNewRecordID = CKRecordID(recordName: savedID)
            //now we can remove from Userdefualts
            UserDefaults.standard.removeObject(forKey: "recentlySaved")
            UserDefaults.standard.synchronize()
        }
    
        self.loadRecordsFromiCloud()
    }
    

    This should fit your example pretty closely and allow you to test what I am saying with only minor changes possibly.

    0 讨论(0)
提交回复
热议问题