SwiftUI实现全局可编辑数组,解决@EnvironmentObject传递报错
问题原因
你碰到的EnvironmentObject should be passed from ancestor view报错,以及View4数组为空的问题,核心是两个原因:
- 你只给View1的父节点注入了
BookViewModel,如果View4不是View1的子视图(比如是通过TabView、NavigationStack跳转到的同级视图,或者直接作为根视图的其他分支),就拿不到这个全局对象 - 你每次在Preview里单独创建了新的
BookViewModel实例,和App运行时的实例不是同一个,所以预览时如果没加模拟数据就是空的,运行时如果跳转路径不对也拿不到实例
正确全局注入方案
要实现全App可用的全局对象,直接在App的根节点注入即可,不要只给View1注入:
@main struct DineApp: App { // 全局持有ViewModel实例,保证整个App生命周期内只有一个 @StateObject private var bookViewModel = BookViewModel() var body: some Scene { WindowGroup { // 给整个WindowGroup下的所有子视图注入,不管层级多深、跳转路径是什么都能拿到 ContentView() // 你的根视图,比如TabView、NavigationStack的容器 .environmentObject(bookViewModel) } } }
这里用@StateObject持有ViewModel是关键,保证App运行期间实例不会被销毁,不会重复创建。
视图层使用注意事项
- 所有需要访问
BookViewModel的视图,只要声明@EnvironmentObject var bookviewModel : BookViewModel即可,不需要再手动传参,只要是根视图WindowGroup下的子视图都能直接拿到 - 调用方法的时候要通过ViewModel实例调用,你原来View1里的
saveBook方法漏了前缀,要改成:
// View1里的按钮点击事件 Button("保存书籍") { bookviewModel.saveBook(Id: selectedId, Name: selectedName) }
- View4的List语法写错了,修正后:
List(bookviewModel.bookarray, id: \.id) { row in // 建议用id作为唯一标识,避免重复 Text(row.name) }
- 预览的时候如果要看到数据,直接给预览用的ViewModel加测试数据即可:
struct View4_Previews: PreviewProvider { static let bookViewModel = BookViewModel() static var previews: some View { // 预览前加测试数据 bookViewModel.bookarray = [Book(Id: 1, Name: "测试书籍1"), Book(Id: 2, Name: "测试书籍2")] return View4().environmentObject(bookViewModel) } }
SwiftUI对应OC AppDelegate全局存储的方案
除了上面的@EnvironmentObject注入方案,还有两种常用的全局存储方案,根据你的使用场景选择:
- 单例模式:直接给ViewModel加静态共享实例,不需要注入,全App直接调用,和OC里的全局单例用法完全一致:
class BookViewModel : ObservableObject{ static let shared = BookViewModel() // 全局单例 @Published var bookarray = [Book]() // 其他方法不变 } // 任意视图里直接用:BookViewModel.shared.bookarray,不需要声明@EnvironmentObject
- AppStorage:如果是小数据(比如配置项)可以用
@AppStorage,会自动持久化,相当于全局的UserDefaults包装
注意:如果你的数据需要持久化(App杀掉再打开还保留),需要在ViewModel里加UserDefaults存储或者CoreData的持久化逻辑,当前的代码只是存在内存里,App杀掉就会清空。
内容的提问来源于stack exchange,提问作者Honey
相关产品推荐
相关产品推荐

