如何在创建新记录时使用 CloudKit 无延迟地更新 TableView 中的数据 [英] How to update data in TableView without the delay using CloudKit when Creating new Records

查看:21
本文介绍了如何在创建新记录时使用 CloudKit 无延迟地更新 TableView 中的数据的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的应用中有 2 个视图控制器.

There are 2 View Controllers in my App.

第一个MainViewController"显示一个tableView,其中包含从私有CloudKit 数据库中提取的CKRecords 字段.在这个 VC 的 viewWillAppear 方法中,我从 CloudKit 获取记录并重新加载 tableview 的数据以显示用户先前保存在 CloudKit 中的最新获取结果.

The first "MainViewController" displays a tableView with CKRecords fields fetched from the private CloudKit database. Inside the viewWillAppear method of this VC I fetch records from CloudKit and reload data of a tableview to show the latest fetched results that have been previously saved in the CloudKit by the user.

第二个视图控制器CreateRecordViewController"用于创建CKRecords并将它们保存到CloudKit的私有数据库中.

The second view controller "CreateRecordViewController" is made for creating CKRecords and saving them to the private database of the CloudKit.

所以我在 CreateRecordViewController 中创建记录并在 MainViewController 中显示它们.

So i create records in the CreateRecordViewController and show them in the MainViewController.

问题如下:当我在 CreateRecordViewController 上创建记录时,它正在保存在 CloudKit 服务器上,但是在关闭这个 CreateRecordViewController 并转到 MainViewController 后,tableview 并不总是及时更新.

The problem is the following: when I create record at CreateRecordViewController, it is saving on the CloudKit server, but after closing this CreateRecordViewController and going to MainViewController the tableview does not always update in time.

这是在我的 CreateRecordViewController 中保存记录的代码:

This is the code of saving record in my CreateRecordViewController:

     CloudKitManager.sharedInstance.privateDatabase.save(myRecord) { (savedRecord, error) -> Void in

                if error == nil {


            print("successfully saved record code: (savedRecord)")


                }
                else {
                    // Insert error handling
                    print("error Saving Data to iCloud: (error.debugDescription)")
                }
            }

保存记录后,我关闭 CreateRecordViewController 并查看 MainViewController.

After record saved i close CreateRecordViewController and see MainViewController.

正如我之前在 MainViewController 的 viewWillAppear 中所说的,我检查 iCloud 是否可用,如果它可用,我会从 CloudKit 中通过查询获取所有记录并将它们显示在 tableView 中.

As i've said earlier in viewWillAppear of MainViewController i check if iCloud available, and if it is available, I fetch all records with a query from CloudKit and showing them in a tableView.

 override func viewWillAppear(_ animated: Bool) {

        CKContainer.default().accountStatus { (accountStatus, error) in
            switch accountStatus {
            case .noAccount:  print("CloudKitManager: no iCloud Alert")
            case .available:
                print("CloudKitManager: checkAccountStatus : iCloud Available")

                 self.loadRecordsFromiCloud()

            case .restricted:
                print("CloudKitManager: checkAccountStatus : iCloud restricted")
            case .couldNotDetermine:
                print("CloudKitManager: checkAccountStatus : Unable to determine iCloud status")
            }
        }


    }

在 loadRecordsFromiCloud() 中,当查询成功以显示最新结果时,我也在重新加载 tableview 异步.

In loadRecordsFromiCloud() I'm also reloading tableview async when query successful to show the latest results.

位于 MainViewController 中的 loadRecordsFromiCloud 方法如下所示:

My loadRecordsFromiCloud method located in my MainViewController looks like this:

func loadRecordsFromiCloud() {

        // Get a private Database
        let privateDatabase = CloudKitManager.sharedInstance.privateDatabase
        let predicate = NSPredicate(value: true)
        let query = CKQuery(recordType: "MyRecords", predicate: predicate)

        let operation = CKQueryOperation(query: query)


        privateDatabase.perform(query, inZoneWith: nil) { (results, error) in
            if ((error) != nil) {
                // Error handling for failed fetch from public database
                print("error loading : (error)")

            }
            else {
                // Display the fetched records
                //print(results!)
                self.tableViewDataArray = results!

                DispatchQueue.main.async {
                    print("DispatchQueue.main.sync")
                     self.tableView.reloadData()
                }

            }
        }

    } 

