在 RxSwift-MVVM 架构中,触发 UI 元素(如弹出窗口和指示器)的最佳方式是什么?
In the RxSwift-MVVM architecture what is the best way to trigger UI elements like pop-ups and indicators?
问题是 - 哪里最好:
- 调用错误处理弹出窗口
- Show/Hide 加载指标
我的应用程序如下所示:
ViewController订阅模型变化时UI更新的触发器:
var viewModel: ViewModel = ViewModel()
...
viewModel.source.asObservable().subscribe(onNext: { (_ ) in
self.tableView.reloadData()
})
.disposed(by: bag)
ViewModel
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 时显示。但是我再次没有引用我的加载指示器引用所在的视图控制器。
想法?
我会在您的 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)
Call error handling popups
假设你有一些信号开始 api fetch
let someSignalWithIdToStartApiFetch = Observable.just(1)
此外,让我们想象一下,当您在出现错误时显示一些 "retry request" 弹出窗口,并且当用户单击 "retry" 按钮时,您将其绑定到某个观察者。然后将观察者转换为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([=13=]) })
.catchError({ Observable.just(Response.error([=13=])) })
})
.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({ [=15=].students })
.filterNil()
.bind(to: source)
.disposed(by: bag)
但错误案例应该绑定到触发弹出窗口的某个观察者:
apiFetch
.map({ [=16=].error })
.filterNil()
.bind(to: observerWhichShowsPopUpWithRetryButton)
.disposed(by: bag)
因此,当显示弹出窗口并且用户单击 "retry" - someSignalWhenUserAsksToRetryRequestAfterError
将触发并重试请求
Show/Hide loading indicator
我使用类似 this 的东西。它是一种特殊的结构,可以捕获可观察对象的 activity 。如何使用它?
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([=18=]) })
.catchError({ Observable.just(Response.error([=18=])) })
.share(replay: 1, scope: .whileConnected)
所以,activity of api fetch 被跟踪了。现在您应该 show/hide 您的 activity 视图。
let observableActivity = indicator.asObservable() // Observable<Bool>
let observableShowLoading = observableActivity.filter({ [=19=] == true })
let observableHideLoading = observableActivity.filter({ [=19=] == false })
将 observableShowLoading
和 observableHideLoading
绑定到 hide/show 函数。即使您有多个可能同时执行的请求 - 将它们全部绑定到一个 ActivityIndicator
.
希望对您有所帮助。编码愉快(^
问题是 - 哪里最好:
- 调用错误处理弹出窗口
- Show/Hide 加载指标
我的应用程序如下所示:
ViewController订阅模型变化时UI更新的触发器:
var viewModel: ViewModel = ViewModel()
...
viewModel.source.asObservable().subscribe(onNext: { (_ ) in
self.tableView.reloadData()
})
.disposed(by: bag)
ViewModel
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 时显示。但是我再次没有引用我的加载指示器引用所在的视图控制器。
想法?
我会在您的 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)
Call error handling popups
假设你有一些信号开始 api fetch
let someSignalWithIdToStartApiFetch = Observable.just(1)
此外,让我们想象一下,当您在出现错误时显示一些 "retry request" 弹出窗口,并且当用户单击 "retry" 按钮时,您将其绑定到某个观察者。然后将观察者转换为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([=13=]) })
.catchError({ Observable.just(Response.error([=13=])) })
})
.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({ [=15=].students })
.filterNil()
.bind(to: source)
.disposed(by: bag)
但错误案例应该绑定到触发弹出窗口的某个观察者:
apiFetch
.map({ [=16=].error })
.filterNil()
.bind(to: observerWhichShowsPopUpWithRetryButton)
.disposed(by: bag)
因此,当显示弹出窗口并且用户单击 "retry" - someSignalWhenUserAsksToRetryRequestAfterError
将触发并重试请求
Show/Hide loading indicator
我使用类似 this 的东西。它是一种特殊的结构,可以捕获可观察对象的 activity 。如何使用它?
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([=18=]) })
.catchError({ Observable.just(Response.error([=18=])) })
.share(replay: 1, scope: .whileConnected)
所以,activity of api fetch 被跟踪了。现在您应该 show/hide 您的 activity 视图。
let observableActivity = indicator.asObservable() // Observable<Bool>
let observableShowLoading = observableActivity.filter({ [=19=] == true })
let observableHideLoading = observableActivity.filter({ [=19=] == false })
将 observableShowLoading
和 observableHideLoading
绑定到 hide/show 函数。即使您有多个可能同时执行的请求 - 将它们全部绑定到一个 ActivityIndicator
.
希望对您有所帮助。编码愉快(^