使用 RxSwift 在网络请求后更新 SwiftUI 列表
Update SwiftUI List after network request using RxSwift
我需要在发出网络请求后更新 SwiftUI 列表。对于请求,我使用 Moya 方法结合触发器(输入和输出 - "Kickstarter")。
由于项目的结构,我不能使用 Combine 框架,虽然他们有很多有用的建议(我的情况不确定)。
简单联系人列表:
struct ContactList: View {
var viewModel: UserViewModel
var body: some View {
NavigationView {
List(viewModel.users) { contact in
NavigationLink(destination: ContactDetail(user: contact)) {
ContactRow(user: contact)
}
}
.navigationBarTitle(Text("Team Members"))
}
}
}
然后是视图模型
class UserViewModel {
let disposeBag = DisposeBag()
var users: [TeamMember] = []
init(users: [TeamMember] = []) {
let networkModel = UserNetworkModel()
networkModel.output.teamMembers.subscribe { (event) in
self.users.append(contentsOf: event.element.orEmpty)
}.disposed(by: disposeBag)
networkModel.output.error.subscribe(onNext: { error in
print(error.localizedDescription)
}).disposed(by: disposeBag)
networkModel.input.loadTrigger.onNext(Void())
self.users = users
}
}
和网络模型
class UserNetworkModel {
let disposeBag = DisposeBag()
let input: Input
let output: Output
struct Input {
let loadTrigger: AnyObserver<Void>
let searchTrigger: AnyObserver<String>
}
struct Output {
let teamMembers: Observable<[TeamMember]>
let error: Observable<Error>
}
internal let loadSubject = PublishSubject<Void>()
internal let searchSubject = BehaviorSubject<String>(value: "")
internal let errorSubject = PublishSubject<Error>()
internal let teamMembersSubject = BehaviorSubject<[TeamMember]>(value: [])
init() {
let service = MoyaProvider<TeamTarget>()
self.input = Input(loadTrigger: loadSubject.asObserver(), searchTrigger: searchSubject.asObserver())
self.output = Output(teamMembers: teamMembersSubject.asObservable(), error: errorSubject.asObservable())
let result = loadSubject.flatMapLatest { _ -> Observable<[TeamMember]> in
service.rx.request(.get).debug().mapArray(TeamMember.self).asObservable()
}.share(replay: 1)
Observable.combineLatest(result, searchSubject).map { (arg) in
let (members, filter) = arg
if filter.isEmpty {
return members
} else {
let searchText = try! self.searchSubject.value()
return members.filter({
return [[=12=].firstName, [=12=].lastName]
.compactMap({ [=12=] })
.first(where: { [=12=].hasPrefix(searchText) }) != nil
})
}
}.bind(to: teamMembersSubject).disposed(by: disposeBag)
result.subscribe(onError: { error in
self.errorSubject.onNext(error)
}).disposed(by: self.disposeBag)
}
}
是否可以通过这种方式更新用户数组?还是只有 Combine 可以轻松为我完成?
谢谢你的时间。
虽然我找到了一些联合解决方案。 仍在等待 Rx 解决方案。
因此,我修复了它只是为 ContactList 属性 添加了 @ObservedObject 并为网络模型中的用户数组添加了 @Published 。代码少得多,原生但不是我想要的。
完整答案:
struct ContactList: View {
@ObservedObject var networkModel: NetworkModel
var body: some View {
NavigationView {
List(networkModel.users) { contact in
NavigationLink(destination: ContactDetail(user: contact)) {
ContactRow(user: contact)
}
}
.navigationBarTitle(Text("Team Members"))
}
}
}
#if DEBUG
struct ContactList_Previews: PreviewProvider {
static var previews: some View {
ContactList(networkModel: .init(users: contactData))
}
}
#endif
class NetworkModel: ObservableObject {
@Published var users = [User]()
init(users: [User] = []) {
getMembers()
}
private func getMembers() {
let provider = MoyaProvider<TeamTarget>()
provider.request(.get) { [weak self] (result) in
switch result {
case .success(let response):
do {
let result = try JSONDecoder().decode([User].self, from: response.data)
self?.users.append(contentsOf: result)
} catch {
print(error.localizedDescription)
}
case .failure(let error):
print(error.errorDescription.orEmpty)
}
}
}
}
我需要在发出网络请求后更新 SwiftUI 列表。对于请求,我使用 Moya 方法结合触发器(输入和输出 - "Kickstarter")。 由于项目的结构,我不能使用 Combine 框架,虽然他们有很多有用的建议(我的情况不确定)。
简单联系人列表:
struct ContactList: View {
var viewModel: UserViewModel
var body: some View {
NavigationView {
List(viewModel.users) { contact in
NavigationLink(destination: ContactDetail(user: contact)) {
ContactRow(user: contact)
}
}
.navigationBarTitle(Text("Team Members"))
}
}
}
然后是视图模型
class UserViewModel {
let disposeBag = DisposeBag()
var users: [TeamMember] = []
init(users: [TeamMember] = []) {
let networkModel = UserNetworkModel()
networkModel.output.teamMembers.subscribe { (event) in
self.users.append(contentsOf: event.element.orEmpty)
}.disposed(by: disposeBag)
networkModel.output.error.subscribe(onNext: { error in
print(error.localizedDescription)
}).disposed(by: disposeBag)
networkModel.input.loadTrigger.onNext(Void())
self.users = users
}
}
和网络模型
class UserNetworkModel {
let disposeBag = DisposeBag()
let input: Input
let output: Output
struct Input {
let loadTrigger: AnyObserver<Void>
let searchTrigger: AnyObserver<String>
}
struct Output {
let teamMembers: Observable<[TeamMember]>
let error: Observable<Error>
}
internal let loadSubject = PublishSubject<Void>()
internal let searchSubject = BehaviorSubject<String>(value: "")
internal let errorSubject = PublishSubject<Error>()
internal let teamMembersSubject = BehaviorSubject<[TeamMember]>(value: [])
init() {
let service = MoyaProvider<TeamTarget>()
self.input = Input(loadTrigger: loadSubject.asObserver(), searchTrigger: searchSubject.asObserver())
self.output = Output(teamMembers: teamMembersSubject.asObservable(), error: errorSubject.asObservable())
let result = loadSubject.flatMapLatest { _ -> Observable<[TeamMember]> in
service.rx.request(.get).debug().mapArray(TeamMember.self).asObservable()
}.share(replay: 1)
Observable.combineLatest(result, searchSubject).map { (arg) in
let (members, filter) = arg
if filter.isEmpty {
return members
} else {
let searchText = try! self.searchSubject.value()
return members.filter({
return [[=12=].firstName, [=12=].lastName]
.compactMap({ [=12=] })
.first(where: { [=12=].hasPrefix(searchText) }) != nil
})
}
}.bind(to: teamMembersSubject).disposed(by: disposeBag)
result.subscribe(onError: { error in
self.errorSubject.onNext(error)
}).disposed(by: self.disposeBag)
}
}
是否可以通过这种方式更新用户数组?还是只有 Combine 可以轻松为我完成? 谢谢你的时间。
虽然我找到了一些联合解决方案。 仍在等待 Rx 解决方案。 因此,我修复了它只是为 ContactList 属性 添加了 @ObservedObject 并为网络模型中的用户数组添加了 @Published 。代码少得多,原生但不是我想要的。
完整答案:
struct ContactList: View {
@ObservedObject var networkModel: NetworkModel
var body: some View {
NavigationView {
List(networkModel.users) { contact in
NavigationLink(destination: ContactDetail(user: contact)) {
ContactRow(user: contact)
}
}
.navigationBarTitle(Text("Team Members"))
}
}
}
#if DEBUG
struct ContactList_Previews: PreviewProvider {
static var previews: some View {
ContactList(networkModel: .init(users: contactData))
}
}
#endif
class NetworkModel: ObservableObject {
@Published var users = [User]()
init(users: [User] = []) {
getMembers()
}
private func getMembers() {
let provider = MoyaProvider<TeamTarget>()
provider.request(.get) { [weak self] (result) in
switch result {
case .success(let response):
do {
let result = try JSONDecoder().decode([User].self, from: response.data)
self?.users.append(contentsOf: result)
} catch {
print(error.localizedDescription)
}
case .failure(let error):
print(error.errorDescription.orEmpty)
}
}
}
}