只有一个开关打开

问题描述 投票:1回答:2

我正在努力应对以下挑战。我使用包含开关的自定义单元格创建了表视图。我只想打开一个开关,例如,启动后我打开了3rd开关,然后又打开了7th开关,因此第3个开关被关闭,依此类推。我对单元使用了rx +协议,但并不完全了解如何确定切换了哪个开关。以前,我将使用过滤器或映射在dataSource数组中查找打开了开关并以某种方式处理的数据,但现在我搞砸了。我不确定不使用表视图委托方法是否有可能。非常感谢,希望有人能解释我错了。

//我的单元格看起来像这样:enter image description here

// 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() {

    }
}
ios swift functional-programming switch-statement rx-swift
2个回答
1
投票

具有类似的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!!!标记了您应注意的代码


1
投票

也许这段代码可以帮助您:

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)
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.