You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI环境对象访问失败:Sheet控制器呈现视图崩溃修复咨询

问题修复:Sheet中SwiftUI视图无法访问环境对象

报错信息

No ObservableObject of type found. A View.environmentObject(_:) for "ClassName" may be missing

问题描述

ToggleView无法访问ManagerClass环境对象,崩溃原因为:通过Sheet Presentation Controller呈现的视图控制器中展示SwiftUI视图时,尝试访问环境对象导致崩溃。已提供可复现崩溃的完整测试代码,且已在顶层添加未在代码中展示的.environmentObject配置。

咨询问题

  1. 该如何修复此问题?
  2. 实际应用中有多个需要向下传递的类时,是否必须进行多级传递?

核心原因

通过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更灵活。


多对象传递的优化方式

不需要进行多级手动传递,可通过以下方式简化:

  1. 批量注入环境对象:在顶层SwiftUI视图一次性注入所有需要的环境对象,纯SwiftUI视图树可自动访问;跨UIKit边界时,仅需在UIHostingConfiguration中一次性注入所有所需对象即可。
  2. 使用容器类封装:将多个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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 22:05:01