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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 02:00:54