提问人:MrKew 提问时间:7/25/2023 更新时间:7/25/2023 访问量:57
如何使用 RxSwift 在单元格添加/删除到 UITableView 后正确滚动
How to properly scroll after cells were added/removed to UITableView with RxSwift
问:
我有一个带有一个部分的视图控制器,我使用RxSwift以以下方式显示其中。每当更改时,我想滚动到底部(或其他任何地方)。我正在将文件用于我的单元格和动画数据源。tableView
data
tableView
data
.xib
问题是,有时在调用时,单元格尚未显示在 中,并且调用会抛出错误。scrollToRow
tableView
如何解决这个问题?
我唯一能想到的就是在数据更改和滚动之间设置延迟,但延迟应该有多大?此外,我认为有一些更强大的方法可以解决这个问题。不知何故等到单元格实际显示。
我试过做:
scrollRectToVisible(...)
到底部tableView.contentSize
- 滚动前检查是否
data.count == tableView.numberOfRows(inSection: 0)
它们中的任何一个都会导致更新时有时不滚动,这比错误要好,但仍然不是预期的结果。tableView
data
class SomeVC: UIViewController {
private typealias DataSource = RxTableViewSectionedAnimatedDataSource<AnimatableSectionModel<..., ...>>
private let db = DisposeBag()
@IBOutlet weak var tableView: UITableView!
private let data = BehaviorRelay<[...]>(value: ...)
private lazy var dataSource = createDataSource()
override func viewDidLoad() {
tableView.register(UINib(nibName: ..., bundle: ...), forCellResuseIdentifier: ...)
dataSource.animationConfiguration = AnimationConfiguration(insertAnimation: .bottom, reloadAnimation: .fade, deleteAnimation: .fade)
data
.asDriver()
.do(afterNext: { [unowned self] data in
DispatchQueue.main.async { [weak self] in
let lastIndexPath = getLastIndexPath(from: data)
// This does not always hold: data.count == tableView.numberOfRows(inSection: 0)
self?.tableView.scrollToRow(at: lastIndexPath, at: .none, animated: true)
}
}
.map { [AnimatableSectionModel(...)] }
.drive(tableView.rx.items(dataSource: dataSource))
.disposed(by: db)
}
private func getLastIndexPath(from data: [...]) {
// Something like this
return IndexPath(row: data.count - 1, section: 0)
}
private func createDataSource() -> DataSource {
return DataSource(configureCell: { dataSource, tv, indexPath, model in
let cell = tv.dequeueReusableCell(withIdentifier: ..., for: indexPath)
...
return cell
}
}
}
答:
1赞
Daniel T.
7/25/2023
#1
以下方法有效,但并不理想:
source
.map { [SectionModel(model: "", items: $0)] }
.bind(to: tableView.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)
source
.filter { $0.count > 0 }
.map { IndexPath(row: $0.count - 1, section: 0) }
.bind(onNext: { [tableView] indexPath in
tableView.scrollToRow(at: indexPath, at: .bottom, animated: true)
})
.disposed(by: disposeBag)
重要的一点,也是最大的问题,是这两个订阅取决于顺序。由于它们不是独立的,因此如果您反转它们,应用程序将无法运行。
这也对我有用。也许你计算错了最后一行?
source
.map { [SectionModel(model: "", items: $0)] }
.do(afterNext: { [tableView] sections in
if sections[0].items.count > 0 {
tableView.scrollToRow(at: IndexPath(row: sections[0].items.count - 1, section: 0), at: .bottom, animated: true)
}
})
.bind(to: tableView.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)
评论
0赞
MrKew
7/27/2023
不,这绝对不适合我。它非常难以重现错误,它几乎每次都有效,但不是每次都有效。我确定我正确计算了索引路径(由 scrollRectToVisible 有时也不会滚动的事实支持)。你对为什么它不应该起作用有任何猜测吗?可能是 asDriver()、.xib 还是 AnimatedDataSource?当可观察的源中有多个更改时,似乎会发生这种情况
评论
afterNext:
assert(sections[0].items.count == tableView!.numberOfRows(inSection: 0))