0

There is a UISwitch and a ControlProperty<Bool> which are bound each other. However I cannot use standard two-way binding operator <-> because of the following requirements:

If user turns UISwitch ON, I need to check some condition and, if not met, show an alert and revert UISwitch back to OFF state. ControlProperty must remain intact. If condition met, ControlProperty becomes true.

If user turns UISwitch OFF, ControlProperty becomes false.

If ControlProperty has been (externally) changed, UISwitch must reflect its state unconditionally (as normal binding works). The condition must not be checked.

I tried the following (maybe naive) approach:

property.bind(to: cell.switch.rx.isOn).disposed(by: cell.disposeBag)

cell.switch.rx.isOn.bind { [unowned self] isOn in
    if !isOn || viewModel.allowTurnEmailNotificationOn {
        property.onNext(isOn)
    } else {
        let alert = MyAlertViewController()
        present(alert, animated: true) { [weak cell] in
            // revert switch back to OFF state
            cell?.switch.isOn = false
        }
    }
}.disposed(by: cell.disposeBag)

Generally, it works, but there is a bug: when ControlProperty changes to TRUE due to some external event, it sets UISwitch ON, which in turn launches checking if !isOn || viewModel.allowTurnEmailNotificationOn. As a result, the alert appears. I only want to perform this check if user manually turns UISwitch ON.

Nick
  • 3,205
  • 9
  • 57
  • 108
  • Something else is going on that you haven't told us about. The `switch.rx.isOn` observable will not emit a value unless the *user* manipulates it. Programatic changes will not cause `isOn` to emit. Maybe it has something to do with the way you created the ControlProperty? – Daniel T. Sep 24 '21 at 00:39

1 Answers1

0
  1. Use Driver instead of bind, and observe the onNext event:
        let switchDriver = cell.switch.rx.isOn.asDriver()
        let aFlag = !isOn || viewModel.allowTurnEmailNotificationOn
        
        switchDriver
            .filter { aFlag }
            .drive(onNext: { isOn in
                property.onNext(isOn)
            })
            .disposed(by: cell.disposeBag)

        switchDriver
            .filter { !aFlag }
            .drive(onNext: { isOn in
                let alert = MyAlertViewController()
                present(alert, animated: true) { [weak cell] in
                    cell?.switch.setOn(false, animated: false)
                }
            })
            .disposed(by: cell.disposeBag)
  1. If you see the same issue then ignore the first event (by using .skip(1)). First event might be triggering due to observable setup.