Home > Mobile >  iOS RxSwift make event view based on observable array data
iOS RxSwift make event view based on observable array data

Time:11-27

Hi I prepare ios developer

Sorry, because do not speak english not well

I would like to know how to implement it in general.

subscribe array data and make custom view

doTableViewModel.doItems
    .observe(on: MainScheduler.instance)
    .subscribe(onNext: { viewModelItems in
        self.doTableView.reloadData(viewModelItems: viewModelItems)
        // make and draw view
        // after view create event
        self.doTableView.doItemViews.forEach{ doItemView in
            let item = doItemView.doItem
            doItemView.rx.tapGesture()
                .when(.recognized)
                .subscribe(onNext: { element in
                    print(item.doID)
                    }
                ).disposed(by: self.disposeBag)
            doItemView.checkButton.rx.tapGesture()
                .when(.recognized)
                .subscribe(onNext: { _ in
                    self.tappedDoItemCheck(viewDoItem: item)
                    }
                ).disposed(by: self.disposeBag)
            
            doItemView.deleteButton.rx.tapGesture()
                .when(.recognized)
                .subscribe(onNext: { _ in
                    self.tappedDoItemRemove(doItem: item)
                    }
                ).disposed(by: self.disposeBag)
            
            doItemView.editButton.rx.tapGesture()
                .when(.recognized)
                .subscribe(onNext: { element in
                    self.tappedDoItemEdit(doItem: item)
                    }
                ).disposed(by: self.disposeBag)
        }
        self.doTableView.configureLayer()
    })
    .disposed(by: disposeBag)

subscribe in subscirbe ... I don't know if this way is correct.

CodePudding user response:

This is not correct.

  • It has retain cycles. Do not capture self strongly in subscribe when self owns the disposeBag.

  • Having subscribe calls inside a subscribe call is a code smell. Try to avoid it. (Notice that in the code below, there is only one subscribe(onNext:) instead of many.)

I would rather see code that looks like this:

doTableViewModel.doItems
    .observe(on: MainScheduler.instance)
    .flatMapLatest { [doTableView] items in
        doTableView.configure(viewModelItems: items)
    }
    .subscribe(onNext: { [weak self] action in
        guard let self else { return }
        switch action {
        case let .check(doItem):
            self.tappedDoItemCheck(viewDoItem: doItem)
        case let .edit(doItem):
            self.tappedDoItemEdit(doItem: doItem)
        case let .remove(doItem):
            self.tappedDoItemEdit(doItem: doItem)
        }
    })
    .disposed(by: disposeBag)

To make the above work, you will need to move the other code into DoTableView and DoItemView like this:

extension DoTableView {
    enum Action {
        case check(DoItem)
        case remove(DoItem)
        case edit(DoItem)
    }

    func configure(viewModelItems: [DoItem]) -> Observable<Action> {
        reloadData(viewModelItems: viewModelItems)
        configureLayer()
        return Observable.merge(zip(doItemViews, viewModelItems).map { doItemView, doItem in
            doItemView.configure()
                .compactMap { viewAction in
                    switch viewAction {
                    case .print:
                        print(doItem.doID)
                        return nil
                    case .check:
                        return .check(doItem)
                    case .remove:
                        return .remove(doItem)
                    case .edit:
                        return .edit(doItem)
                    }
                }
        })
    }
}

extension DoItemView {
    enum Action {
        case print
        case check
        case remove
        case edit
    }

    func configure() -> Observable<Action> {
        Observable.merge(
            rx.tapGesture().when(.recognized).map { _ in Action.print },
            checkButton.rx.tapGesture().when(.recognized).map { _ in Action.check },
            deleteButton.rx.tapGesture().when(.recognized).map { _ in Action.remove },
            editButton.rx.tapGesture().when(.recognized).map { _ in Action.edit }
        )
    }
}

Note that with the above code, DoItemView does not need a doItem property any more. That can be removed.

One problem about this that has nothing to do with RxSwift is that it assumes the DoTableView completely destroys and rebuilds its DoItemViews every time the reloadData(viewModelItems:) method is called. That is incredibly inefficient. It would be better to use a normal UITableView or build your DoTableView so that it can reuse DoTableViews like UITableView reuses its cells.

  • Related