2

I am looking for solution for a problem I am facing. I have TableView which has multiple cell and each cell has a UISwitch and state of that switch (either on/off) is being set like this:

viewModel.permissions
        .drive(tblMessageTypes.rx.items(cellIdentifier: "PermissionCellIdentifier", cellType: PermissionTableViewCell.self)) { [switchSubject] _, item, cell in
            cell.backgroundColor = .clear
            cell.lblTitle.text = item.permissionTitle
            cell.lblDetail.text = item.permissionDescirption
            cell.selectionStyle = .none
            cell.switchPermission.isOn = item.permissionValue
            cell.switchPermission.isEnabled = !item.isPermissionReadOnly

            cell.switchPermission.rx.controlEvent(.valueChanged)
                .withLatestFrom(cell.switchPermission.rx.value)
                .map { isOn in
                    var newPermission = item
                    newPermission.permissionValue = isOn
                    return newPermission
                }
                .bind(to: switchSubject)
                .disposed(by: cell.disposeBag)
        }
        .disposed(by: disposeBag)

So when Switch is toggled, I am passing an current cell value with update Switch state and based on that I am calling api in my VM like this:

let serverReponse =  inputs.switchToggle
        .map { permission in
            let dicto = permission.toDictionary()
            let parameters = ["permissions": [dicto]]
            return parameters
        } .flatMapLatest { parameters in
            userService.updateUserPermission(parameters: parameters)
                .trackActivity(inputs.indicator)
                .materialize()
        }
        .share()

Now the issue I have is, if api is failed due to any reason, How should that UISwitch should fallback to initial state, i.e if it was Off and user toggled it to On State and Api was failed it should fall back to Off State.

Shabir jan
  • 2,295
  • 2
  • 23
  • 37

1 Answers1

0

I would expect to see something like the below. The things to note about this code:

  • All "permission" objects have some way of uniquely identifying each cell, I'm using UUID here, but you might already have an ID of some sort.
  • The "permissions" Observable in the ViewModel is used to update each individual cell. All the cells subscribe to this, map out their own Permission object and display it.
  • As you already have in your view controller, all cells send an updated permission through the switchToggle Observable.

The below code compiles and resets the cells as required. I'm relying on the zip operator to ensure that I am combining the permission that was sent to the request with the response from the server. If the server errors, the permissionValue in the appropriate object in the dictionary will be set to the opposite of what was sent to the server.

The more astute observers (pun intended) of the code below, you will notice that when the server request fails, the permissions object will emit but it won't actually change state since it was the switch that was toggled, not the permissionValue in the dictionary. However, that emission will get picked up by the switch which will cause it to reset itself.

struct Inputs {
    let initialPermissions: [Permission]
    let switchToggle: Observable<Permission>
}

struct ViewModel {

    let permissions: Observable<[UUID: Permission]>

    init(_ inputs: Inputs, userService: UserService) {
        let serverReponse =  inputs.switchToggle
            .map { permission in
                let dicto = permission.toDictionary()
                let parameters = ["permissions": [dicto]]
                return parameters
            }
            .flatMapLatest { parameters in
                userService.updateUserPermission(parameters: parameters)
                    .materialize()
            }
            .share()

        let perms = Dictionary(grouping: inputs.initialPermissions, by: { $0.id })
            .mapValues { $0.first! }

        permissions = Observable.zip(inputs.switchToggle, serverReponse)
            .filter { $0.1.error != nil }
            .map { Permission(id: $0.0.id, permissionValue: !$0.0.permissionValue) }
            .scan(into: perms, accumulator: { state, update in
                state[update.id] = update
            })
            .startWith(perms)
    }
}
Daniel T.
  • 32,821
  • 6
  • 50
  • 72
  • I tried the solution you provided but i am unable to do this: ```let perms = Dictionary(grouping: inputs.initialPermissions, by: { $0.id }) .mapValues { $0.first! }```, As I am not passing [Permission] from my VC, as I have Observable[Permission] in my VM. Can you please tell me how can i refactor above code to fix this issue. – Shabir jan Apr 01 '19 at 20:05