RxDatasource в анимации перезагрузки RxSwift не обновляет источник данных - PullRequest
0 голосов
/ 04 апреля 2020

RxDatasource в RxSwift [RxTableViewSectionedAnimatedDataSource] Анимация перезагрузки не обновляет источник данных. Какую ошибку я делаю? Я даже не могу правильно связать свое действие с кнопкой.

Команды редактирования TableDataSource и Table

struct TableDataSource {
    var header: String
    var items: [Item]
    var SectionViewModel: SectionViewModel
}

extension TableDataSource: AnimatableSectionModelType {
    var identity: String {
        return header
    }

type alias Item = Data

    init(original: TableDataSource, items: [Item]) {
        self = original
        self.items = items
        self.sectionViewModel = original.sectionViewModel
    }
}

enum TableViewEditingCommand {    
    case deleteSingle(IndexPath)
    case clearAll(IndexPath)
    case readAll(IndexPath)
}

struct SectionedTableViewState {

    var sections: [TableDataSource]

    init(sections: [TableDataSource]) {
        self.sections = sections
    }

    func execute(command: TableViewEditingCommand) -> SectionedTableViewState {
        var sections = self.sections
        switch command {

        // Delete single item from datasource
        case .deleteSingle(let indexPath):
            var items = sections[indexPath.section].items
            items.remove(at: indexPath.row)
            if items.count <= 0 {
                sections.remove(at: indexPath.section)
            } else {
                sections[indexPath.section] = TableDataSource(
                    original: sections[indexPath.section],
                    items: items) }

        // Clear all item from datasource with isUnread = false
        case .clearAll(let indexPath):
            sections.remove(at: indexPath.section)

        // Mark all item as read in datasource with isUnread = true
        case .readAll(let indexPath):
            var items = sections[indexPath.section].items
            items = items.map { var unread = $0
                if $0.isUnRead == true { unreadData.isUnRead = false }
                return unreadData
            }
            sections.remove(at: indexPath.section)
            if sections.count > 0 {
            let allReadItems = sections[indexPath.section].items + items
                sections[indexPath.section] = TableDataSource(
                    original: sections[indexPath.section],
                    items: allReadItems)
            }
        }
         return SectionedTableViewState(sections: sections)
    }
}

Это мой контроллер и его расширения


class ViewController: UIViewController, Storyboardable {

    @IBOutlet weak var tableView: UITableView!
    @IBOutlet weak var closeButton: UIButton!
    @IBOutlet weak var titleText: UILabel!

    var viewModel: ViewModel!
    let disposeBag = DisposeBag()

    let sectionHeight: CGFloat = 70
    let dataSource = ViewController.dataSource()

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        bindInitials()
        bindDataSource()
        bindDelete()
    }

    private func bindInitials() {
        tableView.delegate = nil
        tableView.rx.setDelegate(self)
            .disposed(by: disposeBag)
        registerNibs()
    }

    private func registerNibs() {
        let headerNib = UINib.init(
            nibName: TableViewSection.identifier,
            bundle: nil)
        tableView.register(
            headerNib,
            forHeaderFooterViewReuseIdentifier: TableViewSection.identifier)
    }
}

extension ViewController: Bindable {

    func bindViewModel() {
        bindActions()
    }

    private func bindDataSource() {

        bindDelete()


        //        tableView.dataSource = nil
        //        Observable.just(sections)
        //            .bind(to: tableView.rx.items(dataSource: dataSource))
        //            .disposed(by: disposeBag)
    }

    private func bindDelete() {
        /// TODO: to check and update delete code to work properly to sink with clear all and mark all as read
        guard let sections = self.viewModel?.getSections() else {
            return
        }
        let deleteState = SectionedTableViewState(sections: sections)
        let deleteCommand = tableView.rx.itemDeleted.asObservable()
            .map(TableViewEditingCommand.deleteSingle)

        tableView.dataSource = nil
        Observable.of(deleteCommand)
            .merge()
            .scan(deleteState) {
                (state: SectionedTableViewState,
                command: TableViewEditingCommand) -> SectionedTableViewState in
                return state.execute(command: command) }
            .startWith(deleteState) .map { $0.sections }
            .bind(to: tableView.rx.items(dataSource: dataSource))
            .disposed(by: disposeBag)
    }