有时当 CloudKit 服务器工作得更快时,我可以在 tableView 中看到新记录,但大多数时候会有延迟(我在 MainViewController 加载时在 tableview 中看不到新结果)我认为这是因为当我获取记录时,它获取了旧数据(不知道为什么),但也许我又犯了另一个错误.这是一个糟糕的用户体验,我想知道如何避免这种延迟.我希望我的 tableView 在关闭 CreateRecordViewController 后立即显示更新的结果.

Sometimes when CloudKit servers work faster i can see the new records in a tableView, but most of the time there is a delay (i don't see the new results in a tableview at the moment when MainViewController loads) I think this is because when i fetch records it fetches the old data (not sure why), but maybe i've made another mistake . This is a bad user experience and i would like to know how to avoid this delay. I want my tableView to show the updated results just after i close CreateRecordViewController .

我的第一个想法是订阅 CloudKit 记录更改,并在收到通知时在 tableView 中获取和重新加载数据,但我真的不需要推送通知(我宁愿只是代码中的一种方法,在我知道所有 CloudKit 记录已保存或在我知道创建了新记录之后,我可以从 CloudKit 获取数据,并且在获取并获取 tableView 的数据后,我会调用 tableView.reloadData 例如),但我不确定如何正确实施(以何种方法),也不确定这是否是最佳解决方案.我还听说在 WWDC 2016 专门针对 CloudKit 的视频中,现在有一些与订阅记录更改相关的新方法,也许其中一些方法可以提供帮助(不确定).为这个问题寻找最好的,或者任何好的和简单的解决方案(延迟问题).

My first idea was to subscribe to CloudKit Records changes, and fetch and reload data in a tableView when notification received, but i really don't need a push notification (i'd rather have just a method in code where i could fetch data from CloudKit after i know that all CloudKit records saved or after i know that there is a new record created, and after fetching and getting the data for a tableView i would call tableView.reloadData for example), but i'm not sure how to implement this right (in what method) and not sure if this is the best solution. I've also heard that in WWDC 2016 video dedicated to CloudKit, that now there are some new methods related to subscription to Record changes, maybe some of those methods can help (not sure). Looking for the best, or any good and easy solution for this problem (Delay Problem).

我使用的是 XCode 8、iOS 10、swift 3

I'm using XCode 8, iOS 10, swift 3

推荐答案

无法保证查询中的记录何时可用,但您可以做一些事情.您可以重新拼接新记录.因为当您创建并保存记录时,您拥有记录 ID,您可以执行 ckfetchrecords 操作并从新记录中传递 ID,您可以保证立即将其取回.索引有时可能需要一段时间,这让 CloudKit 感到沮丧.因此,基本上保证快速数据库的最佳方法是进行查询,如果新记录 id 不在其中,则使用该 id 进行提取并将其附加到您的结果中.希望这是有道理的.

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.

我之前不得不这样做,因为我不太热衷于 CK.这是缝合记录的操作链接.https://developer.apple.com/reference/cloudkit/ckfetchrecordsoperation 如果您正在使用图像,请查看我制作的这个库,它允许您排除图像数据键并按需下载和缓存,这可以加快您的查询速度.https://github.com/agibson73/AGCKImage

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

评论后

我认为您没有得到的部分是由于索引的工作方式,记录可能会或可能不会与视图控制器 1 中的查询一起出现.您甚至在问题中提到它会获取旧数据.这是由于服务器索引.如果您删除了一条记录,也会发生同样的情况.它仍然可以在查询中显示一段时间.在这种情况下,您可以跟踪最近删除的记录 ID 并在查询后将其删除.同样,我所说的手动添加和删除是保证用户看到的内容以及查询结果与用户期望的内容保持同步的唯一方法.

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()
        }

    }
}

这有点混乱,但您可以看到我正在将丢失的记录 ID(如果不是零)拼接回您正在执行的查询中,因为不能保证该查询实时为您提供预期的新记录.在这种情况下,self.passedBackNewRecordID 是基于 Viewcontroller 2 中的新记录 ID 设置的.如何设置或跟踪此变量取决于您,但您可能需要整个队列系统,因为我告诉您的内容适用于对记录的更改以及删除.因此,在生产应用程序中,我必须跟踪具有更改、删除和添加的记录,并获取每个记录的最新版本,以便您可以想象对象列表的复杂性.因为我停止使用 CloudKit,因为逻辑删除或索引需要太长时间才能显示查询中的更改.

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)")
            }
        }

并且在您在视图控制器 1 中调用查询的代码中,可能 viewWillAppear 您可以调用它

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.

这篇关于如何在创建新记录时使用 CloudKit 无延迟地更新 TableView 中的数据的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