我正在努力应对以下挑战。我使用包含开关的自定义单元格创建了表视图。我只想打开一个开关,例如,启动后我打开了3rd开关,然后又打开了7th开关,因此第3个开关被关闭,依此类推。我对单元使用了rx +协议,但并不完全了解如何确定切换了哪个开关。以前,我将使用过滤器或映射在dataSource数组中查找打开了开关并以某种方式处理的数据,但现在我搞砸了。我不确定不使用表视图委托方法是否有可能。非常感谢,希望有人能解释我错了。
// CellViewModel实现
import Foundation
import RxSwift
protocol ViewModelProtocol {
var bag:DisposeBag {get set}
func dispose()
}
class ViewModel:ViewModelProtocol {
var bag = DisposeBag()
func dispose() {
self.bag = DisposeBag()
}
}
protocol CellViewModelProtocol:ViewModelProtocol {
var isSwitchOn:BehaviorSubject<Bool> {get set}
}
class CellVM:ViewModel, CellViewModelProtocol {
var isSwitchOn: BehaviorSubject<BooleanLiteralType> = BehaviorSubject(value: false)
let internalBag = DisposeBag()
override init() {
}
}
//我的电池实现
import UIKit
import RxSwift
import RxCocoa
class Cell:UITableViewCell {
static let identifier = "cell"
@IBOutlet weak var stateSwitch:UISwitch!
var vm:CellViewModelProtocol? {
didSet {
oldValue?.dispose()
self.bindUI()
}
}
var currentTag:Int?
var bag = DisposeBag()
override func awakeFromNib() {
super.awakeFromNib()
self.bindUI()
}
override func prepareForReuse() {
super.prepareForReuse()
self.bag = DisposeBag()
}
private func bindUI() {
guard let vm = self.vm else { return }
self.stateSwitch.rx.controlEvent(.valueChanged).withLatestFrom(self.stateSwitch.rx.value).observeOn(MainScheduler.asyncInstance).bind(to: vm.isSwitchOn).disposed(by: vm.bag)
}
}
// TableViewController实现
import UIKit
import RxSwift
import RxCocoa
class TableViewController: UITableViewController {
private var dataSource:[CellViewModelProtocol] = []
var vm = TableViewControllerVM()
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.estimatedRowHeight = 70
self.tableView.rowHeight = UITableView.automaticDimension
self.bindUI()
}
private func bindUI() {
vm.dataSource.observeOn(MainScheduler.asyncInstance).bind { [weak self] (dataSource) in
self?.dataSource = dataSource
self?.tableView.reloadData()
}.disposed(by: vm.bag)
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.dataSource.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: Cell.identifier, for: indexPath) as! Cell
if cell.vm == nil {
cell.vm = CellVM()
}
return cell
}
}
class TableViewControllerVM:ViewModel {
var dataSource:BehaviorSubject<[CellViewModelProtocol]> = BehaviorSubject(value: [])
let internalBag = DisposeBag()
override init() {
super.init()
dataSource.onNext(createDataSourceOf(size: 7))
self.handleState()
}
private func createDataSourceOf(size:Int) -> [CellViewModelProtocol] {
var arr:[CellViewModelProtocol] = []
for _ in 0..<size {
let cell = CellVM()
arr.append(cell)
}
return arr
}
private func handleState() {
}
}
具有类似的UI,因此在本地进行了测试,并且可以正常工作。但是代码不是很整齐。
ProfileCellViewModel
import Foundation
import RxSwift
import RxCocoa
struct ProfileCellViewModel {
let topText: String?
let centerText: String?
let bottomText: String?
let switchStatus: Bool?
// IMPORTANT!!!
var bibindRelay: BehaviorRelay<Bool>?
}
ProfileCell
import UIKit
import Swinject
import RxBiBinding
final class ProfileCell: TableViewCell {
@IBOutlet weak var topLabel: Label!
@IBOutlet weak var centerLabel: Label!
@IBOutlet weak var bottomLabel: Label!
@IBOutlet weak var onSwitch: Switch!
public var vm: ProfileCellViewModel? {
didSet {
if let topText = vm?.topText {
topLabel.isHidden = false
topLabel.text = topText
} else { topLabel.isHidden = true }
if let centerText = vm?.centerText {
centerLabel.isHidden = false
centerLabel.text = centerText
} else { centerLabel.isHidden = true }
if let bottomText = vm?.bottomText {
bottomLabel.isHidden = false
bottomLabel.text = bottomText
} else { bottomLabel.isHidden = true }
if let isSwitchOn = vm?.switchStatus {
onSwitch.isHidden = false
onSwitch.isOn = isSwitchOn
} else { onSwitch.isHidden = true }
// IMPORTANT!!!
if let behaviorRelay = vm?.bibindRelay {
(onSwitch.rx.controlProperty(editingEvents: .valueChanged,
getter: { $0.isOn }) { $0.isOn = $1 } <-> behaviorRelay)
.disposed(by: self.rx.reuseBag)
}
}
}
}
ProfileViewModel
import Foundation
import RxSwift
import RxCocoa
import Swinject
final class ProfileViewModel: ViewModel, ViewModelType {
private let apiService = Container.shared.resolve(IApiService.self)!
private let preferenceService = Container.shared.resolve(IPreferenceService.self)!
struct Input {
let loadUserProfileStarted: BehaviorRelay<Void>
}
struct Output {
let userItems: BehaviorRelay<[ProfileCellViewModel]>
let chatRelay: BehaviorRelay<Bool>
let callRelay: BehaviorRelay<Bool>
}
let input = Input(loadUserProfileStarted: BehaviorRelay<Void>(value: ()))
let output = Output(userItems: BehaviorRelay<[ProfileCellViewModel]>(value: []),
chatRelay: BehaviorRelay<Bool>(value: false),
callRelay: BehaviorRelay<Bool>(value:false))
override init() {
super.init()
input.loadUserProfileStarted
.flatMapLatest { self.apiService.getLocalUserProfile() }
.map { (user) -> [ProfileCellViewModel] in
return self.createProfileCellItems(user: user)
}
.asDriver(onErrorJustReturn: [])
.drive(output.userItems).disposed(by: disposeBag)
// IMPORTANT!!!
Observable.combineLatest(output.chatRelay,output.callRelay).pairwise().map { (arg0) -> Int in
let (pre, curr) = arg0
let preFlag = [pre.0,pre.1].filter { $0 == true }.count == 1
let currFlag = [curr.0,curr.1].filter { $0 == true }.count == 2
if preFlag && currFlag {
return [pre.0,pre.1].firstIndex(of: true) ?? 0
}
return -1
}.filter {$0 >= 0}.subscribe(onNext: { (value) in
[self.output.chatRelay,self.output.callRelay][value].accept(false)
}).disposed(by: disposeBag)
}
private func createProfileCellItems(user: User) -> [ProfileCellViewModel] {
let roleCellViewModel = ProfileCellViewModel(topText: R.string.i18n.role(),
centerText: nil,
bottomText: user.role,
switchStatus: nil)
let teamCellViewModel = ProfileCellViewModel(topText: R.string.i18n.team(),
centerText: nil,
bottomText: user.team,
switchStatus: nil)
let statusCellViewModel = ProfileCellViewModel(topText: R.string.i18n.status(),
centerText: nil,
bottomText: user.status?.toString(),
switchStatus: nil)
let sinceCellViewModel = ProfileCellViewModel(topText: R.string.i18n.member_since(),
centerText: nil,
bottomText:
Date.timeIntervalChangeToTimeStr(timeInterval: user.signUpTimestamp ?? 0, dateFormat: nil),
switchStatus: nil)
// IMPORTANT!!!
let chatCellViewModel = ProfileCellViewModel(topText: nil,
centerText: R.string.i18n.chat(),
bottomText: nil,
switchStatus: true,
bibindRelay: output.chatRelay)
// IMPORTANT!!!
let callCellViewModel = ProfileCellViewModel(topText: nil,
centerText: R.string.i18n.call(),
bottomText: nil,
switchStatus: true,
bibindRelay: output.callRelay)
return [roleCellViewModel,
teamCellViewModel,
statusCellViewModel,
sinceCellViewModel,
chatCellViewModel,
callCellViewModel]
}
}
我用// IMPORTANT!!!
标记了您应注意的代码
也许这段代码可以帮助您:
extension TableViewController {
// called from viewDidLoad
func bind() {
let cells = (0..<7).map { _ in UUID() } // each cell needs an ID
let active = ReplaySubject<UUID>.create(bufferSize: 1) // tracks which is the currently active cell by ID
Observable.just(cells) // wrap the array in an Observable
.bind(to: tableView.rx.items(cellIdentifier: "Cell", cellType: Cell.self)) { _, element, cell in
// this subscription causes the inactive cells to turn off
active
.map { $0 == element }
.bind(to: cell.toggleSwitch.rx.isOn)
.disposed(by: cell.disposeBag)
// this subscription watches for when a cell is set to on.
cell.toggleSwitch.rx.isOn
.filter { $0 }
.map { _ in element }
.bind(to: active)
.disposed(by: cell.disposeBag)
}
.disposed(by: disposeBag)
}
}