    private func bindActions() {

        guard let openDetailsObserver = self.viewModel?.input.openDetailsObserver,
            let closeObserver = self.viewModel?.input.closeObserver else {
                return
        }
        viewModel.output.titleTextDriver
            .drive(titleText.rx.text)
            .disposed(by: disposeBag)

//        viewModel.input.dataSourceObserver
//            .mapObserver({ (result) -> [Data] in
//                return result
//            })
//            .disposed(by: disposeBag)

        /// Close button binding with closeObserver
        closeButton.rx.tap
            .bind(to: (closeObserver))
            .disposed(by: disposeBag)

        /// Tableview item selected binding with openDetailsObserver
        tableView.rx.itemSelected
            .map { indexPath in
                return (self.dataSource[indexPath.section].items[indexPath.row])
        }.subscribe(openDetailsObserver).disposed(by: disposeBag)
    }
}

extension ViewController: UITableViewDelegate {

    static func dataSource() -> RxTableViewSectionedAnimatedDataSource<TableDataSource> {
        return RxTableViewSectionedAnimatedDataSource(
            animationConfiguration: AnimationConfiguration(insertAnimation: .fade,
                                                           reloadAnimation: .fade,
                                                           deleteAnimation: .fade),
            configureCell: { (dataSource, table, idxPath, item) in
                var cell = table.dequeueReusableCell(withIdentifier: TableViewCell.identifier) as? TableViewCell
                let cellViewModel = TableCellViewModel(withItem: item)
                cell?.setViewModel(to: cellViewModel)
                return cell ?? UITableViewCell()
        }, canEditRowAtIndexPath: { _, _ in return true })
    }

    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {

        guard var headerView = tableView.dequeueReusableHeaderFooterView(
            withIdentifier: TableViewSection.identifier)
            as? TableViewSection
            else { return UITableViewHeaderFooterView() }
        let viewModel = self.dataSource[section].sectionViewModel
        headerView.setViewModel(to: viewModel)
        headerView.dividerLine.isHidden = section == 0 ? true : false
        headerView.section = section

        let data = TableViewEditingCommand.clearAll(IndexPath(row: 0, section: section ?? 0))
//        /// Section button binding with closeObserver
//        headerView.sectionButton.rx.tap
//            .map(verseNum -> TableViewEditingCommand in (TableViewEditingCommand.deleteSingle))
//            .disposed(by: disposeBag)

        headerView.sectionButtonTappedClosure = { [weak self] (section, buttonType) in
            if buttonType == ButtonType.clearAll {
                self?.showClearAllConfirmationAlert(section: section, buttonType: buttonType)
            } else {
                self?.editAction(section: section, buttonType: buttonType)
            }
        }
        return headerView
    }

    func editAction(section: Int, buttonType: ButtonType) {

        var sections = self.dataSource.sectionModels
        let updateSection = (sections.count == 1 ? 0 : section)

        switch buttonType {
        /// Clear all
        case .clearAll:
            sections.remove(at: updateSection)
            let data = SectionedTableViewState(sections: sections)
            self.tableView.dataSource = nil
            Observable.of(data)
                .startWith(data) .map { $0.sections }
                .bind(to: tableView.rx.items(dataSource: dataSource))
                .disposed(by: disposeBag)
        /// Mark all as read
        case .markAllAsRead:
            if updateSection == 0 { sections = self.viewModel.getSections() }
            var items = sections[updateSection].items
            items = items.map { var unread = $0
                if $0.isUnRead == true { unread.isUnRead = false }
                return unread
            }
            sections.remove(at: updateSection)
            let allReadItems = sections[updateSection].items + items
            sections[updateSection] = TableDataSource(
                original: sections[updateSection],
                items: allReadItems)

            let data = SectionedTableViewState(sections: sections)
            self.tableView.dataSource = nil
            Observable.of(data)
                .startWith(data) .map { $0.sections }
                .bind(to: tableView.rx.items(dataSource: dataSource))
                .disposed(by: disposeBag)
        }
    }

