【问题标题】:How to populate custom header/footer view when using RxDatasources for datasource使用 RxDatasources 作为数据源时如何填充自定义页眉/页脚视图
【发布时间】:2022-02-26 01:16:49
【问题描述】:

我正在使用RxDatasources 创建我的数据源。稍后,我在视图控制器中配置单元格。问题是,因为 headers/footers 与数据源没有任何关系(除了我们可以设置标题,但如果我们使用自定义页眉页脚,这个标题将被覆盖)。

现在,这就是我配置表格视图单元格的方式:

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
}

... 不起作用,因为我想加载一个自定义标头并使用来自 RxDatasource 的数据填充它,我想知道什么是正确的方法:

  • 从我的视图模型中定义的数据源获取数据
  • 根据具有正确数据的部分(我有多个部分)以始终与数据源保持同步的方式填充标题。

这是我的视图模型:

 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: $0, data: nil)}
            let firstSection = ConfigStatusSectionModel(model: defaults.start.title, items: firstSectionItems.compactMap{ConfigCellModel(model: $0)})
            
            let secondSectionItems = defaults.stop.elements.map{ConfigItemModel(item: $0, data: nil)}
            let secondSection = ConfigStatusSectionModel(model: defaults.stop.title, items: secondSectionItems.compactMap{ConfigCellModel(model: $0)})
            
            let sections:[ConfigStatusSectionModel] = [firstSection, secondSection]
            
            return sections
        }
    }

现在我能做的是设置一个 tableview 委托,如下所示:

tableView.rx.setDelegate(self).disposed(by: disposeBag)

然后实施适当的委托方法来创建/返回自定义标头:

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
    }
  
}

如何使用来自我的数据源的数据填充我的自定义标题?我不想做switch (section){...}之类的事情,因为它与数据源完全不同步,而是手动同步,如果数据源发生变化,它不会自动影响标头配置。

这是我的模型结构:

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
}

我尝试使用this,但无法使其完全正常工作,因为我想我没有以正确的方式/时刻提供自定义标头。

【问题讨论】:

    标签: ios swift tableview rx-swift rx-cocoa


    【解决方案1】:

    这里的基本问题是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
        }
    }
    

    【讨论】:

    • 是的,这完全有效。我只需要添加guard !elements.isEmpty, let view = ... 以防止元素为空时崩溃。遗憾的是,没有选择奖励额外的答案。但至少我可以说,非常感谢!
    • 仅供参考,我做了一个小更新。事实证明,即使您没有明确地这样做,RxCocoa 也会安装自己的委托。所以我修改了代码以将这个新委托安装为forwardToDelegate
    猜你喜欢
    • 2022-01-26
    • 2017-03-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-09
    • 2011-12-27
    • 2013-12-03
    相关资源
    最近更新 更多