How to update data at cell in MVVM?

Viewed 166

I'm making a Twitter clone app. If I press the like button on the posts in each cell, I want to reflect the number of likes to increase. Also, I want to make it reflect in the cell as soon as I delete or modify the post. There is also a video player in the cell like Twitter, but every time I press the like button, the ViewModel accepts again, which causes the video that was running to turn off. Please tell me the best way to implement the Like button...

Cell.swift

func bind(_ viewModel: PostItemViewModel) {
    .....
    likesLabel.text = String(viewModel.likes)
    ....
}
....
likesBtn.rx.tapGesture().when(.ended).mapToVoid().asSignalOnErrorJustComplete()
    .emit(onNext: { [unowned self] in
        delegate?.pressLikesBtn(tag)
    }).disposed(by: disposeBag)

ViewController.swift

class PostsViewController: UIViewController {
    private let pressLikesRelay = PublishRelay<Int>()
    private let deleteRelay = PublishRelay<Int>()
    private let disposeBag = DisposeBag()

    var viewModel: PostsViewModel!
    .......
    .......

    private func bindViewModel() {
    assert(viewModel != nil)
    let viewWillAppear = rx.viewWillAppear
        .mapToVoid()
        .asSignalOnErrorJustComplete()
    let pull = timeLineTableView.refreshControl!.rx
        .controlEvent(.valueChanged)
        .asSignal()
    let reachedBottom = timeLineTableView.rx.reachedBottom().asSignal()
    
    let input = PostsViewModel.Input(fetchInitial: Signal.merge(viewWillAppear, pull), fetchNext: reachedBottom, likesTrigger: pressLikesRelay.asSignal(), deleteTrigger: deleteRelay.asSignal())
    let output = viewModel.transform(input: input)
    
    output.posts.drive(timeLineTableView.rx.items(cellIdentifier: PostTableViewCell.identifier, cellType: PostTableViewCell.self)) {
        row, item, cell in
        cell.delegate = self
        cell.bind(item)
        cell.tag = row
    }.disposed(by: disposeBag)
}

extension PostsViewController: PostTableCellDelegate {
    func pressLikesBtn(_ index: Int) {
        pressRelay.accept(index)
    }

    func deletePost(_ index: Int) {
        deleteRelay.accept(index)
    }
}

ViewModel.swift

final class PostsViewModel: ViewModelType {
    ...
    private let postsRelay = BehaviorRelay<[PostItemViewModel]>(value: [])
    .....

    func transform(input: Input) -> Output {
        .......
            
        input.likesTrigger.emit(onNext: { [unowned self] index in
            var updated = self.postsRelay.value
            updated[index].plusLikes()
            self.postsRelay.accept(updated)
        }).disposed(by: disposeBag)
        
        input.deleteTrigger.emit(onNext: { [unowned self] index in
            var deleted = self.postsRelay.value
            deleted.remove(at: index)
            self.postsRelay.accept(deleted)
        }).disposed(by: disposeBag)
            
        return Output(
        ...,
            posts: postsRelay.asDriver(),
            ...)
    }

}

PostItemViewModel.swift

public struct PostItemViewModel {
    .....
    var likes: Int
    ....

    mutating func plusLikes() {
        self.vote1 +=1
    }
}
1 Answers

Here is a sample app that shows how to do what you want. RxMultiCounter.

The key is to only update the table view when you add or remove cells. Each cell needs to independently observe the state and when the inner state of the cell changes, the cell updates directly.

Related