简体   繁体   English

如何在 SwiftUI 中声明 viewModel 对象?

[英]How should the viewModel object be declared in SwiftUI?

I am trying to use an @ObservableObject viewModel which is declared as @ObservedObject inside my view struct.我正在尝试使用在我的视图结构中声明为 @ObservedObject 的 @ObservableObject viewModel。 The problem is that when the viewModel changes it's "domains" property @Published var, the UI is not updating.问题是,当 viewModel 更改它的“域”属性 @Published var 时,UI 不会更新。 Also, I change domains inside getDomains() function which is called inside init() {}.另外,我更改了在 init() {} 中调用的 getDomains() 函数内的域。 Looks like it is called twice, why is that happening?看起来它被调用了两次,为什么会发生这种情况? Here's my code for viewModel:这是我的 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)
            }
        }
    }

}

Code for the view: import Foundation import SwiftUI import Combine视图代码: import Foundation import SwiftUI import Combine

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)
        }
    }
}

Could anyone help me please?有人可以帮我吗? Thanks.谢谢。

DomainsOfInterestView is recreated on every update of its view model. DomainsOfInterestView在其视图模型的每次更新时重新创建。 And you initialise the view model property with a new DomainsViewModel instance every time.并且每次都使用新的DomainsViewModel实例初始化视图模型属性。 A new view model will have the domains property set to [InterestDomain]() and self.getDomains will be called again.一个新的视图模型将把domains属性设置为[InterestDomain]()并且self.getDomains将被再次调用。

First of all, better to inject the viewModel to DomainsOfInterestView in an initiailiser as @youjin wrote in a comment to the previous post.首先,最好将 viewModel 注入到DomainsOfInterestView中的 DomainsOfInterestView,正如@youjin 在上一篇文章的评论中所写的那样。 Also, you can use @StateObject instead of @ObservedObject if your minimum deployment target allows that.此外,如果您的最低部署目标允许,您可以使用@StateObject而不是@ObservedObject In this case, the viewModel property won't be reset every time the view is updated.在这种情况下,每次更新视图时都不会重置viewModel属性。

Quoting the answer from here这里引用答案

If your data can be changed, you need a dynamic ForEach loop (with an explicit id parameter):如果您的数据可以更改,您需要一个动态 ForEach 循环(带有显式 id 参数):

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

In some cases Xcode warns you when you try to modify your array used in a ForEach loop:在某些情况下,当您尝试修改 ForEach 循环中使用的数组时,Xcode 会警告您:

ForEach( :content:) should only be used for constant data. ForEach( :content:) 应该只用于常量数据。 Instead conform data to Identifiable or use ForEach( :id:content:) and provide an explicit id!而是使数据符合 Identifiable 或使用 ForEach( :id:content:) 并提供明确的 id!

Tested code测试代码


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)
        }
    }

}

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM