1

就像反应式和非反应式 iOS 项目一样,如果您有一个 UI 元素(例如,正在选择的按钮或表格视图单元格)将视图控制器推送到导航堆栈上,如果有一些滞后原因(尤其是在旧设备上)重复点击会导致重复推送,从而导致糟糕的用户体验。

通常,您可以在第一次点击后禁用该元素。

例如:

@IBAction func myButtonTap() { 
    button.isEnabled = false
    doTheRestOfTheAction()
}

我对 RxSwift 比较陌生。我正在尝试找出一种适当的反应式方法来实现这一点,以修复我的应用程序中视图被重复推送的一些错误。

一些想法:

可以使用debouncethrottle但看起来像创可贴,不一定能解决所有情况。

我目前认为最好的方法是在预期事件发生后处理订阅。

let disposable = tableView.rx.itemSelected
    .subscribe(onNext: { [weak self] indexPath in 
        self?.performSegue(withIdentifier: "MySegueIdentifier", sender: self)
    })

...

func prepareForSegue() {
    myDisposable.dispose()
    finishPrepareForSegue()
}

尽管如果您想在 subscribe 块内取消订阅,编译器会抱怨在其自己的初始值中使用变量,这是有道理的。我想有解决方法,但我想知道,有没有更好的方法?也许我缺少一个反应式操作员?

尝试搜索类似的示例,但结果有限。

谢谢

编辑:也许是takeUntil运营商?

可能相关的 SO question

4

2 回答 2

1

不是唯一的解决方案,但这似乎至少在推动表格视图选择的情况下运作良好。它使用takeUntil运算符来停止事件

myTableView.rx.itemSelected
.takeUntil(self.rx.methodInvoked(#selector(viewWillDisappear)))
.subscribe(onNext: { [weak self] indexPath in
    self?.performSegue(withIdentifier: "MySegueIdentifier", sender: self)
)}
.dispose(by: self.myDisposeBag)

虽然请注意,如果您可以返回视图控制器,那么您必须重新订阅,也许通过将订阅移动到viewDidAppear. 也许有一种更有效的方法,尽管不需要重新订阅。

另一种选择是take(1)代替takeUntil(…),但在返回视图控制器时仍需要重新订阅。

于 2017-09-11T22:12:12.103 回答
1

我在公司经常看到的一件事是 Rx 的使用,Variable类似于. 这默认为 false,当运行命令登录时,我们将其翻转为 true。该布尔值也与登录按钮相关联,因此一旦用户单击登录,任何后续单击都不会执行任何操作。您可以在用户单击某些内容以更改屏幕以确保没有正在进行的呼叫/事件的任何地方实现此功能。loginInFlightVariable<boolean>

我们遵循 MVVM,所以这里有一个基于此的示例。我试图只显示它下面的准系统,所以希望下面的一切仍然有意义。

登录视图控制器

class LoginViewController: UIViewController {
    @IBOutlet weak var signInButton: UIButton!

    override func viewDidLoad() {
        super.viewDidLoad()

        ...

        // This commandAvailable is what I was talking about 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 was successful we can send the user to the next screen
            }).addDisposableTo(disposeBag)
    }
}

登录视图模型

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> {
        // We let the user login if login is not currently happening AND the user entered their 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 to login
        return authenticationService.login(email: emailAddressProperty.value, password: passwordProperty.value)
        .map { result -> LoginResult in
            loginInFlight.value = false
            return LoginResult.success
        }
    }
}

根据可用性编辑切换命令

登录视图控制器

class LoginViewController: UIViewController {
    @IBOutlet weak var signInButton: UIButton!

    override func viewDidLoad() {
        super.viewDidLoad()

        ...

        // This commandAvailable is what I was talking about 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 was successful we can send the user to the next screen
            }).addDisposableTo(disposeBag)
    }
}
于 2017-09-11T20:57:56.643 回答