从 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 数据的主要内容,如果未能解决你的问题,请参考以下文章