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

Update tableView data after retrieving new data from CloudKit

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

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

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

当我在最后一行时,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() 方法,所有的行都会被添加,但滚动条不会移动,卡在前一个最后一行;

谢谢。

我认为错误源于后台线程中完成块中的 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)
        }
}

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

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

实施

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

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

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

为了解决这个问题,我必须与 tableView.beginUpdates() | tableView.endUpdates() 属性同步进行更新,因此 tableView 会在填充 dataSource 数组的同时更新,并使用 [= 更新它自己的属性33=] 更新,而不是调用 reloadData() 来更新整个 tableView。

我注意到将单元格配置为在 willDisplayCell() 方法上显示,使应用程序出现轻微错误,不是很流畅,因此我已将配置移至 cellForRowAtIndexPath() 方法,并且只检查 willDisplayCell() 方法是否显示最后一个,如果是,则查询服务器以检索更多数据,从上一个光标点开始。

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

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)

    }
}