【问题标题】:Updating EnvironmentObject from within the View Model从视图模型中更新 EnvironmentObject
【发布时间】:2022-08-03 05:54:43
【问题描述】:

在 SwiftUI 中,我想将环境对象传递给视图模型,以便我可以更改/更新它。 EnvironmentObject 是一个简单的 AppState,它由一个属性计数器组成。

class AppState: ObservableObject {
    @Published var counter: Int = 0 
}

视图模型 \"CounterViewModel\" 更新环境对象,如下所示:

class CounterViewModel: ObservableObject {
    
    var appState: AppState
    
    init(appState: AppState) {
        self.appState = appState
    }
    
    var counter: Int {
        appState.counter 
    }
    
    func increment() {
        appState.counter += 1
    }
    
}

ContentView 显示值:

struct ContentView: View {
    
    @ObservedObject var counterVM: CounterViewModel
    
    init(counterVM: CounterViewModel) {
        self.counterVM = counterVM
    }
    
    var body: some View {
        VStack {
            Text(\"\\(counterVM.counter)\")
            Button(\"Increment\") {
                counterVM.increment()
            }
        }
        
    }
}

我也在注入状态,如下所示:

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        NavigationStack {
            
            let appState = AppState()
            
            ContentView(counterVM: CounterViewModel(appState: appState))
                .environmentObject(appState)
        }
    }
}

问题是当我单击增量按钮时,counterVM.counter 永远不会返回更新的值。我错过了什么?

  • 所有可观察对象都必须使用其中一个对象包装器进行包装,您不能将一个放在另一个内部并让它们触发更改。水槽是一种选择,但并不理想。
  • 您有建议的解决方案吗?
  • 之前的评论中提到了唯一的解决方案。
  • 我无法理解评论。谢谢你的帮助!

标签: ios swiftui


【解决方案1】:

你的类CounterViewModel 是一个ObservableObject,但它没有@Published 属性——因此不会自动将任何更改发布到视图。

但是您可以使用objectWillChange.send() 手动发布更改:

    func increment() {
        objectWillChange.send()
        appState.counter += 1
    }

【讨论】:

    【解决方案2】:

    我不确定为什么 CounterViewModel 和 AppState 都需要是可观察的对象,因为您正在使用视图模型来格式化模型的内容。我会认为 AppState 是一个模型,因此我可以将它定义为一个结构。 CounterViewModel 将成为 ObservableObject 并发布 AppState。通过这种方式,您的代码是干净且有效的。

    AppState 的代码:

    import Foundation
    
    struct AppState {
        var counter: Int = 0
    }
    

    CounterViewModel 的代码:

    import SwiftUI
    
    class CounterViewModel: ObservableObject {
        
        @Published var appState: AppState
        
        init(appState: AppState) {
            self.appState = appState
        }
        
        var counter: Int {
            appState.counter
        }
        
        func increment() {
            appState.counter += 1
        }
    }
    

    内容视图的代码:

    import SwiftUI
    
    struct ContentView: View {
        
        @StateObject var counterVM = CounterViewModel(appState: AppState())
        
        var body: some View {
            VStack {
                Text("\(counterVM.counter)")
                Button("Increment") {
                    counterVM.increment()
                }
            }
        }
    }
    

    请注意,在您首先定义 ObservableObject 的 View 中,您使用 @StateObject 定义它。在也将使用该对象的所有视图中,您使用@O​​bservedObject。

    此代码将起作用。

    【讨论】:

    • 我认为将 AppState 作为结构体没有意义。如果这是环境中保存的模型,那么它将在许多视图模型/视图之间共享。您当然希望它作为引用类型。
    • 是的。我希望 AppState 成为引用类型。
    • @Paul,帖子中并不清楚 AppState 是否要在多个视图模型或视图之间共享。如果是这样,你是绝对正确的。你确实想要一个类而不是一个结构。不过,如果两者之间有视图模型,我不会将模型用作可观察对象。
    • 是的,你的答案是 100% 正确的。我猜“AppState”这个名字暗示这是我心目中整个应用程序的模型
    【解决方案3】:

    你检查过你的xxxxApp.swift(以前是AppDelegate)文件吗? 有时 Xcode 会自动为您完成,有时您不必手动添加它并添加您的环境对象。 * 它必须是包含您要共享对象的所有视图的视图。

    var body: some Scene {
        WindowGroup {
            VStack {
                ContentView()
                    .environmentObject(YourViewModel())
            }
        }
    }
    

    【讨论】:

      【解决方案4】:

      我们实际上不使用 SwiftUI 中的视图模型对象来获取视图数据。我们使用@State 结构,如果我们需要在子视图中改变它,我们传入一个绑定,例如

      struct Counter {
          var counter: Int = 0
          
          mutating func increment() {
             counter += 1
          }
      }
      
      struct ContentView: View {
          @State var counter = Counter()
          
          var body: some View {
              ContentView2(counter: $counter)
          }
      }
      
      struct ContentView2: View {
          @Binding var counter: Counter // if we don't need to mutate it then just use let and body will still be called when the value changes.
      
          var body: some View {
              VStack {
                  Text(counter.counter, format: .number) // the formatting must be done in body so that SwiftUI will update the label automatically if the region settings change.
                  Button("Increment") {
                      counter.increment()
                  }
              }
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-03-06
        • 1970-01-01
        • 2011-08-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多