Mic*_*ael 70 mvvm ios swift swiftui
我正在寻找创建一个可以由视图模型(不仅仅是视图)访问的 EnvironmentObject。
Environment 对象跟踪应用程序会话数据,例如登录、访问令牌等,这些数据将被传递到视图模型(或需要时的服务类)以允许调用 API 以从该 EnvironmentObjects 传递数据。
我试图将会话对象从视图传递给视图模型类的初始化程序,但出现错误。
如何使用 SwiftUI 访问/传递 EnvironmentObject 到视图模型中?
eme*_*hex 15
解决方案:iOS 14/15+
以下是您可以如何与视图模型中的环境对象进行交互,而无需在实例化时注入它:
import Combine
final class MyAuthService: ObservableObject {
@Published private(set) var isSignedIn = false
func signIn() {
isSignedIn = true
}
}
Run Code Online (Sandbox Code Playgroud)
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()
}
}
}
Run Code Online (Sandbox Code Playgroud)
extension MyAuthView {
@MainActor final class ViewModel: ObservableObject {
func signIn(with auth: MyAuthService) {
auth.signIn()
}
}
}
Run Code Online (Sandbox Code Playgroud)
struct MyAuthView: View {
@EnvironmentObject var auth: MyAuthService
@StateObject var viewModel = ViewModel()
var body: some View {
Button {
viewModel.signIn(with: auth)
} label: {
Text("Sign In")
}
}
}
Run Code Online (Sandbox Code Playgroud)
struct MyEntryPointView_Previews: PreviewProvider {
static var previews: some View {
MyEntryPointView()
}
}
Run Code Online (Sandbox Code Playgroud)
Asp*_*eri 13
下面提供了对我有用的方法。测试了许多从 Xcode 11.1 开始的解决方案。
问题源于在view中注入EnvironmentObject的方式,一般schema
SomeView().environmentObject(SomeEO())
Run Code Online (Sandbox Code Playgroud)
即,在第一个创建的视图中,在第二个创建的环境对象中,在注入到视图中的第三个环境对象中
因此,如果我需要在视图构造函数中创建/设置视图模型,则环境对象尚不存在。
解决方案:分解一切并使用显式依赖注入
这是它在代码中的样子(通用架构)
SomeView().environmentObject(SomeEO())
Run Code Online (Sandbox Code Playgroud)
这里没有任何权衡,因为 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")
}
}
Run Code Online (Sandbox Code Playgroud)
Jim*_*lai 10
你不应该。SwiftUI 最适合 MVVM 是一个常见的误解。MVVM 在 SwiftUI 中没有位置。你问的是你是否可以推一个矩形来适应一个三角形。它不适合。
让我们从一些事实开始,一步一步地工作:
ViewModel 是 MVVM 中的一个模型。
MVVM 不考虑值类型(例如,Java 中没有这样的东西)。
在不变性的意义上,值类型模型(没有状态的模型)被认为比引用类型模型(有状态的模型)更安全。
现在,MVVM 要求您以这样的方式设置模型,以便每当模型发生变化时,它都会以某种预先确定的方式更新视图。这称为绑定。
如果没有绑定,您将无法很好地分离关注点,例如;重构模型和关联状态,并将它们与视图分开。
这是大多数 iOS MVVM 开发人员失败的两件事:
iOS 没有传统 Java 意义上的“绑定”机制。有些人会忽略绑定,并认为调用对象 ViewModel 会自动解决所有问题;有些人会引入基于 KVO 的 Rx,当 MVVM 应该让事情变得更简单时,一切都会变得复杂。
带状态的模型太危险了,因为 MVVM 过分强调 ViewModel,过少强调状态管理和管理控制的一般规则;大多数开发人员最终认为具有用于更新视图的状态的模型是可重用和可测试的。这就是 Swift 首先引入值类型的原因;没有状态的模型。
现在问您的问题:您问您的 ViewModel 是否可以访问 EnvironmentObject (EO)?
你不应该。因为在 SwiftUI 中,符合 View 的模型会自动引用 EO。例如;
struct Model: View {
@EnvironmentObject state: State
// automatic binding in body
var body: some View {...}
}
Run Code Online (Sandbox Code Playgroud)
我希望人们能体会到 SDK 的设计是多么紧凑。
在 SwiftUI 中,MVVM 是自动的。不需要单独的 ViewModel 对象手动绑定到需要传递给它的 EO 引用的视图。
上面的代码是MVVM。例如; 具有绑定到视图的模型。但是因为模型是值类型,所以不是将模型和状态重构为视图模型,而是重构出控制(例如在协议扩展中)。
这是官方 SDK 使设计模式适应语言特性,而不仅仅是强制执行它。实质重于形式。看看你的解决方案,你必须使用基本上是全局的单例。您应该知道在没有不变性保护的情况下在任何地方访问 global 是多么危险,这是您没有的,因为您必须使用引用类型模型!
TL; 博士
您不会在 SwiftUI 中以 Java 方式执行 MVVM。而 Swift-y 的方式是不需要这样做的,它已经内置了。
希望更多的开发人员看到这一点,因为这似乎是一个受欢迎的问题。
你可以这样做:
struct YourView: View {
@EnvironmentObject var settings: UserSettings
@ObservedObject var viewModel = YourViewModel()
var body: some View {
VStack {
Text("Hello")
}
.onAppear {
self.viewModel.setup(self.settings)
}
}
}
Run Code Online (Sandbox Code Playgroud)
对于视图模型:
class YourViewModel: ObservableObject {
var settings: UserSettings?
func setup(_ settings: UserSettings) {
self.settings = settings
}
}
Run Code Online (Sandbox Code Playgroud)
我选择不使用 ViewModel。(也许是时候推出新模式了?)
我已经使用 aRootView和一些子视图设置了我的项目。我将RootView一个App对象设置为环境对象。我的所有视图都访问 App 上的类,而不是 ViewModel 访问模型。视图层次结构决定布局,而不是 ViewModel 决定布局。通过在一些应用程序的实践中这样做,我发现我的观点仍然是小而具体的。作为一个过度简化:
class App: ObservableObject {
@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())
}
}
Run Code Online (Sandbox Code Playgroud)
struct RootView: View {
@EnvironmentObject var app: App
var body: some View {
if !app.user.isLoggedIn {
LoginView()
} else {
HomeView()
}
}
}
Run Code Online (Sandbox Code Playgroud)
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")
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
在我的预览中,我初始化了 a 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
}
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
10560 次 |
| 最近记录: |