使用 RxDatasources 作为数据源时如何填充自定义 header/footer 视图
How to populate custom header/footer view when using RxDatasources for datasource
我正在使用 RxDatasources
创建我的数据源。稍后,我在我的视图控制器中配置单元格。问题是,因为 headers/footers 与数据源没有任何关系(除了我们可以设置标题,但如果我们使用自定义 header 页脚,这个标题将被覆盖)。
现在,这就是我配置 tableview 单元格的方式:
private func observeDatasource(){
let dataSource = RxTableViewSectionedAnimatedDataSource<ConfigStatusSectionModel>(
configureCell: { dataSource, tableView, indexPath, item in
if let cell = tableView.dequeueReusableCell(withIdentifier: ConfigItemTableViewCell.identifier, for: indexPath) as? BaseTableViewCell{
cell.setup(data: item.model)
return cell
}
return UITableViewCell()
})
botConfigViewModel.sections
.bind(to: tableView.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)
}
现在导致
dataSource.titleForHeaderInSection = { dataSource, index in
return dataSource.sectionModels[index].model
}
... 不会工作,因为我想加载自定义 header 并用来自 RxDatasource
的数据填充它,我想知道什么是正确的方法:
- 从我的视图模型中定义的数据源获取数据
- 根据一个部分(我有多个部分)使用正确的数据填充 header,数据源始终保持最新。
这是我的视图模型:
class ConfigViewModel{
private let disposeBag = DisposeBag()
let sections:BehaviorSubject<[ConfigStatusSectionModel]> = BehaviorSubject(value: [])
func startObserving(){
let observable = getDefaults()
observable.map { conditions -> [ConfigStatusSectionModel] in
return self.createDatasource(with: conditions)
}.bind(to: self.sections).disposed(by: disposeBag)
}
private func getDefaults()->Observable<ConfigDefaultConditionsModel> {
return Observable.create { observer in
FirebaseManager.shared.getConfigDefaults { conditions in
observer.onNext(conditions!)
} failure: { error in
observer.onError(error!)
}
return Disposables.create()
}
}
private func createDatasource(with defaults:ConfigDefaultConditionsModel)->[ConfigStatusSectionModel]{
let firstSectionItems = defaults.start.elements.map{ConfigItemModel(item: [=13=], data: nil)}
let firstSection = ConfigStatusSectionModel(model: defaults.start.title, items: firstSectionItems.compactMap{ConfigCellModel(model: [=13=])})
let secondSectionItems = defaults.stop.elements.map{ConfigItemModel(item: [=13=], data: nil)}
let secondSection = ConfigStatusSectionModel(model: defaults.stop.title, items: secondSectionItems.compactMap{ConfigCellModel(model: [=13=])})
let sections:[ConfigStatusSectionModel] = [firstSection, secondSection]
return sections
}
}
现在我能做的就是设置一个表视图委托,如下所示:
tableView.rx.setDelegate(self).disposed(by: disposeBag)
然后实施适当的委托方法来创建/return自定义header:
extension BotConfigViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView,
viewForHeaderInSection section: Int) -> UIView? {
guard let header = tableView.dequeueReusableHeaderFooterView(
withIdentifier: ConfigSectionTableViewHeader.identifier)
as? ConfigSectionTableViewHeader
else {
return nil
}
return header
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return UITableView.automaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForHeaderInSection section: Int) -> CGFloat {
return 40
}
}
如何用我的数据源中的数据填充我的自定义 header?我不想做 switch (section){...}
之类的事情,因为它完全不与数据源同步,而是手动,如果数据源发生变化,它不会自动影响 header 配置。
这是我的模型结构:
typealias ConfigStatusSectionModel = AnimatableSectionModel<String, ConfigCellModel>
struct ConfigItemData {
let conditionsLink:String?
let iconPath:String?
}
struct ConfigItemModel {
let item:OrderConditionModel
let data:ConfigItemData?
}
struct ConfigCellModel : Equatable, IdentifiableType {
static func == (lhs: ConfigCellModel, rhs: ConfigCellModel) -> Bool {
return lhs.model.item.symbol == rhs.model.item.symbol
}
var identity: String {
return model.item.symbol
}
let model: ConfigItemModel
}
我尝试使用 ,但无法使其完全正常工作,因为我想我没有在右侧 way/moment 中提供自定义 header。
这里的根本问题是 tableView(_:viewForHeaderInSection:)
是一种基于拉的方法,而 Rx 是为基于推的系统设计的。显然是可以做到的。毕竟,基础库是为 tableView(_:cellForRowAt:)
完成的,但要复杂得多。您可以遵循基础库用于后一个功能的相同系统。
下面就是这样一个系统。可以这样使用:
source
.bind(to: tableView.rx.viewForHeaderInSection(
identifier: ConfigSectionTableViewHeader.identifier,
viewType: ConfigSectionTableViewHeader.self
)) { section, element, view in
view.setup(data: element.model)
}
.disposed(by: disposeBag)
这是使上述成为可能的代码:
extension Reactive where Base: UITableView {
func viewForHeaderInSection<Sequence: Swift.Sequence, View: UITableViewHeaderFooterView, Source: ObservableType>
(identifier: String, viewType: View.Type = View.self)
-> (_ source: Source)
-> (_ configure: @escaping (Int, Sequence.Element, View) -> Void)
-> Disposable
where Source.Element == Sequence {
{ source in
{ builder in
let delegate = RxTableViewDelegate<Sequence, View>(identifier: identifier, builder: builder)
base.rx.delegate.setForwardToDelegate(delegate, retainDelegate: false)
return source
.concat(Observable.never())
.subscribe(onNext: { [weak base] elements in
delegate.pushElements(elements)
base?.reloadData()
})
}
}
}
}
final class RxTableViewDelegate<Sequence, View: UITableViewHeaderFooterView>: NSObject, UITableViewDelegate where Sequence: Swift.Sequence {
let build: (Int, Sequence.Element, View) -> Void
let identifier: String
private var elements: [Sequence.Element] = []
init(identifier: String, builder: @escaping (Int, Sequence.Element, View) -> Void) {
self.identifier = identifier
self.build = builder
}
func pushElements(_ elements: Sequence) {
self.elements = Array(elements)
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
guard let view = tableView.dequeueReusableHeaderFooterView(withIdentifier: identifier) as? View else { return nil }
build(section, elements[section], view)
return view
}
}
我正在使用 RxDatasources
创建我的数据源。稍后,我在我的视图控制器中配置单元格。问题是,因为 headers/footers 与数据源没有任何关系(除了我们可以设置标题,但如果我们使用自定义 header 页脚,这个标题将被覆盖)。
现在,这就是我配置 tableview 单元格的方式:
private func observeDatasource(){
let dataSource = RxTableViewSectionedAnimatedDataSource<ConfigStatusSectionModel>(
configureCell: { dataSource, tableView, indexPath, item in
if let cell = tableView.dequeueReusableCell(withIdentifier: ConfigItemTableViewCell.identifier, for: indexPath) as? BaseTableViewCell{
cell.setup(data: item.model)
return cell
}
return UITableViewCell()
})
botConfigViewModel.sections
.bind(to: tableView.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)
}
现在导致
dataSource.titleForHeaderInSection = { dataSource, index in
return dataSource.sectionModels[index].model
}
... 不会工作,因为我想加载自定义 header 并用来自 RxDatasource
的数据填充它,我想知道什么是正确的方法:
- 从我的视图模型中定义的数据源获取数据
- 根据一个部分(我有多个部分)使用正确的数据填充 header,数据源始终保持最新。
这是我的视图模型:
class ConfigViewModel{
private let disposeBag = DisposeBag()
let sections:BehaviorSubject<[ConfigStatusSectionModel]> = BehaviorSubject(value: [])
func startObserving(){
let observable = getDefaults()
observable.map { conditions -> [ConfigStatusSectionModel] in
return self.createDatasource(with: conditions)
}.bind(to: self.sections).disposed(by: disposeBag)
}
private func getDefaults()->Observable<ConfigDefaultConditionsModel> {
return Observable.create { observer in
FirebaseManager.shared.getConfigDefaults { conditions in
observer.onNext(conditions!)
} failure: { error in
observer.onError(error!)
}
return Disposables.create()
}
}
private func createDatasource(with defaults:ConfigDefaultConditionsModel)->[ConfigStatusSectionModel]{
let firstSectionItems = defaults.start.elements.map{ConfigItemModel(item: [=13=], data: nil)}
let firstSection = ConfigStatusSectionModel(model: defaults.start.title, items: firstSectionItems.compactMap{ConfigCellModel(model: [=13=])})
let secondSectionItems = defaults.stop.elements.map{ConfigItemModel(item: [=13=], data: nil)}
let secondSection = ConfigStatusSectionModel(model: defaults.stop.title, items: secondSectionItems.compactMap{ConfigCellModel(model: [=13=])})
let sections:[ConfigStatusSectionModel] = [firstSection, secondSection]
return sections
}
}
现在我能做的就是设置一个表视图委托,如下所示:
tableView.rx.setDelegate(self).disposed(by: disposeBag)
然后实施适当的委托方法来创建/return自定义header:
extension BotConfigViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView,
viewForHeaderInSection section: Int) -> UIView? {
guard let header = tableView.dequeueReusableHeaderFooterView(
withIdentifier: ConfigSectionTableViewHeader.identifier)
as? ConfigSectionTableViewHeader
else {
return nil
}
return header
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return UITableView.automaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForHeaderInSection section: Int) -> CGFloat {
return 40
}
}
如何用我的数据源中的数据填充我的自定义 header?我不想做 switch (section){...}
之类的事情,因为它完全不与数据源同步,而是手动,如果数据源发生变化,它不会自动影响 header 配置。
这是我的模型结构:
typealias ConfigStatusSectionModel = AnimatableSectionModel<String, ConfigCellModel>
struct ConfigItemData {
let conditionsLink:String?
let iconPath:String?
}
struct ConfigItemModel {
let item:OrderConditionModel
let data:ConfigItemData?
}
struct ConfigCellModel : Equatable, IdentifiableType {
static func == (lhs: ConfigCellModel, rhs: ConfigCellModel) -> Bool {
return lhs.model.item.symbol == rhs.model.item.symbol
}
var identity: String {
return model.item.symbol
}
let model: ConfigItemModel
}
我尝试使用
这里的根本问题是 tableView(_:viewForHeaderInSection:)
是一种基于拉的方法,而 Rx 是为基于推的系统设计的。显然是可以做到的。毕竟,基础库是为 tableView(_:cellForRowAt:)
完成的,但要复杂得多。您可以遵循基础库用于后一个功能的相同系统。
下面就是这样一个系统。可以这样使用:
source
.bind(to: tableView.rx.viewForHeaderInSection(
identifier: ConfigSectionTableViewHeader.identifier,
viewType: ConfigSectionTableViewHeader.self
)) { section, element, view in
view.setup(data: element.model)
}
.disposed(by: disposeBag)
这是使上述成为可能的代码:
extension Reactive where Base: UITableView {
func viewForHeaderInSection<Sequence: Swift.Sequence, View: UITableViewHeaderFooterView, Source: ObservableType>
(identifier: String, viewType: View.Type = View.self)
-> (_ source: Source)
-> (_ configure: @escaping (Int, Sequence.Element, View) -> Void)
-> Disposable
where Source.Element == Sequence {
{ source in
{ builder in
let delegate = RxTableViewDelegate<Sequence, View>(identifier: identifier, builder: builder)
base.rx.delegate.setForwardToDelegate(delegate, retainDelegate: false)
return source
.concat(Observable.never())
.subscribe(onNext: { [weak base] elements in
delegate.pushElements(elements)
base?.reloadData()
})
}
}
}
}
final class RxTableViewDelegate<Sequence, View: UITableViewHeaderFooterView>: NSObject, UITableViewDelegate where Sequence: Swift.Sequence {
let build: (Int, Sequence.Element, View) -> Void
let identifier: String
private var elements: [Sequence.Element] = []
init(identifier: String, builder: @escaping (Int, Sequence.Element, View) -> Void) {
self.identifier = identifier
self.build = builder
}
func pushElements(_ elements: Sequence) {
self.elements = Array(elements)
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
guard let view = tableView.dequeueReusableHeaderFooterView(withIdentifier: identifier) as? View else { return nil }
build(section, elements[section], view)
return view
}
}