首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >带有SwiftUI ObservedObject的MVVM

带有SwiftUI ObservedObject的MVVM
EN

Stack Overflow用户
提问于 2022-02-10 03:32:06
回答 3查看 1.8K关注 0票数 0

更新(2/10/22 @11:30 CST):

我重写了工作流,以便我的viewModel符合ObservableObject协议,而不再符合CreateUser类。现在,我的变量正在正确地更新(在设置print("\(viewModel.loadingStatus)")值之前和之后通过loadingState进行测试)。

但是,我的视图使用开关语句来显示视图的当前状态(即loadingState上的开关)不更新,即使loadingState在viewModel中的值被正确设置,而且我能够使用该print语句在视图本身中显示正在设置它。

我甚至尝试将viewModel的@Published设置为:

代码语言:javascript
复制
@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。

下面是一些需要进一步解释的代码(显然,这篇文章的内容已经被简化了,但是如果您阅读了上面的解释,您就会明白这一点):

主要观点:

代码语言:javascript
复制
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)
        }
    }
}

视图模型:

代码语言:javascript
复制
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:

代码语言:javascript
复制
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 = ""
}
EN

回答 3

Stack Overflow用户

发布于 2022-02-10 14:15:00

我们在SwiftUI中不使用MVVM,SwiftUI为我们管理屏幕上的视图,比如UILabels等等,我们使用视图结构中的视图数据,因此不需要额外的视图模型对象。我们也没有在我们的sink中使用ObservableObject,而是assign@Published,后者完成了pipleline,并将它的生命周期与对象绑定(不需要可取消的)。

由于在任务中使用异步/等待,您甚至不再需要组合的ObservableObject,只需使用.task修饰符并在@State上设置结果。

看看任务(id:优先级:),您会注意到当id param更改并取消以前的任务时,它可以重新运行该任务。现在只需将等待结果设置为@State即可,它既可以是数据数组,也可以是包含数据和其他相关vars的自定义结构。

但是,我们确实使用ObservableObject引用类型来管理值类型模型数据的生命周期,即持久化或同步它。

票数 3
EN

Stack Overflow用户

发布于 2022-02-10 16:41:23

我认为,查看您的NavigationLink和将视图模型从父视图传递到表单的多个页面的方式是很重要的。不过,我看不出你的帖子是怎么做到的,所以我的答案是猜测。

我的假设是,UsernameView是表单的多个页面之一,表单中的所有页面都使用@StateObject var viewModel: ViewModel。如果您想要在所有这些视图之间共享相同的视图模型,这将是一个危险的标志。@StateObject表示视图拥有该对象,并将在视图的生存期内保持该对象。另一方面,@ObservedObject是对应该传递到视图中的ObservableObject的引用。如果表单的多个页面都使用@StateObject,那么您构建事物的方式可能会得到视图模型的不同实例化,这就是为什么在其他页面中看到上一页输入的默认值。

这是一个简单的,高层次的概述,我将如何去做。

代码语言:javascript
复制
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)
        }
    }
}
代码语言:javascript
复制
struct ChildView1: View {
    @ObservedObject var viewModel: ViewModel
    var body: some View {
        Text(viewModel.username)
    }
}
代码语言:javascript
复制
struct ChildView2: View {
    @ObservedObject var viewModel: ViewModel
    var body: some View {
        Text(viewModel.username)
    }
}

该示例将在两个子视图中显示相同的用户名。父程序通过@StateObject对视图模型执行了一次实例化,并将其传递给子模型,而无需将模型重新实例化到@ObservedObject中。这样,您将在视图之间正确地共享内存中的同一个对象。

票数 0
EN

Stack Overflow用户

发布于 2022-02-10 22:01:13

最后,我忘记了尝试使用MVVM工作流。不幸的是,尽管我对所有内容进行了重新编码并使其非常接近于工作,但嵌套的ObservedObjects并没有更新视图。经过相当多的研究,我发现有几篇文章表达了同样的观点。

相反,我决定遵循malhal的建议,将所有内容分解为单独的视图、结构和类。这是一个严重的痛苦,但希望它将使代码更容易处理,随着事情的发展。

我很感谢大家的意见。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/71059468

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档