如何结合RxSwift、RxCocoa以类似tableView.rx.items的方式使用SkeletonView
实现思路:自定义同时兼容RxSwift数据源协议和SkeletonTableViewDataSource协议的数据源类,再扩展UITableView的Rx绑定方法,即可实现类似tableView.rx.items的调用形式。
1. 定义Rx兼容的骨架屏数据源类
import RxSwift import RxCocoa import SkeletonView import RxDataSources class RxSkeletonTableViewDataSource<Section: SectionModelType>: TableViewSectionedDataSource<Section>, SkeletonTableViewDataSource { // 骨架屏配置参数,可根据业务场景自定义 var skeletonSectionCount: Int = 1 var skeletonRowCountPerSection: Int = 5 var skeletonCellReuseID: String = "" // MARK: SkeletonTableViewDataSource 协议实现 func numSections(in collectionSkeletonView: UITableView) -> Int { skeletonSectionCount } func collectionSkeletonView(_ skeletonView: UITableView, numberOfRowsInSection section: Int) -> Int { skeletonRowCountPerSection } func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier { skeletonCellReuseID } func collectionSkeletonView(_ skeletonView: UITableView, prepareCellForSkeleton cell: UITableViewCell, at indexPath: IndexPath) { cell.showAnimatedGradientSkeleton() } }
2. 扩展UITableView的Rx绑定方法
extension Reactive where Base: UITableView { func items<S: Sequence, Cell: UITableViewCell, O: ObservableType>( skeletonCellID: String, skeletonSectionCount: Int = 1, skeletonRowCount: Int = 5, cellType: Cell.Type = Cell.self ) -> (_ source: O) -> (_ configureCell: @escaping (Int, S.Element, Cell) -> Void) -> Disposable where O.Element == S { return { source in return { configureCell in let dataSource = RxSkeletonTableViewDataSource<SectionModel<String, S.Element>>() dataSource.skeletonCellReuseID = skeletonCellID dataSource.skeletonSectionCount = skeletonSectionCount dataSource.skeletonRowCountPerSection = skeletonRowCount dataSource.configureCell = { _, tableView, indexPath, item in let cell = tableView.dequeueReusableCell(withIdentifier: skeletonCellID, for: indexPath) as! Cell // 数据加载完成后隐藏骨架屏 cell.hideSkeleton() // 执行外部传入的cell配置逻辑 configureCell(indexPath.row, item, cell) return cell } return self.items(dataSource: dataSource)(source.map { [SectionModel(model: "", items: Array($0))] }) } } } }
3. 业务使用示例
// 提前注册cell tableView.register(MyBusinessCell.self, forCellReuseIdentifier: "MyBusinessCellID") // 页面加载后先展示骨架屏 tableView.showAnimatedSkeleton() // 你的业务数据序列(比如网络请求返回的列表数据) let businessDataList: Observable<[MyBusinessItem]> = ... // 绑定数据,调用方式和原生rx.items完全一致 businessDataList .bind(to: tableView.rx.items( skeletonCellID: "MyBusinessCellID", skeletonRowCount: 6, cellType: MyBusinessCell.self )) { row, item, cell in // 正常写你的业务cell配置逻辑 cell.titleLabel.text = item.title cell.avatarImageView.kf.setImage(with: URL(string: item.avatarUrl)) } .disposed(by: disposeBag) // 数据返回后隐藏骨架屏 businessDataList .subscribe(onNext: { [weak self] _ in self?.tableView.hideSkeleton(reloadDataAfter: true) }) .disposed(by: disposeBag)
注意事项
- 骨架屏用的cell复用ID需要和正常业务cell的复用ID保持一致,保证骨架样式和实际业务样式匹配
- 多section场景可调整
skeletonSectionCount参数,同时修改数据源类的section相关配置即可 - 如果需要自定义骨架动画效果,直接修改
prepareCellForSkeleton方法内的骨架调用参数即可
内容的提问来源于stack exchange,提问作者Kou Yamamoto
相关产品推荐
相关产品推荐

