从 CloudKit 检索新数据后更新 tableView 数据

Posted

技术标签:

【中文标题】从 CloudKit 检索新数据后更新 tableView 数据【英文标题】:Update tableView data after retrieving new data from CloudKit 【发布时间】:2017-02-17 13:17:37 【问题描述】:

我目前正在从 CloudKit 检索数据并成功填充表格视图。

我有一个实例,它检测滚动是否在最后一行,如果是,它会重新排队服务器以检索更多数据。

成功检索所需数据后,我将在特定 indexPath 处插入特定项目,然后重新加载表格视图。

发生的情况是,当我在最后一行时,tableView.reloadData() 使我的应用程序闪烁屏幕,但一切正常。

几秒钟后,我的调试开始打印相同的错误,每个单元格是:

This application is modifying the autolayout engine from a background thread after the engine was accessed from the main thread. This can lead to engine corruption and weird crashes.

我正在计算 heightForRowAtIndexPath :

override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat 
    var cell = PostFeedTableViewCell()
    cell = tableView.dequeueReusableCell(withIdentifier: Storyboard.PostWithImage) as! PostFeedTableViewCell
    return cell.bounds.size.height;

一旦用户到达最后一行,我检索数据的代码如下:

func continueQuering(cursor: CKQueryCursor?) 
    if cursor != nil 
        self._loadingData = true

        let publicData = CKContainer.default().publicCloudDatabase
        let predicate = NSPredicate(format: "TRUEPREDICATE", argumentArray: nil)

        let q = CKQuery(recordType: "Posts", predicate: predicate)
        q.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]

        let qop = CKQueryOperation(query: q)
        qop.resultsLimit = 5
        qop.cursor = cursor

        qop.recordFetchedBlock = (record) in
            print("appending")
            self.posts.append(record)
            self.tableView.insertRows(at: [IndexPath.init(row: self.posts.count - 1, section: 0)], with: .automatic)
        

        qop.queryCompletionBlock  =  [weak self] (cursor : CKQueryCursor?, error : Error?) -> Void in
            print("********************")
            print("cursor = \(String(describing: cursor)) , error = \(String(describing: error))")
            print("********************")
            self?._Cursor = cursor 
            self?._loadingData = false
            DispatchQueue.main.async 
                print("reloading async ******")
                self?.tableView.reloadData()
            
        
        publicData.add(qop)
    

我需要一些关于如何避免闪烁屏幕的帮助,这可能是由 tableView.reloadData() 函数引起的,以及如何修复这些几秒钟后提示的警告。

如果我不调用 reloadData() 方法,所有行都被添加但滚动不移动,卡在上一个最后一行;

谢谢。

【问题讨论】:

遍历新帖子并在该迭代中将它们插入 tableview 然后重新加载 tableView 数据是否是一个不错的选择,所有这些都是异步的? 【参考方案1】:

所以我已经设法解决了这个问题,我将解释如何并提供更新的代码;

当我使用后台线程从服务器检索代码时,我在更新 UI 时遇到了问题,因为 Apple 文档说我们应该只在主线程中更新 UI。

实施后

DispatchQueue.Main.async 
    self.tableView.insertRows(at: [IndexPath.init(row: self.posts.count - 1, section: 0)], with: .automatic)

我还是有问题,应用崩溃提示错误,提醒应用数据源在更新前后应具有相同的对象。

我发现在后台线程上附加数据,然后同时在主线程上更新 UI 会引发该错误,即使这样,如果我之前有 5 行,我的 tableView 滚动并没有更新,之后插入,比如说 10 行,滚动仍然会卡在第 5 行。

为了解决这个问题,我必须与 tableView.beginUpdates() | tableView.endUpdates() 属性同步更新,因此 tableView 将在填充数据源数组的同时更新,并使用开始/结束更新更新它自己的属性,而不是调用reloadData() 更新整个tableView。

我注意到将单元格配置为在 willDisplayCell() 方法上显示,使应用程序有点错误,不是很流畅,所以我将配置移动到 cellForRowAtIndexPath() 方法,并且只签入 @ 987654326@ 方法,如果它显示最后一个,如果是,则查询服务器以检索更多数据,从上一个光标点开始。