    func showClearAllConfirmationAlert(section: Int, buttonType: ButtonType) {

        let alert = UIAlertController(title: "Clear All",
                                      message: "Are you sure, you want to clear all Items?",
                                      preferredStyle: .alert)

        alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { action in
            switch action.style{
            case .default:
                self.editAction(section: section, buttonType: buttonType)
            case .cancel: break
            case .destructive: break
            default:break
            }}))
        let cancel = UIAlertAction(title: "Cancel", style: .default, handler: { action in
        })
        alert.addAction(cancel)
        self.present(alert, animated: true, completion: nil)
    }

    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return sectionHeight
    }
}

Просмотреть модель для контроллера


class ViewModel {

    private enum Constants {
        static let titleText = "Test".localized
        static let testHistoryHeaderText = "test".localized
        static let unreadHeaderText = "Unread".localized
    }

    struct Input {
        let dataSourceObserver: AnyObserver<[Data]>
        let openDetailsObserver: AnyObserver<Data>
        let closeObserver: AnyObserver<Void>
        let sectionButtonTappedObserver: AnyObserver<IndexPath>
    }

    struct Output {
        let titleTextDriver: Driver<String>
        let dataSourceDriver: Driver<[Data]>
        let viewComplete: Observable<DataCoordinator.Event>
    }

    let input: Input
    let output: Output

    private let dataSourceSubject =  PublishSubject<[Data]>()
    private let closeSubject = PublishSubject<Void>()
    private let openDetailsSubject = BehaviorSubject<Data>(value:Data())
    private let sectionButtonTappedSubject = PublishSubject<IndexPath>()
    private let disposeBag = DisposeBag()

    init(withRepository repository: Repository) {
        input = Input(
            dataSourceObserver: dataSourceSubject.asObserver(),
            openDetailsObserver: openDetailsSubject.asObserver(),
            closeObserver: closeSubject.asObserver(), sectionButtonTappedObserver: sectionButtonTappedSubject.asObserver()
        )
        let closeEventObservable = closeSubject.asObservable().map { _ in
            return Coordinator.Event.goBack
        }
        let openDetailsEventObservable = openDetailsSubject.asObservable().map { _ in
            return Coordinator.Event.goToDetails
        }

        let viewCompleteObservable = Observable.merge(closeEventObservable, openDetailsEventObservable)

        let list = ViewModel.getData(repository: repository)

        output = Output(
            titleTextDriver: Observable.just(Constants.titleText).asDriver(onErrorJustReturn: Constants.titleText),
            dataSourceDriver: Observable.just(list).asDriver(onErrorJustReturn: list),
            viewComplete: viewCompleteObservable)


    }


    ///TODO: To be updated as per response after API integration
    static func getData(repository: Repository) -> [Data] {
        return repository.getData()
    }

    func getSections() -> [TableDataSource] {

        let List = ViewModel.getData(repository: Repository())

        let unread = list.filter { $0.isUnRead == true }
        let read = list.filter { $0.isUnRead == false }

        let headerText = String(format:Constants.unreadHeaderText, unread.count)

        let sections = [TableDataSource(
            header: headerText,
            items: unread,
            sectionViewModel: SectionViewModel(
                withHeader: headerText,
                buttonType: ButtonType.markAllAsRead.rawValue,
                items: unread)),
                        TableDataSource(
                            header: Constants.historyHeaderText,
                            items: read,
                            SectionViewModel: SectionViewModel(
                                withHeader: Constants.historyHeaderText,
                                buttonType: ButtonType.clearAll.rawValue,
                                items: read))]
        return sections
    }
}

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...