Ios 如何使用MVVM和RxSwift编辑/删除UICollectionView单元格

Ios 如何使用MVVM和RxSwift编辑/删除UICollectionView单元格,ios,swift,mvvm,uicollectionview,rx-swift,Ios,Swift,Mvvm,Uicollectionview,Rx Swift,我试图理解如何使用对象列表和UICollectionView实现MVVM。我不理解如何实现用户迭代->模型流 我已经设置了一个,模型只是一个带有Int的类,视图是一个UICollectionViewCell,它显示一个带有相应Int值的文本,并有加号、减号和删除按钮分别递增、递减和删除一个元素。 每个条目看起来像: 我想知道使用MVVM和RxSwift更新/删除单元格的最佳方法 我有一个随机生成的Int值列表 let items: [Model] 仅具有Int值的模型 class Model

我试图理解如何使用对象列表和UICollectionView实现MVVM。我不理解如何实现用户迭代->模型流

我已经设置了一个,模型只是一个带有Int的类,视图是一个UICollectionViewCell,它显示一个带有相应Int值的文本,并有加号、减号和删除按钮分别递增、递减和删除一个元素。 每个条目看起来像: 我想知道使用MVVM和RxSwift更新/删除单元格的最佳方法

我有一个随机生成的Int值列表

let items: [Model]
仅具有Int值的模型

class Model {
    var number: Int

    init(_ n: Int = 0) {
        self.number = n
    }
}
ViewModel类,它只保存模型并具有可观察的

class ViewModel {

    var value: Observable<Model>

    init(_ model: Model) {
        self.value = Observable.just(model)
    }
}
我不清楚如何将按钮连接到相应的操作,然后更新模型和视图

单元格的ViewModel是否对此负责?它应该是接收tap事件、更新模型然后更新视图的那个吗


在删除的情况下,单元格的删除按钮需要从数据列表中删除当前模型。如何在不将所有内容混合在一起的情况下做到这一点?

以下是GitHub中包含以下更新的项目:

我们做的第一件事是概述我们所有的输入和输出。输出应该是视图模型结构的成员,输入应该是输入结构的成员

在这种情况下,我们有三个来自单元的输入:

struct CellInput {
    let plus: Observable<Void>
    let minus: Observable<Void>
    let delete: Observable<Void>
}
现在我们需要构建视图模型的init方法。这是所有实际工作发生的地方

extension CellViewModel {
    init(_ input: CellInput, initialValue: Int) {
        let add = input.plus.map { 1 } // plus adds one to the value
        let subtract = input.minus.map { -1 } // minus subtracts one

        value = Observable.merge(add, subtract)
            .scan(initialValue, accumulator: +) // the logic is here

        label = value
            .startWith(initialValue)
            .map { "number is \($0)" } // create the string from the value
        delete = input.delete // delete is just a passthrough in this case
    }
}
您会注意到视图模型的init方法需要的比工厂函数提供的更多。视图控制器在创建工厂时将提供额外的信息

视图控制器的viewDidLoad中将包含以下内容:

对于上述示例,我假设:

计数器属于可观察类型,来自视图控制器的视图模型。 值的类型为PublishSubject,并输入到视图控制器的视图模型中。 deletes的类型为PublishSubject,并输入到视图控制器的视图模型中。 视图控制器视图模型的构造遵循与单元相同的模式:

struct CellInput {
    let plus: Observable<Void>
    let minus: Observable<Void>
    let delete: Observable<Void>
}
投入:

struct Input {
    let value: Observable<(id: UUID, value: Int)>
    let add: Observable<Void>
    let delete: Observable<UUID>
}

以下是GitHub中包含以下更新的项目:

我们做的第一件事是概述我们所有的输入和输出。输出应该是视图模型结构的成员,输入应该是输入结构的成员

在这种情况下,我们有三个来自单元的输入:

struct CellInput {
    let plus: Observable<Void>
    let minus: Observable<Void>
    let delete: Observable<Void>
}
现在我们需要构建视图模型的init方法。这是所有实际工作发生的地方

extension CellViewModel {
    init(_ input: CellInput, initialValue: Int) {
        let add = input.plus.map { 1 } // plus adds one to the value
        let subtract = input.minus.map { -1 } // minus subtracts one

        value = Observable.merge(add, subtract)
            .scan(initialValue, accumulator: +) // the logic is here

        label = value
            .startWith(initialValue)
            .map { "number is \($0)" } // create the string from the value
        delete = input.delete // delete is just a passthrough in this case
    }
}
您会注意到视图模型的init方法需要的比工厂函数提供的更多。视图控制器在创建工厂时将提供额外的信息

视图控制器的viewDidLoad中将包含以下内容:

对于上述示例,我假设:

计数器属于可观察类型,来自视图控制器的视图模型。 值的类型为PublishSubject,并输入到视图控制器的视图模型中。 deletes的类型为PublishSubject,并输入到视图控制器的视图模型中。 视图控制器视图模型的构造遵循与单元相同的模式:

