在 RxSwift-MVVM 架构中,触发弹出窗口和指示器等 UI 元素的最佳方式是啥?
Posted
技术标签:
【中文标题】在 RxSwift-MVVM 架构中,触发弹出窗口和指示器等 UI 元素的最佳方式是啥?【英文标题】:In the RxSwift-MVVM architecture what is the best way to trigger UI elements like pop-ups and indicators?在 RxSwift-MVVM 架构中,触发弹出窗口和指示器等 UI 元素的最佳方式是什么? 【发布时间】:2018-01-31 14:26:16 【问题描述】:问题是 - 在哪里最好:
-
调用错误处理弹出窗口
显示/隐藏加载指示器
我的应用如下所示:
ViewController 订阅模型变化时触发 UI 更新:
var viewModel: ViewModel = ViewModel()
...
viewModel.source.asObservable().subscribe(onNext: (_ ) in
self.tableView.reloadData()
)
.disposed(by: bag)
视图模型
var source = Variable<[Student]>([])
并且在初始化时获取源输出
api.fetchSourceOutput(id: id)
.do(onError: (error) in
//show error here???
)
.catchErrorJustReturn([])
.bind(to: source)
.disposed(by: bag)
我不能只将 ViewController 的引用传递给 ViewModel,这会破坏它独立于 UI 的想法。那么我应该如何在视图控制器的视图中调用错误弹出窗口?获取顶视图控制器也不是一个好的选择,因为我可能需要特定的视图来显示我的弹出窗口。 在 viewModel 内部调用 onNext 并隐藏 onCompleted 时,可以显示加载指示器。但是我再次没有引用我的加载指示器引用所在的视图控制器。
想法?
【问题讨论】:
【参考方案1】:调用错误处理弹出窗口
假设你有一些启动 api fetch 的信号
let someSignalWithIdToStartApiFetch = Observable.just(1)
另外,让我们想象一下,当您在错误时显示一些“重试请求”弹出窗口并且当用户单击“重试”按钮时,您会将其绑定到某个观察者。然后将观察者转换为Observable
。所以你有一些第二个信号:
let someSignalWhenUserAsksToRetryRequestAfterError = Observable.just(())
当您需要重试请求时,您可以通过这种方式从someSignalWithIdToStartApiFetch
获取最后一个 id:
let someSignalWithIdToRetryApiFetch = someSignalWhenUserAsksToRetryRequestAfterError
.withLatestFrom(someSignalWithIdToStartApiFetch)
.share(replay: 1, scope: .whileConnected)
然后你结合两个信号并发出请求:
let apiFetch = Observable
.of(someSignalWithIdToRetryApiFetch, someSignalWithIdToStartApiFetch)
.merge()
.flatMap( id -> Observable<Response> in
return api
.fetchSourceOutput(id: id)
.map( Response.success($0) )
.catchError( Observable.just(Response.error($0)) )
)
.share(replay: 1, scope: .whileConnected)
如您所见,错误被捕获并转换为某种结果。例如:
enum Response
case error(Error)
case success([Student])
var error: Error?
switch self
case .error(let error): return error
default: return nil
var students: [Student]?
switch self
case .success(let students): return students
default: return nil
然后你像往常一样获得成功的结果:
apiFetch
.map( $0.students )
.filterNil()
.bind(to: source)
.disposed(by: bag)
但是错误情况应该绑定到一些触发弹出窗口的观察者:
apiFetch
.map( $0.error )
.filterNil()
.bind(to: observerWhichShowsPopUpWithRetryButton)
.disposed(by: bag)
因此,当显示弹出窗口并且用户单击“重试”时 - someSignalWhenUserAsksToRetryRequestAfterError
将触发并重试请求
显示/隐藏加载指示器
我使用类似this 的东西。它是一种特殊的结构,可以捕捉可观察对象的活动。如何使用它?
let indicator = ActivityIndicator()
还有问题第一部分的一些代码。
let apiFetch = Observable
.of(someSignalWithIdToRetryApiFetch, someSignalWithIdToStartApiFetch)
.merge()
.flatMap( id -> Observable<[Student]> in
return indicator
.trackActivity(api.fetchSourceOutput(id: id))
)
.map( Response.success($0) )
.catchError( Observable.just(Response.error($0)) )
.share(replay: 1, scope: .whileConnected)
因此,api fetch 的活动被跟踪。现在您应该显示/隐藏您的活动视图。
let observableActivity = indicator.asObservable() // Observable<Bool>
let observableShowLoading = observableActivity.filter( $0 == true )
let observableHideLoading = observableActivity.filter( $0 == false )
绑定observableShowLoading
和observableHideLoading
以隐藏/显示函数。即使您有多个可能同时执行的请求 - 将它们全部绑定到单个 ActivityIndicator
。
希望对您有所帮助。快乐编码(^
【讨论】:
谢谢。但是,如果 apiFetch 发送错误,订阅将被终止,并且不再点击按钮将重复该过程。要修复它,您可以使用 RxSwiftExt 中的“materialize, elements, errors”。 @RealNmae 很好... apiFetch 和错误捕获都应该在 flatMap 中。我的错误 你能把你的答案给我看吗?因为在我看来,每当您发现错误时,订阅就会完成然后释放。 @RealNmae 不,它不像您期望的那样工作。你可以发现一个错误!我会修正我的答案【参考方案2】:我会在您的 viewModel
中进行此更改:
// MARK: - Properties
let identifier = Variable(0)
lazy var source: Observable<[Student]> = identifier.asObservable()
.skip(1)
.flatMapLatest id in
return api.fetchSourceOutput(id: id)
.observeOn(MainScheduler.instance)
.share(replay: 1)
...
// MARK: - Initialization
init(id: Int)
identifier.value = id
...
那么,在你的ViewController
:
viewModel.source
.subscribe(onNext: _ in
self.tableView.reloadData()
, onError: error in
// Manage errors
)
.disposed(by: bag)
【讨论】:
以上是关于在 RxSwift-MVVM 架构中,触发弹出窗口和指示器等 UI 元素的最佳方式是啥?的主要内容,如果未能解决你的问题,请参考以下文章