3 votes

Comment remplir une vue d'en-tête/pied de page personnalisée lorsque l'on utilise RxDatasources comme source de données ?

J'utilise RxDatasources pour créer ma source de données. Plus tard, je configure les cellules dans mon contrôleur de vue. Le problème est que les headers/footers n'ont rien à voir avec la source de données (sauf que nous pouvons définir un titre, mais si nous utilisons un header footer personnalisé, ce titre sera remplacé).

Voici comment je configure les cellules de mon tableau :

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

cause actuelle

dataSource.titleForHeaderInSection = { dataSource, index in
            return dataSource.sectionModels[index].model
}

... ne fonctionnera pas, car je veux charger un en-tête personnalisé et le remplir avec des données provenant de RxDatasource Je me demande quelle serait la bonne façon de procéder :

  • obtenir des données de ma source de données qui est définie dans mon modèle de vue
  • remplir l'en-tête, basé sur une section (j'ai plusieurs sections) avec des données correctes, d'une manière qui est toujours à jour avec une source de données.

Voici mon modèle de vue :

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

Ce que j'ai pu faire, c'est définir un délégué tableview, comme ceci :

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

puis de mettre en œuvre la ou les méthodes déléguées appropriées pour créer / renvoyer l'en-tête personnalisé :

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
    }

}

Comment remplir mon en-tête personnalisé avec des données provenant de ma source de données ? Je ne veux pas faire des choses comme switch (section){...} En effet, il n'est pas synchronisé avec une source de données, mais plutôt manuellement, et si la source de données change, cela n'affectera pas la configuration de l'en-tête automatiquement.

Voici les structures de mon modèle :

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
}

J'ai essayé d'utiliser este mais je n'ai pas réussi à le faire fonctionner complètement, car je suppose que je n'ai pas fourni l'en-tête personnalisé de la bonne façon/moment.

3voto

Daniel T. Points 11250

La question fondamentale ici est que tableView(_:viewForHeaderInSection:) est une méthode basée sur la traction et Rx est conçu pour les systèmes basés sur la poussée. Il est évident que c'est possible. Après tout, la bibliothèque de base l'a fait pour tableView(_:cellForRowAt:) mais c'est un peu plus complexe. Vous pouvez suivre le même système que celui utilisé par la bibliothèque de base pour cette dernière fonction.

Vous trouverez ci-dessous un tel système. Il peut être utilisé comme suit :

source
    .bind(to: tableView.rx.viewForHeaderInSection(
        identifier: ConfigSectionTableViewHeader.identifier,
        viewType: ConfigSectionTableViewHeader.self
    )) { section, element, view in
        view.setup(data: element.model)
    }
    .disposed(by: disposeBag)

Voici le code qui rend possible ce qui précède :

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X