如何在SwiftUI中声明viewModel对象?

ny6fqffe  于 2023-10-15  发布在  Swift
关注(0)|答案(3)|浏览(120)

我试图使用一个@ObservableObject viewModel,它在我的视图结构中被声明为@ObservedObject。问题是,当viewModel更改它的“domains”属性@Published var时,UI不会更新。另外,我在init(){}中调用的getDomains()函数中更改域。它被称为两次,为什么会发生这种情况?下面是我的viewModel代码:

import Combine

class DomainsViewModel: ObservableObject {

    @Published var domains: [InterestDomain] = [InterestDomain]()
    
    init() {
        self.getDomains { (response) in
            print(response)
        }
    }
    
    func getDomains(completion: @escaping (Bool) -> Void) {
        NetworkEngine.shared.appNetwork.getInterestDomains { result in
            self.domains.removeAll()
            switch result {
            case .success(let domainsOfInterest):
                if let domainsList = domainsOfInterest {
                    self.domains = domainsList
                }
                completion(true)
            case .failure(_):
                completion(false)
            }
        }
    }

}

视图代码:导入Foundation导入SwiftUI导入合并

struct DomainsOfInterestView: View {

    @ObservedObject var viewModel: DomainsViewModel = DomainsViewModel()
    
    @State var isActive = true
    
    var body: some View {
            VStack(alignment: .center) {
                HStack {
                    Text("Choose domains of interest for your profile")
                        .font(.headline)
                    Spacer()
                }.padding(.bottom, 16)
                
                ForEach(viewModel.domains.indices) { index in
                    DomainOfInterestElement(isActive: self.$isActive, domain: self.viewModel.domains[index].name)
                }

                OrangeButton(action: {
                }) {
                    Text("Save")
                }.padding(.top, 30)
                    .padding([.leading, .trailing], 30)
                Spacer()
            }.padding([.leading, .trailing], 12)
            .navigationBarTitle("Domains of interest")
    }
}

struct DomainsOfInterestView_Previews: PreviewProvider {
    static var previews: some View {
    DomainsOfInterestView()
    }
}

struct DomainOfInterestElement: View {
    @Binding var isActive: Bool
    var domain: String
    var body: some View {
        Button(action: {
            self.isActive.toggle()
        }) {
            VStack {
                Divider().padding(.bottom, 12)
                HStack {
                    checkBoxView()
                        .frame(width: 36, height: 36)
                    textView().font(.custom("SFProDisplay-Regular", size: 16))
                    Spacer()
                }
            }
        }
    }
    
    func checkBoxView() -> Image {
        switch isActive {
        case true:
            return Image(uiImage: #imageLiteral(resourceName: "check-box-active-2-1")).renderingMode(.original)
        case false:
            return Image(uiImage: #imageLiteral(resourceName: "check-box-active-1")).renderingMode(.original)
        }
    }
    func textView() -> Text {
        switch isActive {
        case true:
            return Text(domain)
                .foregroundColor(.black)
        case false:
            return Text(domain)
                .foregroundColor(Color.orGrayColor)
        }
    }
}

有人能帮帮我吗?谢谢.

vs91vp4v

vs91vp4v1#

DomainsOfInterestView在其视图模型的每次更新时都会重新创建。每次都用新的DomainsViewModel示例初始化视图模型属性。新的视图模型将domains属性设置为[InterestDomain](),并将再次调用self.getDomains
首先,最好在初始化器中将viewModel注入到DomainsOfInterestView中,正如@youjin在上一篇文章的评论中所写的那样。此外,如果您的最低部署目标允许,您可以使用@StateObject而不是@ObservedObject。在这种情况下,viewModel属性不会在每次更新视图时重置。

vaqhlq81

vaqhlq812#

ForEach是一个View,而不是传统的for循环。将您的模型设置为Identifiable,并将ForEach更改为:

ForEach(fetcher.domains) { domain in

此外,你的fetcher必须是@StateObject,否则它会在每次重新初始化DomainsOfInterestView时丢失。
您的获取对象缺少取消支持。你可以通过将fetcher对象更改为.task来自动获得它,例如。

.task {
    do {
        domains = try await NetworkEngine.shared.appNetwork.getInterestDomains()
    }
    catch {
        message = error.localizedDescription
    }
}
hyrbngr7

hyrbngr73#

引用here的答案
如果你的数据可以改变,你需要一个动态的ForEach循环(带有一个显式的id参数):

ForEach(viewModel.domains.indices, id: \.self) { index in // <=
    // ...
 }

在某些情况下,当你试图修改ForEach循环中使用的数组时,Xcode会警告你:
ForEach(:content:)应该只用于常量数据。相反,请将数据符合Identifiable或使用ForEach(:id:content:)并提供显式id!

测试代码

struct DomainsOfInterestView: View {

    @ObservedObject var viewModel: DomainsViewModel = DomainsViewModel()
    
    @State var isActive = true
    
    var body: some View {
            VStack(alignment: .center) {
                HStack {
                    Text("Choose domains of interest for your profile")
                        .font(.headline)
                    Spacer()
                }.padding(.bottom, 16)
                
                ForEach(viewModel.domains.indices, id: \.self) { index in // <=
                    Text(String(index))
                }

                Button(action: {
                }) {
                    Text("Save")
                }.padding(.top, 30)
                    .padding([.leading, .trailing], 30)
                Spacer()
            }.padding([.leading, .trailing], 12)
            .navigationBarTitle("Domains of interest")
    }
}

class DomainsViewModel: ObservableObject {

    @Published var domains: [Int] = [Int]()
    
    init() {
        self.getDomains { (response) in
            print(response)
        }
    }
    
    func getDomains(completion: @escaping (Bool) -> Void) {
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            self.domains = [1,2,3,4,5]
            completion(true)
        }
    }

}

在iOS 14 beta 6上使用Xcode 12 beta 6进行测试

相关问题