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

下面是我如何配置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
}

...不会起作用,因为我想加载一个自定义标题并用来自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){...}这样的事情,因为那样它与数据源完全不同步,而是手动的,如果数据源改变,它不会自动影响头配置.

以下是我的模型 struct :

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,但是我不能让它完全工作,因为我想我没有以正确的方式/时机提供自定义标题.

推荐答案

这里的基本问题是,tableView(_:viewForHeaderInSection:)是基于Pull的方法,而Rx是为基于Push的系统设计的.显然这是可以做到的.毕竟,基库为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
    }
}

Ios相关问答推荐

RN 0.63支持iOS ProMotion?

由于代码签名时出错,无法在iOS设备上发布iOS Maui Build

如何在iOS中以编程方式推送另一个页面?

在flatter_riverpod中,如果没有ref对象,我们如何访问提供程序?

如何使用 Rxswift 处理程序最喜欢的按钮

我正在try 制作一种 Select 器样式,例如设置中的外观 Select 器

Flutter IOS 构建错误 - 在签名和功能编辑器中 Select 一个开发团队.

在标签中显示多个计数器但在同一屏幕(Swift,IOS)

SwiftUI:如何为 ScrollView 制作可拉伸(灵活)的粘性标题?

UIButton在iOS7中没有显示突出显示

动画 UILabel 字体大小更改

在 Swift 中追加字符串

Swift中类的初始化类方法?

iOS UITextView 或 UILabel 带有可点击的动作链接

Select 器touchesBegan:withEvent:的覆盖方法具有不兼容的类型(NSSet,UIEvent)->()

如何在 Objective-C (iOS) 中的图像上写文本?

iOS SDK - 以编程方式生成 PDF 文件

iOS:如何从 URL 调试新启动应用程序

iOS在应用程序中下载并保存图像

错误 itms-90035 - Xcode