SwiftUI中Coordinator模式跳转后页面立即消失的问题
Coordinator模式页面跳转后立即消失问题排查与修复
问题描述
实现Coordinator模式时,点击Launch视图上的按钮,预期跳转到Dashboard视图,但页面却立即消失。相关代码如下:
AppCoordinator.swift
import SwiftUI enum Page: String, Identifiable { case launch, dashboard, newContact, contactList var id: String { self.rawValue } } enum Sheet: String, Identifiable { case `default` var id: String { self.rawValue } } enum FullScreenCover: String, Identifiable { case `default` var id: String { self.rawValue } } protocol Coordinator: ObservableObject { func push(_ page: Page) func present(_ sheet: Sheet) func present(_ fullScreenCover: FullScreenCover) func pop() func popToRoot() func dismissSheet() func dismissFullScreenCover() } class AppCoordinator: Coordinator { // MARK: - Properties @Published var path = NavigationPath() @Published var sheet: Sheet? @Published var fullScreenCover: FullScreenCover? // MARK: - Builders // Launch let launchBuilder = Launch.Builder() let signInBuilder = SignIn.Builder() let signUpBuilder = SignUp.Builder() // Main let dashboardBuilder = Dashboard.Builder() // let newContactBuilder = NewContact.Builder() // let contactListBuilder = ContactList.Builder() // MARK: - Conformance: Coordinator func push(_ page: Page) { path.append(page) } func present(_ sheet: Sheet) { self.sheet = sheet } func present(_ fullScreenCover: FullScreenCover) { self.fullScreenCover = fullScreenCover } func pop() { path.removeLast() } func popToRoot() { path.removeLast(path.count) } func dismissSheet() { self.sheet = nil } func dismissFullScreenCover() { self.fullScreenCover = nil } // MARK: - Helpers @ViewBuilder func build(page: Page) -> some View { switch page { case .launch: self.launchBuilder.build() case .dashboard: self.dashboardBuilder.build(with: self) default: self.launchBuilder.build() } } @ViewBuilder func build(sheet: Sheet) -> some View { switch sheet { default: self.launchBuilder.build() } } @ViewBuilder func build(fullScreenCover: FullScreenCover) -> some View { switch fullScreenCover { default: self.launchBuilder.build() } } }
AppCoordinatorView.swift
import SwiftUI struct AppCoordinatorView: View { @StateObject var coordinator: AppCoordinator = AppCoordinator() var body: some View { NavigationStack(path: $coordinator.path) { coordinator.build(page: .launch) .navigationDestination(for: Page.self) { page in coordinator.build(page: page) } .sheet(item: $coordinator.sheet) { sheet in coordinator.build(sheet: sheet) } .fullScreenCover(item: $coordinator.fullScreenCover) { fullScreenCover in coordinator.build(fullScreenCover: fullScreenCover) } } .environmentObject(coordinator) } }
Launch.Builder.swift
extension Launch { struct Builder: LaunchBuilder { // MARK: - Properties // MARK: - Conformance: Builder func build() -> some CRM_View { let useCases = Launch.UseCases() let viewModel = Launch.ViewModel( useCases: useCases ) let view = Launch.View(viewModel: viewModel) return view } } }
Launch.View.swift
import SwiftUI protocol LaunchView: CRM_View { } extension Launch { struct View: LaunchView { // MARK: - Properties let viewModel: Launch.ViewModel @EnvironmentObject var coordinator: AppCoordinator // MARK: - Views var body: some SwiftUI.View { NavigationStack { VStack { Text("Launch Placeholder") Button { viewModel.handle(event: .didAppear) coordinator.push(.dashboard) } label: { Text("Hi") } } } } } }
Launch.ViewModel.swift
import SwiftUI protocol LaunchViewModel: CRM_ViewModel { typealias ModuleEvents = Launch.Event } extension Launch { class ViewModel: LaunchViewModel { // MARK: - Properties let useCases: Launch.UseCases // MARK: - Lifecycle init( useCases: Launch.UseCases ) { self.useCases = useCases } // MARK: - Conformance: LaunchViewModel func handle(event: Launch.Event) { switch event { case .didAppear: break case .willLoadData: break case .didLoadData: break } } } }
Dashboard模块已按相同方式实现,仅修改名称,无额外功能。
问题原因
核心问题是Launch.View内部嵌套了多余的NavigationStack。AppCoordinatorView已经在最外层提供了一个NavigationStack用于管理全局导航路径,而Launch.View内部又添加了一层NavigationStack,这会导致导航层级冲突:当调用coordinator.push(.dashboard)时,外层NavigationStack尝试执行跳转,但内层的NavigationStack会干扰这个流程,最终导致新页面被弹出后立即消失。
修复方案
移除Launch.View中的NavigationStack包裹,直接返回VStack内容,让全局的NavigationStack统一管理导航状态。
修改后的Launch.View代码:
import SwiftUI protocol LaunchView: CRM_View { } extension Launch { struct View: LaunchView { // MARK: - Properties let viewModel: Launch.ViewModel @EnvironmentObject var coordinator: AppCoordinator // MARK: - Views var body: some SwiftUI.View { VStack { Text("Launch Placeholder") Button { viewModel.handle(event: .didAppear) coordinator.push(.dashboard) } label: { Text("Hi") } } } } }
同时需确保Dashboard.View也没有嵌套额外的NavigationStack,保持导航层级由AppCoordinatorView的外层NavigationStack统一控制。
内容的提问来源于stack exchange,提问作者Tiny Tim
相关产品推荐
相关产品推荐

