如何在创建新记录时使用CloudKit延迟使用Cludkit中更新tableview中的数据



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

第一个" mainviewController"显示了一个tableView,其中带有从私有Cloudkit数据库中获取的CKRECORDS字段。在此VC的ViewWillAppear方法中,我从CloudKit和TableView的重新加载数据中获取了记录,以显示用户以前保存在CloudKit中的最新获取结果。

第二个视图控制器" CreateRecordViewController"是为创建CKRECORD并将其保存到CloudKit的私有数据库而制作的。

因此,我在CreateRecordViewController中创建记录,并在MainviewController中显示它们。

问题是:当我在CreateCordViewController上创建记录时,它将保存在CloudKit Server上,但是关闭此CreateCoreCordViewController并转到MainViewController之后,TableView并不总是及时更新。

这是在我的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)")
                }
            }

保存记录后,我关闭了CreateCordViewController,请参阅MainViewController。

正如我之前在MainViewController的ViewWillAppear中所说的那样,我检查是否可用,如果可用,我可以从CloudKit查询中获取所有记录,并在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 async。

我的MainViewController中的LoadRecordSfromicLoud方法看起来像这样:

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中没有看到新的结果,而MainView Controlller loaders loads loads loads loda则没有)是因为当我获取记录时,它会获取旧数据(不确定为什么),但也许我犯了另一个错误。这是一种糟糕的用户体验,我想知道如何避免这种延迟。我希望我的TableView在关闭CreateCordViewController之后显示更新的结果。

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

我正在使用Xcode 8,iOS 10,Swift 3

无法保证何时在查询中可用,但是您可以做一些事情。您可以将新记录拼入。由于创建并保存记录时,您拥有记录ID,因此可以制作ckfetchrecordsodseration并从新记录中传递ID,并保证您会立即将其恢复。索引有时可能需要一段时间,这对CloudKit感到沮丧。因此,基本上,确保快速数据库的最佳方法是进行查询,如果不在那里的新记录ID,请与ID获取并将其附加到您的结果中。希望这是有道理的。

我不得不在CK之前和既然都热衷于此。这是将记录缝制回记录的操作的链接。https://developer.apple.com/reference/cloudkit/ckfetchrecordsoperation,如果您使用的是使用图像查看此库,则我允许您排除图像数据键并按需下载和缓存,这可能会加快您的查询。https://github.com/agibson73/agckimage

评论后编辑:

我认为您无法获得的部分是记录可能会或可能不会在ViewController 1中的查询下降,因为索引的工作方式。您甚至在您的问题中提到它获取旧数据。这是由于服务器索引。如果您删除记录,也会发生同样的情况。它仍然可以在查询中出现一段时间。在这种情况下,您会跟踪最近删除的记录ID,并在查询之后将其删除。同样,我正在谈论的手动添加和删除这是保证用户看到的内容的唯一方法,并且查询结果与用户期望的结果保持同步。

这是一些代码,尽管我希望完全未经测试,希望您能够可视化我上面的话。

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

这有点混乱,但是您可以看到我正在缝制丢失的recordId,如果不是回到您正在做的查询中,因为无法实时保证该查询可以为您提供预期的新记录。在这种情况下,self.passedbackNewRecorDID是基于ViewController 2的新recorpID设置以及删除。因此,在一个生产应用程序中,我必须跟踪具有更改,删除和添加的记录,并获得每个版本的新版本,以便您可以想象对象列表的复杂性。由于我停止使用CloudKit,因为墓碑或索引花费太长以显示查询的变化。

测试您的保存代码可能看起来像这样。

 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的代码中,您可以调用此

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

这应该非常适合您的示例,并让您测试我所说的话,只有很小的更改可能。

最新更新