struct CellInput {
    let plus: Observable<Void>
    let minus: Observable<Void>
    let delete: Observable<Void>
}
投入:

struct Input {
    let value: Observable<(id: UUID, value: Int)>
    let add: Observable<Void>
    let delete: Observable<UUID>
}
我是这样做的:

ViewModel.swift

import Foundation
import RxSwift
import RxCocoa

typealias Model = (String, Int)

class ViewModel {
    let disposeBag = DisposeBag()
    let items = BehaviorRelay<[Model]>(value: [])
    let add = PublishSubject<Model>()
    let remove = PublishSubject<Model>()
    let addRandom = PublishSubject<()>()

    init() {
        addRandom
            .map { _ in (UUID().uuidString, Int.random(in: 0 ..< 10)) }
            .bind(to: add)
            .disposed(by: disposeBag)
        add.map { newItem in self.items.value + [newItem] }
            .bind(to: items)
            .disposed(by: disposeBag)
        remove.map { removedItem in
            self.items.value.filter { (name, _) -> Bool in
                name != removedItem.0
            }
            }
            .bind(to: items)
            .disposed(by: disposeBag)
    }
}
ViewController.swift

import UIKit
import Material
import RxSwift
import SnapKit

class ViewController: Material.ViewController {
    let disposeBag = DisposeBag()
    let vm = ViewModel()

    let tableView = UITableView()
    let addButton = FABButton(image: Icon.cm.add, tintColor: .white)

    override func prepare() {
        super.prepare()

        view.addSubview(tableView)
        tableView.snp.makeConstraints { make in
            make.edges.equalToSuperview()
        }

        addButton.pulseColor = .white
        addButton.backgroundColor = Color.red.base
        view.layout(addButton)
            .width(48)
            .height(48)
            .bottomRight(bottom: 16, right: 16)
        addButton.rx.tap
            .bind(to: vm.addRandom)
            .disposed(by: disposeBag)

        tableView.register(Cell.self, forCellReuseIdentifier: "Cell")
        vm.items
            .bind(to: tableView.rx.items) { (tableView, row, model) in
                let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! Cell
                cell.model = model
                cell.disposeBag = DisposeBag()
                cell.removeButton.rx.tap
                    .map { _ in model }
                    .bind(to: self.vm.remove)
                    .disposed(by: cell.disposeBag!)
                return cell
            }
            .disposed(by: disposeBag)
    }
}
请注意,一个常见的错误是仅在单元格内创建一次DisposeBag,这将在触发操作时造成混乱

每次重新使用单元时,都必须重新创建DisposeBag

可以找到一个完整的工作示例。

我是这样做的:

ViewModel.swift

import Foundation
import RxSwift
import RxCocoa

typealias Model = (String, Int)

class ViewModel {
    let disposeBag = DisposeBag()
    let items = BehaviorRelay<[Model]>(value: [])
    let add = PublishSubject<Model>()
    let remove = PublishSubject<Model>()
    let addRandom = PublishSubject<()>()

    init() {
        addRandom
            .map { _ in (UUID().uuidString, Int.random(in: 0 ..< 10)) }
            .bind(to: add)
            .disposed(by: disposeBag)
        add.map { newItem in self.items.value + [newItem] }
            .bind(to: items)
            .disposed(by: disposeBag)
        remove.map { removedItem in
            self.items.value.filter { (name, _) -> Bool in
                name != removedItem.0
            }
            }
            .bind(to: items)
            .disposed(by: disposeBag)
    }
}
ViewController.swift

import UIKit
import Material
import RxSwift
import SnapKit

class ViewController: Material.ViewController {
    let disposeBag = DisposeBag()
    let vm = ViewModel()

    let tableView = UITableView()
    let addButton = FABButton(image: Icon.cm.add, tintColor: .white)

    override func prepare() {
        super.prepare()

        view.addSubview(tableView)
        tableView.snp.makeConstraints { make in
            make.edges.equalToSuperview()
        }

        addButton.pulseColor = .white
        addButton.backgroundColor = Color.red.base
        view.layout(addButton)
            .width(48)
            .height(48)
            .bottomRight(bottom: 16, right: 16)
        addButton.rx.tap
            .bind(to: vm.addRandom)
            .disposed(by: disposeBag)

        tableView.register(Cell.self, forCellReuseIdentifier: "Cell")
        vm.items
            .bind(to: tableView.rx.items) { (tableView, row, model) in
                let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! Cell
                cell.model = model
                cell.disposeBag = DisposeBag()
                cell.removeButton.rx.tap
                    .map { _ in model }
                    .bind(to: self.vm.remove)
                    .disposed(by: cell.disposeBag!)
                return cell
            }
            .disposed(by: disposeBag)
    }
}
请注意,一个常见的错误是仅在单元格内创建一次DisposeBag,这将在触发操作时造成混乱

每次重新使用单元时,都必须重新创建DisposeBag


可以找到完整的工作示例。

感谢您的解释和建议更改的请求:!感谢您的解释和建议更改的请求:!