Swiftui iOS 18 中的导航问题跳过页面

问题描述 投票:0回答:1

我有一个应用程序,它采用称为

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
        }
    }
}
ios swiftui navigation task swiftui-navigationstack
1个回答
0
投票
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
            }
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.