SwiftUI中基于@ObservableObject更新Tab视图的问题及导航异常
问题背景
使用ObservableObject(FeatureToggleController)从API异步获取功能开关,用于在Tab View中切换视图,但开关加载完成后,选中的Tab视图未重新渲染。后续修复了视图不更新问题,但出现导航崩溃错误。
核心代码实现
1. FeatureToggleController实现
class FeatureToggleController: ObservableObject { @Published var featureToggles: [FeatureToggle] = [] private var networkController: NetworkController = Container.networkController private var errorHandler = Container.errorHandler func isFeatureToggleOn(_ featureToggle: FeatureToggle) -> Bool { return featureToggles.contains(featureToggle) } func fetchFeatureToggles() async -> Void { let result: Result<[String], OperatorAppError> = await networkController.getUrlDataResult(.featureToggles) switch result { case .success(let fts): DispatchQueue.main.async { self.featureToggles = fts.compactMap { FeatureToggle(rawValue:$0) } } case .failure(let error): errorHandler.handleError(title: "Error while fetching feature toggles", error: error) } } }
2. Tab View代码
var body: some View { TabView(selection: selectedTab) { ForEach(Tab.allCases) { tab in tab.destination .tag(tab as Tab?) .tabItem { tab.getIcon() } } } }
3. Tab枚举与视图切换逻辑
enum Tab: Hashable, Identifiable, CaseIterable { case otherTabs case tabInQuestion var id: Tab { self } } extension Tab { @ViewBuilder var destination: some View { switch self { case .otherTabs: OtherView() case .tabInQuestion: if (Container.featureToggleController.isFeatureToggleOn(.isFeature1Enabled)) { FeatureToggledView() } else { NormalView() } } } }
4. 依赖注入容器Container
class Container { // Mark: - Single instance members public static var networkController: NetworkController = NetworkControllerImplementation() public static var featureToggleController: FeatureToggleController = FeatureToggleController() // Mark: - Multiple instance members public static var errorHandler: ErrorHandler { ErrorHandlerImplementation( logController: logController, alertController: alertController) } }
视图不更新问题的诊断与修复
原以为isFeatureToggleOn方法使用@Published属性会触发视图更新,实际问题是:Container实例化的ObservableObject未被视图持有状态,因此未订阅@Published的变更。
由于枚举扩展中无法使用@StateObject,创建专门的视图处理切换逻辑,将状态锚定在视图中:
struct FeatureToggledSwitchView: View { @StateObject private var featureToggleController = Container.featureToggleController var body: some View { if (featureToggleController.featureToggles.contains(.isFeature1Enabled)) { FeatureToggledView() } else { NormalView() } } }
在Tab的destination扩展中替换为该视图后,视图可随功能开关更新正常重新渲染。
剩余导航崩溃问题
修复视图更新后出现导航相关崩溃,错误信息翻译如下:
*** 应用因未捕获的异常'NSInternalInconsistencyException'终止,原因:'请求对可见导航栏<SwiftUI.UIKitNavigationBar: 0x105b558e0; baseClass = UINavigationBar; frame = (0 53.6667; 393 96); opaque = NO; autoresize = W; layer = <CALayer: 0x6000002db5a0>> delegate=0x10795be00进行布局,但顶部条目属于另一个导航栏。topItem = <UINavigationItem: 0x105b64c90> title='Feature Toggled View' style=navigator leftItemsSupplementBackButton trailingItemGroups=0x600000cbd200 largeTitleDisplayMode=always, navigation bar = <SwiftUI.UIKitNavigationBar: 0x105b6cb50; baseClass = UINavigationBar; frame = (0 0; 393 96); opaque = NO; autoresize = W; layer = <CALayer: 0x6000002bab40>> delegate=0x10782b800,可能是客户端尝试嵌套包装的导航控制器导致。'
推测该问题与NavigationStack的实现相关。
内容的提问来源于stack exchange,提问作者esbenr

