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

使用@AppStorage路由时导航异常:FirstView重复显示问题

问题

使用@AppStorage控制用户是否进入引导页时,出现异常行为:点击「Finish the onboarding」按钮后,本该直接跳转至MainView,但FirstView会短暂重复显示一次。可复现的极简SwiftUI代码如下:

import SwiftUI

@main
struct OnboardingApp: App {
    @AppStorage("onboardingCompleted") var onboardingCompleted = false
    
    var body: some Scene {
        WindowGroup {
            if !onboardingCompleted {
                FirstView()
            } else {
                MainView()
            }
        }
    }
}

struct FirstView: View {
    var body: some View {
        NavigationStack {
            VStack {
                Text("Welcome")
                
                NavigationLink("Second") {
                    SecondView()
                }
            }
            .onAppear {
                print("First View")
            }
        }
    }
}

struct SecondView: View {
    @AppStorage("onboardingCompleted") var onboardingCompleted = false
    
    var body: some View {
        VStack {
            Text("Onboarding complete")
            Button("Finish the onboarding") {
                onboardingCompleted = true
            }
        }
        .onAppear {
            print("Second View")
        }
    }
}

struct MainView: View {
    @AppStorage("onboardingCompleted") var onboardingCompleted = true
    
    var body: some View {
        NavigationStack {
            VStack {
                Text("Hello, Main View!")
                Button("Uncomplete onboarding") {
                    onboardingCompleted = false
                }
            }
            .onAppear {
                print("Main View")
            }
        }
    }
}

原因分析

问题核心在于导航栈层级设计和@AppStorage变更后的视图刷新逻辑:

  1. 当SecondView中把onboardingCompleted设为true时,App的body会立即重新计算,但原FirstView所在的NavigationStack尚未完全销毁,作为导航栈根视图的FirstView会短暂触发onAppear,造成视觉上的闪现。
  2. MainView中@AppStorage的默认值设为true是错误的,这会导致首次启动App时,若UserDefaults无对应键值,直接跳过引导页显示主视图,违背初始逻辑。

解决方案

方案1:统一导航栈层级(推荐)

将NavigationStack提升到App层级统一管理,避免每个视图单独嵌套导航栈,确保视图切换时旧导航栈被正确销毁:

import SwiftUI

@main
struct OnboardingApp: App {
    @AppStorage("onboardingCompleted") var onboardingCompleted = false
    
    var body: some Scene {
        WindowGroup {
            // 导航栈移至App层级,统一控制视图切换
            NavigationStack {
                if !onboardingCompleted {
                    FirstView()
                } else {
                    MainView()
                }
            }
        }
    }
}

struct FirstView: View {
    var body: some View {
        VStack {
            Text("Welcome")
            NavigationLink("Second") {
                SecondView()
            }
        }
        .onAppear {
            print("First View")
        }
    }
}

struct SecondView: View {
    @AppStorage("onboardingCompleted") var onboardingCompleted = false
    
    var body: some View {
        VStack {
            Text("Onboarding complete")
            Button("Finish the onboarding") {
                onboardingCompleted = true
            }
        }
        .onAppear {
            print("Second View")
        }
    }
}

struct MainView: View {
    // 修正@AppStorage默认值为false,与全局逻辑保持一致
    @AppStorage("onboardingCompleted") var onboardingCompleted = false
    
    var body: some View {
        VStack {
            Text("Hello, Main View!")
            Button("Uncomplete onboarding") {
                onboardingCompleted = false
            }
        }
        .onAppear {
            print("Main View")
        }
    }
}

方案2:手动清空导航栈(备选)

若不想调整导航栈层级,可在SecondView设置onboardingCompleted后,手动弹出当前视图回到根视图,减少闪现概率:

struct SecondView: View {
    @AppStorage("onboardingCompleted") var onboardingCompleted = false
    @Environment(\.dismiss) private var dismiss
    
    var body: some View {
        VStack {
            Text("Onboarding complete")
            Button("Finish the onboarding") {
                onboardingCompleted = true
                // 先弹出当前视图,再等待App切换到主视图
                dismiss()
            }
        }
        .onAppear {
            print("Second View")
        }
    }
}

说明:方案1从根源解决了导航栈残留问题,是更稳定的实现方式。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 16:38:18