更新(2/10/22 @11:30 CST):
我重写了工作流,以便我的viewModel符合ObservableObject协议,而不再符合CreateUser类。现在,我的变量正在正确地更新(在设置print("\(viewModel.loadingStatus)")值之前和之后通过loadingState进行测试)。
但是,我的视图使用开关语句来显示视图的当前状态(即loadingState上的开关)不更新,即使loadingState在viewModel中的值被正确设置,而且我能够使用该print语句在视图本身中显示正在设置它。
我甚至尝试将viewModel的@Published设置为:
@Published var loadingState: LoadingState = .loading {
willSet {
objectwillchange.send()
}
}当然,根据苹果公司的文档,这种行为与@自行发布的行为完全相同,对解决这一问题没有任何帮助。
其他一切似乎都正常工作,除了视图开关情况之外,当loadingState在viewModel中更改时不更新。
原文:
我有一个多页表单,其中表单的所有视图共享相同的ObservableObject。我想要做的是将每个单独的视图分割成一个MVVM工作流,这样UI内容就在视图中,数据内容在viewModel中。
问题是,由于所有这些视图都共享相同的ObservableObject,而且每个视图中的函数都大量使用这些ObservableObjects (存储变量、使用其他函数等),所以viewModel不能只是自己的ObservableObject,因为我需要为所有东西编写一个自定义的合并器(而且有很多变量)。这也意味着在我的NavigationLink和视图的预览中,我必须调用每个单独初始化的vars。
CreateUser (共享ObservableObject将所有表单变量作为@已发布的vars)是在用户移动页面时设置所有表单变量的地方。我想出的最好办法是使我的viewModel与CreateUser相一致,而不是简单的ObservableObject。这似乎是可行的,经过相当多的重新编码,没有更多的错误和我的应用程序。话虽如此,但由于它不起作用,我觉得这不是正确的方法。
不幸的是,viewModel似乎没有更新任何CreateUser变量,也没有准确引用正在设置的CreateUser变量。我使用表单的第3页(输入用户名)测试MVVM体系结构,并要求它从第2页打印变量,它返回默认值,而不是实际设置的值。
这意味着我的数据没有被正确地传递,并且我的变量没有以应有的方式被引用,所以第3页( MVVM视图)显示加载状态,即使它应该在json提取完成后显示加载状态。
当您在每个视图中使用多个其他ObservedObjects时,建立MVVM工作流的正确过程是什么?这是用于iOS 15+的最新的SwiftUI/XCode。
下面是一些需要进一步解释的代码(显然,这篇文章的内容已经被简化了,但是如果您阅读了上面的解释,您就会明白这一点):
主要观点:
import Combine
import SwiftUI
struct UsernameView: View {
@StateObject var viewModel: ViewModel
var body: some View {
VStack {
List {
switch viewModel.loadingState {
case .loading:
Section {
Text("I am loading")
}
case .loaded:
Section {
Text("I am loaded")
}
case .failed:
Section {
Text("Something went wrong")
}
}
}
}
.task {
viewModel.userActivity = Date.now
jsonFetch.sink (receiveCompletion: { completion in
switch completion {
case .failure:
viewModel.loadingState = .failed
case .finished:
viewModel.loadingState = .loaded
}
},
receiveValue: { loadedData in
viewModel.userData = loadedData
}).store(in: &viewModel.dataTransfer.requests)
}
}
}视图模型:
import Combine
import SwiftUI
extension UsernameView {
class ViewModel: CreateUser {
@ObservedObject var textBindingManager = TextBindingManager(limit: 15)
@Published var loadingState: LoadingState = .loading
@Published var userData: [Usernames] = []
@Published var usernameExists: [UsernameExists] = []
func editingChanged(_ value: String) {
username = String(value.prefix(textBindingManager.characterLimit))
let jsonFetchUserExistsURL = URL(string: "https://api.foo.com/userselect?user=\(user)")
let jsonFetchUserExistsTask = dataTransfer.jsonFetch(jsonFetchUserExistsURL, defaultValue: [UsernameExists]())
if username.count >= 8 {
guard network.isNetworkActive else { loadingAlert = true; return }
Task {
status = .loading
usernameExists.removeAll()
jsonFetchUserExistsTask.sink (receiveCompletion: { completion in
switch completion {
case .failure:
self.loadingState = .failed
case .finished:
return
}
},
receiveValue: { loadedUserExists in
self.usernameExists = loadedUserExists
}).store(in: &dataTransfer.requests)
}
}
}
extension UsernameView.ViewModel {
enum LoadingState {
case loading, loaded, failed
}
}CreateUser:
import Combine
import SwiftUI
class CreateUser: ObservableObject {
@ObservedObject var dataTransfer = NetworkTransfer()
let network: NetworkMonitor
init(network: NetworkMonitor) {
self.network = network
}
@Published var userActivity = Date.now
@Published var userActivityAlert = false
@Published var birthday = Date()
@Published var username = ""
}发布于 2022-02-10 14:15:00
我们在SwiftUI中不使用MVVM,SwiftUI为我们管理屏幕上的视图,比如UILabels等等,我们使用视图结构中的视图数据,因此不需要额外的视图模型对象。我们也没有在我们的sink中使用ObservableObject,而是assign到@Published,后者完成了pipleline,并将它的生命周期与对象绑定(不需要可取消的)。
由于在任务中使用异步/等待,您甚至不再需要组合的ObservableObject,只需使用.task修饰符并在@State上设置结果。
看看任务(id:优先级:),您会注意到当id param更改并取消以前的任务时,它可以重新运行该任务。现在只需将等待结果设置为@State即可,它既可以是数据数组,也可以是包含数据和其他相关vars的自定义结构。
但是,我们确实使用ObservableObject引用类型来管理值类型模型数据的生命周期,即持久化或同步它。
发布于 2022-02-10 16:41:23
我认为,查看您的NavigationLink和将视图模型从父视图传递到表单的多个页面的方式是很重要的。不过,我看不出你的帖子是怎么做到的,所以我的答案是猜测。
我的假设是,UsernameView是表单的多个页面之一,表单中的所有页面都使用@StateObject var viewModel: ViewModel。如果您想要在所有这些视图之间共享相同的视图模型,这将是一个危险的标志。@StateObject表示视图拥有该对象,并将在视图的生存期内保持该对象。另一方面,@ObservedObject是对应该传递到视图中的ObservableObject的引用。如果表单的多个页面都使用@StateObject,那么您构建事物的方式可能会得到视图模型的不同实例化,这就是为什么在其他页面中看到上一页输入的默认值。
这是一个简单的,高层次的概述,我将如何去做。
struct ParentView: View {
@StateObject var viewModel = ViewModel()
@State var page = 1
var body: some View {
switch page {
case 1:
ChildView1(viewModel: self.viewModel)
case 2:
ChildView2(viewModel: self.viewModel)
}
}
}struct ChildView1: View {
@ObservedObject var viewModel: ViewModel
var body: some View {
Text(viewModel.username)
}
}struct ChildView2: View {
@ObservedObject var viewModel: ViewModel
var body: some View {
Text(viewModel.username)
}
}该示例将在两个子视图中显示相同的用户名。父程序通过@StateObject对视图模型执行了一次实例化,并将其传递给子模型,而无需将模型重新实例化到@ObservedObject中。这样,您将在视图之间正确地共享内存中的同一个对象。
发布于 2022-02-10 22:01:13
最后,我忘记了尝试使用MVVM工作流。不幸的是,尽管我对所有内容进行了重新编码并使其非常接近于工作,但嵌套的ObservedObjects并没有更新视图。经过相当多的研究,我发现有几篇文章表达了同样的观点。
相反,我决定遵循malhal的建议,将所有内容分解为单独的视图、结构和类。这是一个严重的痛苦,但希望它将使代码更容易处理,随着事情的发展。
我很感谢大家的意见。
https://stackoverflow.com/questions/71059468
复制相似问题