我有一个应用程序,它采用称为
ObservableObject
的 AppState()
,设置为 StateObject
并传递到 MenuView()
,依此类推,作为来自 EnvironmentObject
的 @main struct
。
My MenuView 使用新的
TabView
以及 TabSection
和 Tab
。它嵌套在 NavigationStack
中,其中 navigationDestination()
位于其内部,.alert()
附着于其外部。
此附件
Alert
的主要用例是从应用程序内的任何视图导航到特定页面。
ContentView()
使用 navigationDestination() 导航到 PageTwo()
,该导航绑定到 @EnvironmentObject
中更新的 Task
(实际应用程序执行异步操作,因此需要保留)。请注意,NavigationStack
也在该视图中,而不是其他视图中。
在
PageTwo()
上,导航至 PageThree()
,导航方式与之前相同,但也会启动 10 秒计时器。
一旦计时器到了,就会弹出警报。这提供了再次导航至
ThirdPage()
的选项。这似乎在应用程序内的任何视图中都能很好地工作。
我遇到的问题是,一旦通过警报导航到
PageThree()
,按左上角的“后退”按钮将跳过PageTwo()
并直接返回到ContentView()
。
不良行为。
我是不是做错了什么?是我导航设置不正确吗?我是否错误地处理了
navigationDestination
?
最小重现:
@main
struct FirstAppApp: App {
@StateObject private var app = AppState()
var body: some Scene {
WindowGroup {
MenuView()
.environmentObject(app)
}
}
}
struct MenuView: View {
@EnvironmentObject var app: AppState
@State private var device = UIDevice.current.userInterfaceIdiom
@AppStorage("MyAppTabViewCustomization")
private var customisation: TabViewCustomization
var body: some View {
NavigationStack {
Group {
if app.showDisclaimer {
Legal()
} else {
TabView {
ForEach(SidebarItem.allCases, id: \.self) { tab in
TabSection("Planning") {
Tab("Plan", systemImage: "house") {
ContentView()
}.customizationID("plan")
}
}
}
.tabViewStyle(.sidebarAdaptable)
.tabViewCustomization($customisation)
}
}
.navigationDestination(isPresented: $app.atisUpdated) {
PageThree()
}
}
.alert(isPresented: $app.newAtisAvailable) {
Alert(
title: Text("Press GO to navigate to page 3"),
primaryButton: .default(Text("GO"), action: {
Task {
app.atisUpdated = true
}
}),
secondaryButton: .cancel(Text("STOP"), action: {
Task {
app.atisTimer.invalidate()
}
})
)
}
}
}
enum SidebarItem: String, Identifiable, CaseIterable {
case planner = "Planner"
var id: String { self.rawValue }
var iconName: String {
switch self {
case .planner:
return "house"
}
}
var customizationID: String {
switch self {
case .planner:
return "planner"
}
}
}
struct ContentView: View {
@EnvironmentObject var app: AppState
var body: some View {
NavigationStack {
VStack {
Text("Page 1")
Button("Go to page 2") {
Task {
app.readyToNavigate = true
}
}
}
.navigationDestination(isPresented: $app.readyToNavigate) {
PageTwo()
}
}
}
}
struct PageTwo: View {
@EnvironmentObject var app: AppState
var body: some View {
VStack {
Text("Page 2")
Button("Go to page 3") {
Task {
app.atisButtonPressed = true
await app.startTimer()
}
}
}
.navigationDestination(isPresented: $app.atisButtonPressed) {
PageThree()
}
}
}
struct PageThree: View {
@EnvironmentObject var app: AppState
var body: some View {
NavigationStack {
VStack {
Text("Page 3")
}
}
}
}
class AppState: ObservableObject {
@Published var showDisclaimer: Bool = false // !UserDefaults.standard.bool(forKey: "DisclaimerAccepted")
@Published var atisButtonPressed: Bool = false
@Published var readyToNavigate: Bool = false
@Published var atisUpdated: Bool = false
@Published var atisTimer: Timer = Timer()
@Published var newAtisAvailable: Bool = false
@MainActor
func startTimer() async {
self.atisTimer = Timer.scheduledTimer(withTimeInterval: 10, repeats: true) { _ in
self.newAtisAvailable = true
}
}
}
I think the problem is that you'r not ensuring that NavStack maintains the correct state, the root cause is that the navigation state might be overwritten or improperly managed when the alert triggers the navigation.
You must ensure single NavStack in the entire App rather than nesting NavStacks instances within individual views, then Use Binding and ObservableObject properly, you must manage the navigation state more effectively by ensuring state changes are correctly bound and observed across views.
here is an update of your version code :
import SwiftUI
@main
struct FirstAppApp: App {
@StateObject private var app = AppState()
var body: some Scene {
WindowGroup {
NavigationStack {
MenuView()
.environmentObject(app)
}
}
}
}
struct MenuView: View {
@EnvironmentObject var app: AppState
@AppStorage("MyAppTabViewCustomization") private var customisation: TabViewCustomization
var body: some View {
Group {
if app.showDisclaimer {
Legal()
} else {
TabView {
ForEach(SidebarItem.allCases, id: \.self) { tab in
TabSection("Planning") {
Tab("Plan", systemImage: "house") {
ContentView()
}.customizationID("plan")
}
}
}
.tabViewStyle(.sidebarAdaptable)
.tabViewCustomization($customisation)
.alert(isPresented: $app.newAtisAvailable) {
Alert(
title: Text("Press GO to navigate to page 3"),
primaryButton: .default(Text("GO"), action: {
app.atisUpdated = true
}),
secondaryButton: .cancel(Text("STOP"), action: {
app.atisTimer.invalidate()
})
)
}
}
}
.navigationDestination(isPresented: $app.atisUpdated) {
PageThree()
}
}
}
struct ContentView: View {
@EnvironmentObject var app: AppState
var body: some View {
VStack {
Text("Page 1")
Button("Go to page 2") {
app.readyToNavigate = true
}
}
.navigationDestination(isPresented: $app.readyToNavigate) {
PageTwo()
}
}
}
struct PageTwo: View {
@EnvironmentObject var app: AppState
var body: some View {
VStack {
Text("Page 2")
Button("Go to page 3") {
app.atisButtonPressed = true
app.startTimer()
}
}
.navigationDestination(isPresented: $app.atisButtonPressed) {
PageThree()
}
}
}
struct PageThree: View {
@EnvironmentObject var app: AppState
var body: some View {
VStack {
Text("Page 3")
}
}
}
class AppState: ObservableObject {
@Published var showDisclaimer: Bool = false // !UserDefaults.standard.bool(forKey: "DisclaimerAccepted")
@Published var atisButtonPressed: Bool = false
@Published var readyToNavigate: Bool = false
@Published var atisUpdated: Bool = false
@Published var atisTimer: Timer = Timer()
@Published var newAtisAvailable: Bool = false
func startTimer() {
self.atisTimer = Timer.scheduledTimer(withTimeInterval: 10, repeats: false) { _ in
DispatchQueue.main.async {
self.newAtisAvailable = true
}
}
}
}