RxSwift绑定UITableView带动画Cell出现闪烁重载问题求助
解决RxSwift绑定UITableView时可见Cell重复刷新闪烁的问题
看起来你的核心问题是数据源更新时,RxSwift默认会重新绑定所有Cell(包括当前可见的),而你的Cell在设置viewModel时会无条件触发动画,哪怕内容没有变化,最终导致闪烁。下面给你几个逐步优化的方案,从简单到优雅:
方案一:手动拦截重复的ViewModel设置(无需额外框架)
这是最直接的修复方式,通过判断Cell当前的ViewModel是否和新数据一致,避免重复执行更新逻辑:
1. 修改TableView的绑定代码
在bind(to:items)的闭包里,先检查Cell当前的ViewModel是否和新model匹配(用postId作为唯一标识),只有不匹配时才更新:
self.presenter.posts .bind(to: self.tableView.rx.items(cellIdentifier: "ListPostsCell")) { index, model, cell in guard let cell = cell as? ImagePostTableViewCell else { return } // 仅当Cell的当前ViewModel和新model的postId不同时,才执行更新 if cell.viewModel?.postId != model.postId { cell.viewModel = model cell.actionButton.rx.action = self.action(for: model.postId, index: index) } }.disposed(by: self.bag)
2. 优化Cell内部的ViewModel设置逻辑
在Cell的viewModel属性的didSet里,增加引用和内容的双重判断,避免重复订阅和UI更新:
var viewModel: ImagePostTableViewCellViewModel? { didSet { // 跳过相同引用的ViewModel,以及内容未变化的情况 guard let vm = viewModel, vm !== oldValue, vm.haiScoreText != oldValue?.haiScoreText, vm.lastVoteText != oldValue?.lastVoteText, vm.actionLabelText != oldValue?.actionLabelText else { return } let bag = DisposeBag() vm.image.asDriver(onErrorJustReturn: UIImage()) // 额外拦截重复的图片,避免无意义的动画 .distinctUntilChanged { $0.pngData() == $1.pngData() } .drive(onNext: { [weak self] image in self?.updateAppearanceFor(image) }).disposed(by: bag) self.haiScoreLabel.text = vm.haiScoreText self.lastVoteLabel.text = vm.lastVoteText self.actionButton.setTitle(vm.actionLabelText, for: .normal) self.activtiyIndicator.startAnimating() self.bag = bag } }
方案二:使用RxDataSources实现差异更新(更优雅的长期方案)
如果你的项目复杂度较高,推荐使用RxDataSources框架,它能自动计算数据源的差异,只刷新真正变化的Cell(新增、删除、内容变更),完全避免不必要的Cell更新:
1. 定义Section和Item模型
先把你的数据包装成支持差异对比的模型:
// 定义Section struct PostSection: SectionModelType { typealias Item = PostItem var items: [PostItem] init(original: PostSection, items: [PostItem]) { self = original self.items = items } } // 定义可对比的Item,用postId作为唯一标识 struct PostItem: IdentifiableType, Equatable { let postId: String let viewModel: ImagePostTableViewCellViewModel // 唯一标识,用于判断Item是否是同一个 var identity: String { postId } // 自定义相等判断,只有当内容变化时才视为不同 static func == (lhs: Self, rhs: Self) -> Bool { return lhs.postId == rhs.postId && lhs.viewModel.haiScoreText == rhs.viewModel.haiScoreText && lhs.viewModel.lastVoteText == rhs.viewModel.lastVoteText && lhs.viewModel.actionLabelText == rhs.viewModel.actionLabelText } }
2. 用RxDataSources绑定TableView
// 初始化数据源 let dataSource = RxTableViewSectionedAnimatedDataSource<PostSection>( configureCell: { [weak self] dataSource, tableView, indexPath, item in let cell = tableView.dequeueReusableCell(withIdentifier: "ListPostsCell", for: indexPath) as! ImagePostTableViewCell cell.viewModel = item.viewModel cell.actionButton.rx.action = self?.action(for: item.postId, index: indexPath.item) return cell } ) // 绑定数据源到TableView self.presenter.posts .map { posts in [PostSection(items: posts.map { PostItem(postId: $0.postId, viewModel: $0) })] } .bind(to: tableView.rx.items(dataSource: dataSource)) .disposed(by: bag)
这种方式下,当你加载更多数据更新presenter.posts时,RxDataSources只会刷新新增的Cell,可见的旧Cell如果内容没变化,完全不会被触发更新,从根源上解决了闪烁问题。
额外优化:避免图片加载的重复动画
即使上面的判断都做了,如果图片加载逻辑本身会重复触发(比如ViewModel的image序列重复发送相同图片),也可以在Cell的图片订阅里加上distinctUntilChanged,比如用图片的缓存key或者内容哈希来判断,避免无意义的动画执行。
内容的提问来源于stack exchange,提问作者Kevin Lücke
相关产品推荐
相关产品推荐

