SwiftUI环境对象访问失败:Sheet控制器呈现视图崩溃修复咨询
问题修复:Sheet中SwiftUI视图无法访问环境对象
报错信息
No ObservableObject of type found. A View.environmentObject(_:) for "ClassName" may be missing
问题描述
ToggleView无法访问ManagerClass环境对象,崩溃原因为:通过Sheet Presentation Controller呈现的视图控制器中展示SwiftUI视图时,尝试访问环境对象导致崩溃。已提供可复现崩溃的完整测试代码,且已在顶层添加未在代码中展示的.environmentObject配置。
咨询问题
- 该如何修复此问题?
- 实际应用中有多个需要向下传递的类时,是否必须进行多级传递?
核心原因
通过UIHostingConfiguration嵌入的SwiftUI视图(如CellView)及其子视图(如ToggleView),无法自动继承顶层SwiftUI视图树的环境对象——因为它们被包裹在UIKit的视图控制器层级中,环境对象不能跨UIKit/SwiftUI的边界自动传递。
修复方案
方案1:手动为UIHostingConfiguration注入环境对象
在SheetViewController创建CellView时,手动注入环境对象,让后续的SwiftUI视图树能获取到实例:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) cell.contentConfiguration = UIHostingConfiguration { CellView() .environmentObject(ManagerClass.shared) // 手动注入环境对象 } return cell }
方案2:直接使用单例绕过环境对象
由于ManagerClass已经实现了单例,可直接在ToggleView中引用单例,无需依赖环境对象传递:
struct ToggleView: View { @ObservedObject var managerClass = ManagerClass.shared // 直接使用单例 var body: some View { Toggle("Enable", isOn: $managerClass.isEnabled) } }
这种方式适合简单全局状态场景,若需支持依赖注入测试,方案1更灵活。
多对象传递的优化方式
不需要进行多级手动传递,可通过以下方式简化:
- 批量注入环境对象:在顶层SwiftUI视图一次性注入所有需要的环境对象,纯SwiftUI视图树可自动访问;跨UIKit边界时,仅需在UIHostingConfiguration中一次性注入所有所需对象即可。
- 使用容器类封装:将多个ObservableObject封装到一个容器类中,仅传递该容器作为环境对象,减少重复注入代码:
class AppEnvironment: ObservableObject { let manager = ManagerClass.shared let otherManager = OtherManager.shared } // 顶层注入 ContentView() .environmentObject(AppEnvironment()) // 子视图访问 @EnvironmentObject var appEnv: AppEnvironment // 使用示例:appEnv.manager.isEnabled
完整修复后的测试代码(方案1示例)
class ManagerClass: ObservableObject { static let shared = ManagerClass() @Published var isEnabled = false } struct ContentView: View { var body: some View { UIList() } } struct UIList: UIViewControllerRepresentable { let tableViewController = UITableViewController() func makeUIViewController(context: Context) -> UITableViewController { tableViewController.tableView.delegate = context.coordinator tableViewController.tableView.dataSource = context.coordinator tableViewController.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell") return tableViewController } func updateUIViewController(_ uiViewController: UITableViewController, context: Context) {} func makeCoordinator() -> Coordinator { Coordinator(self) } class Coordinator: NSObject, UITableViewDelegate, UITableViewDataSource { let parent: UIList init(_ parent: UIList) { self.parent = parent } let items = ["Item 1", "Item 2"] func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { items.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) cell.textLabel?.text = "Tap me" return cell } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { presentSheet() } func presentSheet() { let sheetVC = SheetViewController() if let sheet = sheetVC.sheetPresentationController { sheet.largestUndimmedDetentIdentifier = .medium sheet.prefersScrollingExpandsWhenScrolledToEdge = false sheet.prefersEdgeAttachedInCompactHeight = true sheet.widthFollowsPreferredContentSizeWhenEdgeAttached = true parent.tableViewController.present(sheetVC, animated: true) } } } } class SheetViewController: UITableViewController { override func viewDidLoad() { super.viewDidLoad() tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell") tableView.delegate = self tableView.dataSource = self } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 1 } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) cell.contentConfiguration = UIHostingConfiguration { CellView() .environmentObject(ManagerClass.shared) // 手动注入环境对象 } return cell } } struct CellView: View { @State private var showSheet = false var body: some View { Button("Show") { showSheet = true } .sheet(isPresented: $showSheet) { ToggleView() } } } struct ToggleView: View { @EnvironmentObject var managerClass: ManagerClass var body: some View { Toggle("Enable", isOn: $managerClass.isEnabled) } }
内容的提问来源于stack exchange,提问作者User95797654974
相关产品推荐
相关产品推荐

