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

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

问题描述

我的应用程序中有2个视图控制器。

There are 2 View Controllers in my App.

第一个MainViewController显示带有从私有CloudKit数据库获取的CKRecords字段的tableView。在此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.

我的MainRev中的loadRecordsFromiCloud方法ewController看起来像这样:

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的视频中,现在有一些与订阅Record更改相关的新方法,也许其中一些方法可以帮助(不确定)。寻找最佳,或任何好的和简单的解决方案来解决这个问题(延迟问题)。

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,您可以进行ckfetchrecordsoperation并从新记录中传递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如果不是nil回到你正在做的查询,因为该查询不能实时保证给你预期的新记录。在这种情况下,self.passedBackNewRecordID是根据Viewcontroller 2中的新recordID设置的。你如何设置或跟踪这个变量取决于你,但你可能需要一个完整的队列系统,因为我告诉你的内容适用于记录的更改以及删除。因此,在生产应用程序中,我必须跟踪具有更改,删除和添加的记录,并获取每个记录的新版本,以便您可以想象对象列表的复杂性。由于我停止使用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天全站免登陆