import Combine
final class MyAuthService: ObservableObject {
@Published private(set) var isSignedIn = false
func signIn() {
isSignedIn = true
}
}
1.创建视图以拥有并传递环境对象:
import SwiftUI
struct MyEntryPointView: View {
@StateObject var auth = MyAuthService()
var body: some View {
content
.environmentObject(auth)
}
@ViewBuilder private var content: some View {
if auth.isSignedIn {
Text("Yay, you're all signed in now!")
} else {
MyAuthView()
}
}
}
1.使用将环境对象作为参数的方法定义视图模型:
extension MyAuthView {
@MainActor final class ViewModel: ObservableObject {
func signIn(with auth: MyAuthService) {
auth.signIn()
}
}
}
1.创建拥有视图模型、接收环境对象并调用适当方法的视图:
struct MyAuthView: View {
@EnvironmentObject var auth: MyAuthService
@StateObject var viewModel = ViewModel()
var body: some View {
Button {
viewModel.signIn(with: auth)
} label: {
Text("Sign In")
}
}
}
1.预览以确保完整性:
struct MyEntryPointView_Previews: PreviewProvider {
static var previews: some View {
MyEntryPointView()
}
}
struct HomeView_Previews: PreviewProvider {
static var previews: some View {
Group {
HomeView()
.environmentObject(MockApp() as App) // <- This is needed for EnvironmentObject to treat the MockApp as an App Type
}
}
}
class ApplicationSessionData
{
// this is the shared instance / local copy / singleton
static let singleInstance = ApplicationSessionData()
// save shared mambers/vars here
var loggedIn: Bool = false
var access: someAccessClass = someAccessClass()
var token: String = "NO TOKET OBTAINED YET"
...
}
使用类/结构/视图应如下所示:
struct SomeModel {
// obtain the shared instance
var appSessData = ApplicationSessionData.singleInstance
// use shared mambers/vars here
if(appSessData.loggedIn && appSessData.access.hasAccessToThisView) {
appSessData.token = "ABC123RTY..."
...
}
}
// ContentView.swift
import SwiftUI
struct ContentView: View {
@EnvironmentObject var store: Store
var body: some View {
Child(viewModel: ChildViewModel(store))
}
}
// Child.swift
import SwiftUI
struct Child: View {
// only added here to verify that the actual
// @EnvironmentObject store was updated
// not needed to run
@EnvironmentObject var store: Store
@StateObject var viewModel: ViewModel
var body: some View {
Text("Hello, World!").onAppear {
viewModel.update()
print(store.canUpdateStore)
// prints true
}
}
}
extension Child {
final class ViewModel: ObservableObject {
let store: StoreProtocol
init(store: StoreProtocol) {
self.store = store
}
public func update() {
store.updateStore()
}
}
}
// myApp.swift
import SwiftUI
protocol StoreProtocol {
var canUpdateStore: Bool { get }
func updateStore() -> Void
}
class Store: ObservableObject, StoreProtocol {
@Published private(set) var canUpdateStore: Bool = false
func updateStore() {
canUpdateStore = true
}
}
@main
struct myApp: App {
@StateObject private var store = Store()
var body: some Scene {
WindowGroup {
ContentView().environmentObject(store)
}
}
}
// ViewModel
struct ProfileViewModel {
@EnvironmentObject state: State
private func businessLogic() {}
}
// The "separate" UI part of the view model
extension ProfileViewModel: View {
var body: some View {
ProfileView(model: self)
}
}
// The "real" view
struct ProfileView: View {
@ObservedObject var model
@Environment(\.accessibilityEnabled) var accessibilityEnabled
var body: some View {
// real view
}
}
9条答案
按热度按时间wwtsj6pe1#
下面提供了适合我的方法。用许多从Xcode 11.1开始的解决方案进行了测试。
问题源于EnvironmentObject注入视图的方式,通用模式
即在第一创建视图处、在第二创建的环境对象处、在注入到视图中的第三环境对象处
因此,如果我需要在视图构造器中创建/设置视图模型,环境对象还不存在。
解决方案:将所有内容分开并使用显式依赖注入
下面是它在代码中的外观(通用架构)
这里没有任何折衷,因为ViewModel和EnvironmentObject在设计上都是引用类型(实际上是
ObservableObject
),所以我只在这里和那里传递引用(又称指针)。7cwmlq892#
你可以这样做:
对于视图模型:
up9lanfz3#
你不应该这样做。SwiftUI与MVVM配合使用效果最好是一个常见的误解。MVVM在SwiftUI中没有位置。你是在问你是否可以将一个矩形推到一个三角形中。它不适合。
让我们从一些事实开始,一步一步来:
1.从不变性的意义上讲,值类型模型(没有状态的模型)被认为比引用类型模型(有状态的模型)更安全。
现在,MVVM要求您以这样的方式设置模型,即无论它何时改变,它都以某种预定义的方式更新视图,这称为绑定。
没有绑定,就不会有很好的关注点分离,例如:重构出模型和相关状态,并将它们与视图分开。
这是大多数iOS MVVM开发人员都会失败的两件事:
1.带状态的模型太危险了,因为MVVM太强调视图模型,而对状态管理和管理控制的一般规则太少;大多数开发者最终认为一个带有状态的模型是可以重用和测试的,这就是Swift首先引入值类型的原因;一个没有国家模式。
现在回答你的问题:您询问ViewModel是否可以访问EnvironmentObject(EO)?
你不应该这样做,因为在SwiftUI中,一个符合View的模型会自动引用EO。
我希望人们能欣赏SDK的紧凑设计。
在SwiftUI中,MVVM是 * 自动 * 的。不需要一个单独的ViewModel对象手动绑定到视图,而视图需要一个EO引用传递给它。
以上代码 * 为 * MVVM,例如:但是因为模型是值类型,所以不是重构出模型和状态作为视图模型,而是重构出控制(例如在协议扩展中)。
这是官方的SDK,它使设计模式适应语言特性,而不仅仅是强制它。实质重于形式。看看你的解决方案,你必须使用基本上是全局的单例。你应该知道在没有不变性保护的情况下访问全局是多么危险,你没有不变性保护,因为你必须使用引用类型模型!
你不用在SwiftUI中用java的方式来做MVVM,而且Swift-y的方式也不需要做,它已经内置了。
希望更多的开发人员看到这一点,因为这似乎是一个流行的问题。
wz3gfoph4#
解决方案:iOS 14/15以上版本
以下是您可以如何从视图模型与环境对象交互,而不必在示例化时注入它:
1.定义环境对象:
1.创建视图以拥有并传递环境对象:
1.使用将环境对象作为参数的方法定义视图模型:
1.创建拥有视图模型、接收环境对象并调用适当方法的视图:
1.预览以确保完整性:
gk7wooem5#
我选择不使用ViewModel。(也许是时候使用新模式了?)
我已经用
RootView
和一些子视图设置了我的项目。我用App
对象作为EnvironmentObject设置了我的RootView
。我的所有视图都访问应用程序上的类,而不是ViewModel访问模型。视图层次结构确定布局,而不是ViewModel确定布局。从实践中对一些应用程序这样做,我发现我的观点都很小很具体。简单地说:在我的预览中,我初始化了一个
MockApp
,它是App
的子类。MockApp使用Mocked对象初始化指定的初始化器。这里不需要模拟UserService,但需要模拟数据源(即NetworkManagerProtocol)。yyyllmsg6#
Resolver库很好地为模型类进行了依赖注入,它提供了一个属性 Package 器
@Injected
,它在本质上与@EnvironmentObject
非常相似,但在任何地方都可以工作,因此在模型中,我将注入一个ExampleService,如下所示:这也可用于解析视图的相关性:
视图的另一种选择是在SwiftUI视图层次结构中使用@EnvironmentObject,但这会变得有点麻烦,因为您将有两个依赖注入容器,Resolver/@Injected用于应用程序范围内/类似服务的所有内容,SwiftUI/@EnvironmentObject用于视图层次结构中与视图/视图模型相关的所有内容。
zu0ti5jz7#
只需创建一个Singleton并在任何需要的地方使用它(视图/类/结构/ ObservableObject...)
创建类应如下所示:
使用类/结构/视图应如下所示:
你需要意识到单身者存在的陷阱,这样你就不会陷入其中,
阅读更多:https://matteomanferdini.com/swift-singleton
yruzcnhs8#
这是我发现的访问和更新viewModel中
@EnvironmentObject
属性的最简单方法:这种方法还允许您在单元测试
ChildViewModel
或在画布预览中通过依赖注入模拟store
。与其他使用
onAppear
的方法不同,它没有可选性,可以在onAppear被触发之前运行代码,视图模型的范围仅限于它所服务的视图。您还可以在viewModel中直接修改
store
,这也可以正常工作。h79rfbju9#
也许这或多或少是关于观点的: