ios - RxCocoa - prevent multiple view controller pushes when there's lag -
as case both reactive , non-reactive ios projects alike, if have ui element (e.g. button or table view cell being selected) pushes view controller onto navigation stack, if there's lag reason (especially on older devices) repetitive taps can result in duplicate pushes , bad ux.
normally disable element after first tap.
for example:
@ibaction func mybuttontap() { button.isenabled = false dotherestoftheaction() }
i relatively new rxswift. trying figure out appropriate reactive way implement fix few bugs in app views pushed repetitively.
some thoughts:
could use debounce
or throttle
seems bandaid , won't fix every situation.
i'm thinking best way dispose of subscription once expected event has occurred.
let disposable = tableview.rx.itemselected .subscribe(onnext: { [weak self] indexpath in self?.performsegue(withidentifier: "mysegueidentifier", sender: self) }) ... func prepareforsegue() { mydisposable.dispose() finishprepareforsegue() }
although if want unsubscribe inside subscribe block, compiler complains using variable inside own initial value, makes sense. suppose there workarounds wonder, there better way? maybe reactive operator i'm missing?
tried searching around similar examples results limited.
thanks
edit: perhaps takeuntil
operator?
one thing see lot @ company use of rx's variable
logininflight
variable<boolean>
. defaulted false , when command run login flip true. boolean tied login button once user clicks login subsequent clicks not anything. implement wherever user can click on change screens make sure there isn't call / event in progress.
we follow mvvm here's example based on that. tried show barebones down below still makes sense below.
loginviewcontroller
class loginviewcontroller: uiviewcontroller { @iboutlet weak var signinbutton: uibutton! override func viewdidload() { super.viewdidload() ... // commandavailable talking above viewmodel? .logincommandavailable .subscribe(onnext: {[unowned self] (available: bool) in self.signinbutton.isenabled = available }) .adddisposableto(disposebag) signinbutton.rx.tap .map { // send login command return viewmodel?.logincommand() }.subscribe(onnext: { (result: loginresult) // if result successful can send user next screen }).adddisposableto(disposebag) } }
loginviewmodel
enum loginresult: error { case success case failure } class loginviewmodel { private let logininflight = variable<bool>(false) private var emailaddressproperty = variable<string>("") var emailaddress: driver<string> { return emailaddressproperty .asobservable() .subscribeon(concurrentdispatchqueuescheduler(queue: dispatchqueue.global())) .asdriver(onerrorjustreturn: "") } ... var logincommandavailable: observable<bool> { // let user login if login not happening , user entered email address return observable.combinelatest(emailaddressproperty.asobservable(), passwordproperty.asobservable(), logininflight.asobservable()) { (emailaddress: string, password: string, logininflight: bool) in return !emailaddress.isempty && !password.isempty && !logininflight } } func logincommand() -> driver<loginresult> { logininflight.value = true // make call login return authenticationservice.login(email: emailaddressproperty.value, password: passwordproperty.value) .map { result -> loginresult in logininflight.value = false return loginresult.success } } }
edit toggling command based on availability
loginviewcontroller
class loginviewcontroller: uiviewcontroller { @iboutlet weak var signinbutton: uibutton! override func viewdidload() { super.viewdidload() ... // commandavailable talking above viewmodel? .logincommandavailable .subscribe(onnext: {[unowned self] (available: bool) in self.signinbutton.isenabled = available }) .adddisposableto(disposebag) signinbutton.rx.tap .map { return viewmodel?.logincommandavailable }.flatmap { (available: bool) -> observable<loginresult> // send login command if available if (available) { return viewmodel?.logincommand() } }.subscribe(onnext: { (result: loginresult) // if result successful can send user next screen }).adddisposableto(disposebag) } }
Comments
Post a Comment