SwiftUI-如何将EnvironmentObject传递到视图模型中?


16

我正在寻找创建一个可以由视图模型(而不仅仅是视图)访问的EnvironmentObject。

Environment对象跟踪应用程序会话数据,例如,loginIn,访问令牌等,该数据将传递到视图模型(或需要的服务类)中,以允许调用API来传递来自此EnvironmentObjects的数据。

我试图将会话对象从视图传递给视图模型类的初始化程序,但出现错误。

如何使用SwiftUI将EnvironmentObject访问/传递到视图模型?

请参阅测试项目的链接:https : //gofile.io/?c=vgHLVx


为什么不通过viewmodel作为EO?
E.Coms

似乎在顶部,将会有很多视图模型,我链接的上载只是一个简化的示例
Michael

2
我不确定为什么这个问题被否决,我想知道也是一样。我会回答我所做的事情,希望其他人可以提出更好的建议。
Michael Ozeryansky '19

2
@ E.Coms我期望EnvironmentObject通常是一个对象。我知道有很多工作,这似乎使他们可以在全球范围内访问,这似乎是一种代码味道。
Michael Ozeryansky '19

@Michael您是否找到了解决方案?
布雷特

Answers:


3

我选择没有ViewModel。(也许是时候采用一种新模式了?)

我设置了带有RootView和一些子视图的项目。我RootView将一个App对象设置为EnvironmentObject。我所有的视图都访问App上的类,而不是ViewModel访问模型。由视图层次结构确定布局,而不是由ViewModel确定布局。通过在实践中对一些应用程序执行此操作,我发现我的观点仍然很小而具体。过于简化:

class App {
   @Published var user = User()

   let networkManager: NetworkManagerProtocol
   lazy var userService = UserService(networkManager: networkManager)

   init(networkManager: NetworkManagerProtocol) {
      self.networkManager = networkManager
   }

   convenience init() {
      self.init(networkManager: NetworkManager())
   }
}
struct RootView {
    @EnvironmentObject var app: App

    var body: some View {
        if !app.user.isLoggedIn {
            LoginView()
        } else {
            HomeView()
        }
    }
}
struct HomeView: View {
    @EnvironmentObject var app: App

    var body: some View {
       VStack {
          Text("User name: \(app.user.name)")
          Button(action: { app.userService.logout() }) {
             Text("Logout")
          }
       }
    }
}

在预览中,我初始化了MockApp,它是的子类App。MockApp使用Mocked对象初始化指定的初始化程序。这里不需要模拟UserService,但是数据源(即NetworkManagerProtocol)需要模拟。

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

}

请注意:我认为最好避免像这样的链接app.userService.logout()userService应该是私有的,并且只能从app类内部访问。上面的代码应如下所示:Button(action: { app.logout() })然后注销函数将直接调用userService.logout()
pawello2222

@ pawello2222并没有更好,只是外观没有任何好处,但是您可以随心所欲。
迈克尔·奥泽良斯基

3

你不应该 常见的误解是,SwiftUI与MVVM配合使用效果最佳。

MVVM在SwfitUI中没有位置。您在问是否可以将矩形推到

适合三角形。不适合

让我们从一些事实开始,并逐步进行工作:

  1. ViewModel是MVVM中的模型。

  2. MVVM不考虑值类型(例如,java中没有这样的东西)。

  3. 值类型模型(无状态模型)被认为比参考安全

    不变性意义上的类型模型(具有状态的模型)。

现在,MVVM要求您以这样一种方式建立模型:

以某种预定的方式更新视图。这称为绑定。

没有约束力,您将无法很好地分离关注点,例如;重构

模型和关联状态,并使它们与视图分离。

这是大多数iOS MVVM开发人员失败的两件事:

  1. iOS没有传统Java意义上的“绑定”机制。

    有些人只是忽略绑定,而认为调用对象ViewModel

    自动解决所有问题;有些人会引入基于KVO的Rx,并且

    当MVVM使事情变得更简单时,一切都会变得复杂。

  2. 有状态的模型太危险了

    因为MVVM过分强调ViewModel,而过分强调状态管理

    管理控制的一般学科;大多数开发商最终

    认为具有用于更新视图的状态的模型是可重用的,并且

    可测试的

    这就是为什么Swift首先引入值类型的原因。没有模型

    州。

现在,您的问题是:您问您的ViewModel是否可以访问EnvironmentObject(EO)?

你不应该 因为在SwiftUI中,符合View的模型会自动具有

参考EO。例如;

struct Model: View {
    @EnvironmentObject state: State
    // automatic binding in body
    var body: some View {...}
}

希望人们能够欣赏紧凑型SDK的设计方式。

在SwiftUI中,MVVM是自动的。无需单独的ViewModel对象

手动绑定到需要传递EO参考的视图。

上面的代码 MVVM。例如; 具有绑定到视图的模型。

但是因为模型是值类型,所以与其将模型和状态重构为

视图模型中,您可以重构控制(例如,在协议扩展中)。

这是官方的SDK,可根据语言功能调整设计模式,而不仅仅是

强制执行。实质重于形式。

查看您的解决方案,您必须使用基本上是全局的单例。您

应该知道在没有保护的情况下在任何地方访问全局有多危险

不变性,这是没有的,因为您必须使用引用类型模型!

TL; DR

您不会在SwiftUI中以Java方式执行MVVM。不需要Swift-y方法

为此,它已经内置。

希望更多的开发人员能够看到这一点,因为这似乎是一个受欢迎的问题。


1

下面提供了适合我的方法。经过Xcode 11.1开始的许多解决方案测试。

该问题源于在视图,一般模式中注入EnvironmentObject的方式

SomeView().environmentObject(SomeEO())

即,在第一个创建的视图,第二个创建的环境对象,在第三个环境对象中注入视图

因此,如果需要在视图构造函数中创建/设置视图模型,则环境对象尚不存在。

解决方案:分解所有内容并使用显式依赖项注入

这是它在代码(通用模式)中的外观

// somewhere, say, in SceneDelegate

let someEO = SomeEO()                            // create environment object
let someVM = SomeVM(eo: someEO)                  // create view model
let someView = SomeView(vm: someVM)              // create view 
                   .environmentObject(someEO)

这里没有任何取舍,因为ViewModel和EnvironmentObject在设计上是引用类型(实际上是ObservableObject),所以我在此处传递引用,而这里仅引用(也就是指针)。

class SomeEO: ObservableObject {
}

class BaseVM: ObservableObject {
    let eo: SomeEO
    init(eo: SomeEO) {
       self.eo = eo
    }
}

class SomeVM: BaseVM {
}

class ChildVM: BaseVM {
}

struct SomeView: View {
    @EnvironmentObject var eo: SomeEO
    @ObservedObject var vm: SomeVM

    init(vm: SomeVM) {
       self.vm = vm
    }

    var body: some View {
        // environment object will be injected automatically if declared inside ChildView
        ChildView(vm: ChildVM(eo: self.eo)) 
    }
}

struct ChildView: View {
    @EnvironmentObject var eo: SomeEO
    @ObservedObject var vm: ChildVM

    init(vm: ChildVM) {
       self.vm = vm
    }

    var body: some View {
        Text("Just demo stub")
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.