UITableView 在滚动/分页时无法正确处理 contentOffset
Posted
技术标签:
【中文标题】UITableView 在滚动/分页时无法正确处理 contentOffset【英文标题】:UITableView does not handle contentOffset correctly when scrolling/paging 【发布时间】:2017-09-18 18:57:52 【问题描述】:我有一个包含大量数据的表格视图。 出于性能原因,它不能一次全部加载。 此外,有时需要随机加载数组的位置,因此增量分页不是一个好的选择。
目前针对这些要求的解决方案是在数据数组上滑动窗口。当用户滚动时,我从一端添加单元格并从另一端删除。我使用滚动位置(通过查看屏幕上的单元格)来确定是否该加载新数据。
通常,当您调用tableView.deleteRows(at:with:)
并从表格的开头删除单元格时,tableView 会调整其contentOffset
属性,因此用户仍会看到与操作前相同的单元格。
但是,当 tableView 在滚动后减速时,它的contentOffset
不会在更新时调整,这会导致一遍又一遍地加载新页面,直到减速完成。然后,在减速后第一次更新时,contentOffset
被 tableView 修复并停止加载。
当使用tableView.insertRows(at:with:)
向后滚动并在表的开头添加值时,也会发生同样的情况。
如何让 UITableView 正确调整其 contentOffset?
或者还有其他方法可以克服这个错误 - 保持在数据数组中间加载任意片段并从中滚动的能力?
我做了一个小项目来说明这个错误:
https://github.com/wsb9/TableViewExample
【问题讨论】:
你只是想反转滚动吗?这个练习的最终目标是什么?还有什么阻止它一次加载?出列一个单元格应该重用一个已经在内存中的单元格 或者您是否试图创建循环的无限滚动?我很困惑你为什么要从顶部/底部添加/删除行 我的建议是创建操作队列!! , 新单元格出现时创建操作,单元格离开屏幕时取消操作。 我下载了你的示例项目。当我在 iPhone 8 Plus 模拟器上执行它时,它确实在两个方向上平滑滚动。那么,有什么问题呢? 【参考方案1】:从您的示例项目中,我可以理解您正在尝试通过窗口内容概念实现无限滚动,以便您始终可以拥有固定数量的行(索引路径,比如说 100),以便当窗口向下/向上滚动时- 表格视图相应地从顶部/底部删除 indexPaths。 即使您有更多数据源项,您也可以始终拥有 indexPaths 100 的 tableView
基本上你在这里处理两个问题:
内容偏移
动态高度
假设我们的高度是固定的 (44) 并且桌子没有倒置。 要实现 Window 进行无限滚动,您必须执行以下操作:
override func scrollViewDidScroll(_ scrollView: UIScrollView)
let bottom: CGFloat = scrollView.contentSize.height - scrollView.frame.size.height
let buffer: CGFloat = 3 * 44
let scrollPosition = scrollView.contentOffset.y
if (scrollPosition > bottom - buffer)
dataSource.expose(dataSource.exposedRange.shift(by: 25))
self.tableView.contentOffset.y -= self.dataSource.deltaHeightToRemove
决定向下滚动时需要保留多少高度缓冲区。此高度缓冲区是您决定在数据源中插入更多项目 (25) 之后的高度。
此时您必须从顶部删除项目
当您从顶部移除项目时,您基本上是在告诉 scrollView 将其内容偏移量减少相同的高度。
这样每次总内容大小都会固定
希望它会有所帮助。
编辑:- 这是修改后的代码,它实际上在底部进行无限滚动 具有动态单元格高度的表格视图。这不会使行数增加超过 100。但仍会在滑动窗口中加载数据。 link
【讨论】:
【参考方案2】:从您的示例项目中,我可以理解以下内容,
一件事是您希望通过一次仅加载少量单元格来提高表格视图的性能 您的第二个顾虑是有时您希望使用随机放置在数据源数组中的数据加载表视图我检查了您的代码,并且非常有趣地实现了您的sliding-window over data-source model
。由于您导致的问题一直试图通过删除和读取单元格来有效地制作tableview
。
实际上,使单元格出列应该是重用内存中已经存在的单元格。请看一下苹果文档,
出于性能原因,表视图的数据源通常应该 将单元格分配给其行时重用 UITableViewCell 对象 tableView(_:cellForRowAt:) 方法。表视图维护一个队列或 数据源已标记的 UITableViewCell 对象列表 重用。当被要求时,从您的数据源对象调用此方法 为表格视图提供一个新单元格。此方法使 现有单元格(如果可用)或使用该类创建新单元格 或您之前注册的 nib 文件。如果没有可用的单元格 重用并且您没有注册类或nib文件,此方法 返回零。
好消息是,一旦我删除了您的行删除和读取机制,您的 sliding-window over data-source model
就可以正常工作。这是您的工作代码,
https://drive.google.com/file/d/0B2y_JJbzjRA6dDR3QzRMUzExSGs/view?usp=sharing
【讨论】:
我认为@Timex 在这里看到的是一种滑动窗口——固定数量的表格视图数据源行或项目——实际上可以在大型数据集上滑动。通过这种方式,每当您有更多数据项时,您实际上会从已出列的项中删除相同的数量并再次添加新项,以便在相同的索引路径中。这样,您的 indexPaths 将始终保持不变,但您仍然可以滑过。 但是当我检查你的链接时——因为你没有对数据集进行任何删除操作——你 tableView 的数据源计数将始终保持增加,这将创建更多 indexPaths 因为 tableView 将找不到那些 indexPaths在出队的单元格中-这与每个文档相同-“如果现有单元格可用,则此方法使现有单元格出列,或者使用您之前注册的类或 nib 文件创建一个新单元格。如果没有单元格可供重用并且您没有注册class 或 nib 文件,此方法返回 nil。”。以上是关于UITableView 在滚动/分页时无法正确处理 contentOffset的主要内容,如果未能解决你的问题,请参考以下文章
UIScrollView 在分页时确定 scrollViewDidEndDragging 上的滚动方向
启用collectionview分页时将collection view滚动到索引
启用分页时将水平可滚动集合视图的单元格居中并使其他单元格部分可见