如前所述,解析代码如下:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
    // Default não tem image, assim se não houver posts para download retorna a cell vazia
    var cell = PostFeedTableViewCell()

    if (posts.count == 0)  return cell  /* Não tem posts, retorna uma cell vazia */

    let post = posts[indexPath.row]

    // Instancia o reuse identifier
    if post["post_image"] != nil 
        cell = tableView.dequeueReusableCell(withIdentifier: Storyboard.PostWithImage, for: indexPath) as! PostFeedTableViewCell
     else 
        cell = tableView.dequeueReusableCell(withIdentifier: Storyboard.PostWithoutImage, for: indexPath) as! PostFeedTableViewCell
    
    configureCell(cell: cell, atIndexPath: indexPath)
    return cell



override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) 
    if (!_loadingData) && indexPath.row == posts.count - 1 
        print("last row displayed")
        continueQuering(cursor: _Cursor)
    


func continueQuering(cursor: CKQueryCursor?) 
    if cursor != nil 
        self._loadingData = true

        // novo query com cursor a continuar na posição anterior
        let publicData = CKContainer.default().publicCloudDatabase
        let predicate = NSPredicate(format: "TRUEPREDICATE", argumentArray: nil)

        let q = CKQuery(recordType: "Posts", predicate: predicate)
        q.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]

        let qop = CKQueryOperation(query: q)
        qop.resultsLimit = 5
        qop.cursor = cursor

        qop.recordFetchedBlock = (record) in
            self.posts.append(record)
            print("appending...")
            DispatchQueue.main.sync 
                print("updating...")
                self.tableView.beginUpdates()
                self.tableView.insertRows(at: [IndexPath.init(row: self.posts.count - 1, section: 0)], with: .automatic)
                self.tableView.endUpdates()
            

        

        qop.queryCompletionBlock  =  [weak self] (cursor : CKQueryCursor?, error : Error?) -> Void in
            self?._Cursor = cursor // Actualiza o cursor se houver mais dados
            self?._loadingData = false
            print("cursor = \(cursor)")
        
        publicData.add(qop)

    

【讨论】:

很高兴你能成功!另一个想法,如果您完全删除行插入并仅在末尾更改表格,您会看到明显的差异reloadData 我愿意,假设我在表格视图中已经有 50 个项目(行),我向它们添加了 5 个。如果我重新加载整个数据,我会看到屏幕闪烁,因为它会更新整个数据项,如果我用 begin 和 endUpdates 逐一插入它们,它只会更新添加的行和视觉上看起来很正常,没有屏幕闪存重新加载。如果我要替换所有项目,调用“reloadData”会很棒,但由于我不是,因此 insertRowAtIndex 可以完成这项工作。 我明白了。无论如何,这是关于您如何成功地处理异步调用的详尽、精心编写的解释!【参考方案2】:

我相信错误源于self.tableView.insertRows 在后台线程的完成块中运行。尝试将insertRows 分派到主线程:

qop.recordFetchedBlock = (record) in
        print("appending")
        self.posts.append(record)
        DispatchQueue.main.async 
             self.tableView.insertRows(at: [IndexPath.init(row: self.posts.count - 1, section: 0)], with: .automatic)
        

【讨论】:

我会试一试,我会尽快回复。我尝试过类似的方法,但是在插入所有新行之后,表格视图不允许我滚动到它们,卡在之前的表格视图“内部高度”中。我会尽快反馈给你。谢谢

以上是关于从 CloudKit 检索新数据后更新 tableView 数据的主要内容,如果未能解决你的问题,请参考以下文章

CloudKit - 无法从 iCloud 检索数据

从模拟器查询公共数据库时出现 Cloudkit 错误

如何使用 Cloudkit 快速从 iCloud 获取当前用户信息

是否可以将外部数据添加到 CloudKit?

SWIFT/CloudKit - 更新记录并将其保存回容器

用于从 cloudkit 检索单列的代码模式/片段