Search code examples
swiftuicollectionviewrx-swift

How do I bind a ViewModel to a Collationview?


I'm trying to bind a view model to a collection view. But I don't know how to do it. I'm using MVVM pattern and RxSwift, and I've only tried table view binding before. Here's my view model and the view controller code I've done so far.

class SearchViewModel: ViewModelType {
    private let disposeBag = DisposeBag()

    struct input {
        let loadData: Signal<Void>
    }

    struct output {
        let result: Signal<String>
        let loadApplyList: PublishRelay<friends>
    }

    func transform(_ input: input) -> output {
        let api = SearchAPI()
        let result = PublishSubject<String>()
        let loadApplyList = PublishRelay<friends>()

        input.loadData.asObservable().subscribe(onNext: { [weak self] in
            guard let self = self else { return }
            api.getFriend().subscribe(onNext: { (response, statuscode) in
                switch statuscode {
                case .ok:
                    if let response = response {
                        loadApplyList.accept(response)
                    }
                default:
                    print("default")
                }
            }).disposed(by: self.disposeBag)
        }).disposed(by: disposeBag)

        return output(result: result.asSignal(onErrorJustReturn: ""), loadApplyList: loadApplyList)
    }
}

This is my ViewModel code

func bindViewModel() {
       let input = SearchViewModel.input(loadData: loadData.asSignal(onErrorJustReturn: ()))
       let output = viewModel.transform(input)
       
   }

And this is my ViewController code.
How should the collection view bind?


Solution

  • Here is what your view model should look like:

    class SearchViewModel {
        // no need for a disposedBag. If you are putting a disposeBag in your view model, you are likely doing something wrong.
    
        struct input {
            let loadData: Signal<Void>
        }
    
        struct output {
            let loadApplyList: Driver<[User]> // you should be passing an array here, not an object.
        }
    
        func transform(_ input: input) -> output {
            let api = SearchAPI()
    
            let friendResult = input.loadData
                .flatMapLatest {
                    api.getFriend()
                        .compactMap { $0.0.map(Result<friends, Error>.success) }
                        .asDriver(onErrorRecover: { Driver.just(Result<friends, Error>.failure($0)) })
                }
    
            let loadApplyList = friendResult
                .compactMap { (result) -> [User]? in
                    guard case let .success(list) = result else { return nil }
                    return list.friends
                }
    
            return output(loadApplyList: loadApplyList)
        }
    }
    

    Now in your view controller, you can bind it like this:

    func bindViewModel() {
        let input = SearchViewModel.input(loadData: loadData.asSignal(onErrorJustReturn: ()))
        let output = viewModel.transform(input)
    
        output.loadApplyList
            .drive(collectionView.rx.items(cellIdentifier: "Cell", cellType: MyCellType.self)) { index, item, cell in
                // configure cell with item here
            }
            .disposed(by: disposeBag)
    }