1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【RxSwift】TableViewのCell内にUISwitchを配置する

Posted at

はじめに

RxSwiftを使ってTableViewのCellに配置したUISwitchを操作しようとしたところ、想定外の挙動に悩まされたので、ハマったポイントをまとめてみました

環境

Xcode 13.3
Swift 5.6
RxSwift 6.2.0

内容

RxSwiftを使いましたが複数セクション&複数カスタムセルを表示する必要があったため、
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCellを使いました
コードは対象箇所のみにしたため、分かりにくいかもしれません

このような画面の実装をしました(他にもセクションが存在します)

実装パターン① : CustomCell内に処理を書く

UITableViewCell
class CustomCell: UITableViewCell {
    @IBOutlet private weak var playerLabel: UILabel!
    @IBOutlet private weak var playerSwitch: UISwitch!
    var disposeBag = DisposeBag()

    override func prepareForReuse() {
        super.prepareForReuse()
        disposeBag = DisposeBag()
    }    
 
    func render(player: Player, delegate: TableViewCellDelegate) {
        self.playerLabel.text = player.name
        self.playerLabel.textColor = player.gender ? .label : .red

        playerSwitch.setOn(player.isPlaying, animated: false)

        playerSwitch.rx.controlEvent(.valueChanged)
            .withLatestFrom(playerSwitch.rx.value)
            .subscribe(onNext : { isPlaying in
                 delegate.handlePlayerSwitchChanged(playerId: player.playerId, isPlaying: isPlaying)
            }).disposed(by: disposeBag)
    }
}

MainViewController

extension MainViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: R.reuseIdentifier.customCell, for: indexPath)!

        cell.render(player: viewModel.playerList.value[indexPath.row], delegate: self)
        return cell
    }
}

extension MainViewController: TableViewCellDelegate {
    func playerSwitchChanged(playerId: Int16, isPlaying: Bool) {
        viewModel.handlePlayerSwitchChanged(playerId: playerId, isPlaying: isPlaying)
    }
}

delegateを実装する必要がありますが、UISwitchの処理をセル側に書けるので少し読みやすくなるかと思います
セル側のprepareForReuseで一度disposeする必要があります

実装パターン② : ViewController内に処理を書く

UITableViewCell
class CustomCell: UITableViewCell {
    @IBOutlet private weak var playerLabel: UILabel!
    @IBOutlet weak var playerSwitch: UISwitch!
    var disposeBag = DisposeBag()

    override func prepareForReuse() {
        super.prepareForReuse()
        disposeBag = DisposeBag()
    }    
 
    func render(player: Player) {
        self.playerLabel.text = player.name
        self.playerLabel.textColor = player.gender ? .label : .red

        playerSwitch.setOn(player.isPlaying, animated: false)
    }
}

ViewController
extension MainViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: R.reuseIdentifier.customCell, for: indexPath)!

        let player = viewModel.playerList.value[indexPath.row]
        render(player: player)

        cell.playerSwitch.rx.controlEvent(.valueChanged)
            .withLatestFrom(playerSwitch.rx.value)
            .subscribe(onNext : { [weak self] isPlaying in
                 guard let self = self else { return }
                 viewModel.handlePlayerSwitchChanged(playerId: player.playerId, isPlaying: isPlaying)
            }).disposed(by: cell.disposeBag)
        return cell
    }
}

セル内にUISwitchの処理を書かない場合はdelegateの実装が不要になります
しかし複数セクション・複数カスタムセルがある場合は、パターン①の方が良さそうです
こちらもセル側のprepareForReuseで一度disposeする必要があります

ハマったポイント

  • UISwitchの.isSelected = で値をセットした
    これだと値をセットしたタイミングで値が変わったとカウントされるので、.setOnを使うべきでした

  • .rx.isOnをsubscribeしていた
    値が変更されたときのみ、処理を流すようにするべきでした

        playerSwitch.rx.controlEvent(.valueChanged)
            .withLatestFrom(playerSwitch.rx.value)
            .subscribe(onNext : { isPlaying in
  • Cellに値を持たせた
    BehaviorRelayなどを使いCellに値を持たせたところprepareForReuseでdisposeしても想定外の挙動が起こりました。やり方が悪かったのかもしれませんが、よく考えたら値を持たせる必要がなかったのでやめました

おわりに

RxSwiftの理解が足りてない以前にUISwitchの挙動も理解が足りてなく、思わぬところでハマってました。
困ったらprepareForReuseで一度disposeすれば全てOKだと思ってたところもあり、もう少し理解を深める必要がありそうです。

参考

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?