【问题标题】:Only one switch is on只有一个开关打开
【发布时间】:2020-09-27 07:40:58
【问题描述】:

我正在努力应对以下挑战。我使用包含开关的自定义单元格创建了表格视图。我只想打开一个开关,例如,发射后我打开第三个开关,然后我打开第七个开关,因此第三个开关关闭,依此类推。我对单元使用 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() {

    }

}

//我的Cell实现

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

    }
}

【问题讨论】:

  • 查看this 对相同问题的回答,只需使用 SwiftUI 和 Combine。您应该可以使用相同的想法,只需将其转换为 RxSwift 和 UIKit。
  • 非常感谢@DávidPásztor!

标签: ios swift functional-programming switch-statement rx-swift


【解决方案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)
    }
}

【讨论】:

    【解决方案2】:

    有一个类似的用户界面,所以在本地测试过,它可以工作。但不是很整洁的代码。

    ProfileCellViewModel

    struct ProfileCellViewModel {
    
        // IMPORTANT!!!
        var bibindRelay: BehaviorRelay<Bool>?
    }
    

    个人资料单元

    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 {
    
    
                // IMPORTANT!!!
                if let behaviorRelay = vm?.bibindRelay {
                    (onSwitch.rx.controlProperty(editingEvents: .valueChanged,
                                                 getter: { $0.isOn }) { $0.isOn = $1 } <-> behaviorRelay)
                        .disposed(by: self.rx.reuseBag)
                }
    
            }
        }
    }
    

    ProfileViewModel

    final class ProfileViewModel: ViewModel, ViewModelType {
    
        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()
    
    
            // 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] {
    
    
            // 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!!!标记了你应该注意的代码

    【讨论】:

    • 您可以删除与答案无关的代码,这会使答案更加清晰......
    • @DanielT。想首先显示更多上下文,我可以稍后删除不相关的代码。
    猜你喜欢
    • 2017-11-21
    • 2013-03-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多