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

SwiftUI NavigationStack无限循环及页面跳转异常问题咨询

问题场景

使用NavigationStack实现页面跳转时,点击按钮设置showRegistrationView = true后,ForgotPasswordView或RegistrationView会被无限调用;手动在目标视图的onAppear中重置状态后,虽能阻止无限加载,但关闭目标视图时,LoginView会短暂显示后跳转到空白页面,点击空白页的返回按钮才能回到初始视图。

相关代码

根视图代码:

@State private var showLoginView = false
var body: some View {
    NavigationStack {
        content()
            .navigationDestination(isPresented: $showLoginView) {
                LoginView(viewModel: LoginViewModel(pageId: "login_view"))
            }.navigationDestination(isPresented: $showRegistrationView) {
                RegistrationView(viewModel: RegistrationViewModel(pageId: "registration_view"))
            }.navigationDestination(isPresented: $continueAsGuest, destination: {
                TabbedView(viewModel: TabbedViewModel())
            })
    }
}

LoginView代码:

var body: some View {
    content()
        .navigationDestination(isPresented: $showForgotPasswordView, destination: {
            ForgotPasswordView(viewModel: ForgotPasswordViewModel(pageId: "forgot_password_view"))
        })
        .navigationDestination(isPresented: $showRegistrationView, destination: {
            RegistrationView(viewModel: RegistrationViewModel(pageId: "registration_view"))
        })
}

临时修复代码:

ForgotPasswordView(viewModel: ForgotPasswordViewModel(pageId: "forgot_password_view")).onAppear {
    self.showForgotPasswordView = false
}

问题原因

  1. 重复注册跳转目标:根视图和LoginView中都注册了RegistrationView(以及ForgotPasswordView)的navigationDestination。当showRegistrationView变为true时,两个注册的目标都会被触发,导致NavigationStack不断往栈里推入新视图,形成无限循环。
  2. 手动重置状态的副作用:在目标视图的onAppear中立刻把绑定的布尔值设为false,会破坏NavigationStack的状态一致性——视图已经被推入栈,但触发跳转的状态被强行重置,关闭目标视图时,NavigationStack找不到正确的上一级页面,就会显示空白视图。

解决方案

1. 统一管理跳转目标

所有页面的navigationDestination都只在根NavigationStack中注册,子视图仅负责修改绑定的状态变量,不要重复注册目标,避免重复触发跳转。

2. 不要手动重置跳转状态

NavigationStack会自动在视图被弹出时,将绑定的布尔值设为false,无需手动在onAppear中修改。

修改后代码示例

根视图代码

@State private var showLoginView = false
@State private var showRegistrationView = false
@State private var showForgotPasswordView = false
@State private var continueAsGuest = false

var body: some View {
    NavigationStack {
        content()
            // 所有跳转目标统一在根层级注册
            .navigationDestination(isPresented: $showLoginView) {
                LoginView(
                    viewModel: LoginViewModel(pageId: "login_view"),
                    showForgotPasswordView: $showForgotPasswordView,
                    showRegistrationView: $showRegistrationView
                )
            }
            .navigationDestination(isPresented: $showRegistrationView) {
                RegistrationView(viewModel: RegistrationViewModel(pageId: "registration_view"))
            }
            .navigationDestination(isPresented: $showForgotPasswordView) {
                ForgotPasswordView(viewModel: ForgotPasswordViewModel(pageId: "forgot_password_view"))
            }
            .navigationDestination(isPresented: $continueAsGuest) {
                TabbedView(viewModel: TabbedViewModel())
            }
    }
}

LoginView代码

// 通过Binding接收根视图的状态变量
@Binding var showForgotPasswordView: Bool
@Binding var showRegistrationView: Bool
let viewModel: LoginViewModel

var body: some View {
    content()
        // 按钮直接修改绑定值即可,无需再注册navigationDestination
        .toolbar {
            Button("忘记密码") {
                showForgotPasswordView = true
            }
            Button("注册新账号") {
                showRegistrationView = true
            }
        }
}

内容的提问来源于stack exchange,提问作者Easy Coder

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 13:05:26