深度编程 SwiftUI NavigationView 导航

And*_*omb 7 navigation swiftui ios-navigationview swiftui-navigationlink

我正在尝试按顺序获得一个深层嵌套的编程导航堆栈。当手动完成导航(即:按下链接)时,以下代码按预期工作。当您按下Set Nav按钮时,导航堆栈确实发生了变化 - 但不像预期的那样 - 最终堆栈损坏[start -> b -> bbb],并且视图之间有很多翻转

class NavState: ObservableObject {
    @Published var firstLevel: String? = nil
    @Published var secondLevel: String? = nil
    @Published var thirdLevel: String? = nil
}

struct LandingPageView: View {

    @ObservedObject var navigationState: NavState

    func resetNav() {
        self.navigationState.firstLevel = "b"
        self.navigationState.secondLevel = "ba"
        self.navigationState.thirdLevel = "bbb"
    }

    var body: some View {

        return NavigationView {
            List {
                NavigationLink(
                    destination: Place(
                        text: "a",
                        childValues: [ ("aa", [ "aaa"]) ],
                        navigationState: self.navigationState
                    ).navigationBarTitle("a"),
                    tag: "a",
                    selection: self.$navigationState.firstLevel
                ) {
                    Text("a")
                }
                NavigationLink(
                    destination: Place(
                        text: "b",
                        childValues: [ ("bb", [ "bbb"]), ("ba", [ "baa", "bbb" ]) ],
                        navigationState: self.navigationState
                    ).navigationBarTitle("b"),
                    tag: "b",
                    selection: self.$navigationState.firstLevel
                ) {
                    Text("b")
                }

                Button(action: self.resetNav) {
                    Text("Set Nav")
                }
            }
            .navigationBarTitle("Start")
        }
        .navigationViewStyle(StackNavigationViewStyle())
    }
}


struct Place: View {
    var text: String
    var childValues: [ (String, [String]) ]

    @ObservedObject var navigationState: NavState

    var body: some View {
        List(childValues, id: \.self.0) { childValue in
            NavigationLink(
                destination: NextPlace(
                    text: childValue.0,
                    childValues: childValue.1,
                    navigationState: self.navigationState
                ).navigationBarTitle(childValue.0),
                tag: childValue.0,
                selection: self.$navigationState.secondLevel
            ) {
                Text(childValue.0)
            }
        }
    }
}

struct NextPlace: View {
    var text: String
    var childValues: [String]

    @ObservedObject var navigationState: NavState

    var body: some View {
        List(childValues, id: \.self) { childValue in
            NavigationLink(
                destination: FinalPlace(
                    text: childValue,
                    navigationState: self.navigationState
                ).navigationBarTitle(childValue),
                tag: childValue,
                selection: self.$navigationState.thirdLevel
            ) {
                Text(childValue)
            }
        }
    }
}

struct FinalPlace: View {
    var text: String
    @ObservedObject var navigationState: NavState

    var body: some View {
        let concat: String = "\(navigationState.firstLevel)/\(navigationState.secondLevel))/\(navigationState.thirdLevel)/"

        return VStack {
            Text(text)
            Text(concat)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我最初试图将导航转换动画作为问题源来解决 - 但如何禁用 NavigationView 推送和弹出动画表明这是不可配置的

是否有任何关于 >1 级程序化导航的合理示例?

编辑:我希望在这里获得的部分内容也是导航正常工作的初始状态- 如果我从具有导航状态的外部上下文进入我希望反映(即:从具有一些应用程序内上下文的通知开始从,或从保存到磁盘编码状态)然后我希望能够加载顶部View导航正确指向正确的子视图。本质上 -用实际值替换 the 中的nils NavState。Qt 的 QML 和 ReactRouter 都可以声明性地做到这一点——SwiftUI 也应该能够做到。

Asp*_*eri 3

更新:Xcode 14 / SwiftUI4

现在我们有了NavigationStack动态异构路径支持。因此更新的方法可能如下。

注意:虽然现在可以大大简化原始版本:a)我想保留视图层次结构b)我想展示不同模型类型的处理

使用 Xcode 14 / iOS 16 进行测试

演示

在 GitHub 上测试

struct LandingPageView2: View {
    class NavState: ObservableObject {
        @Published var path = NavigationPath()
        let level1 = [
            "a" : ["bb", "ba"],
            "b" : ["bb", "ba"]
        ]
        let level2 = [
            "bb" : ["baa", "bbb"],
            "ba" : ["baa", "bbb"]
        ]
    }
    struct Lev1: Hashable {
        var text: String
    }
    struct Lev2: Hashable {
        var text: String
    }
    struct Lev3: Hashable {
        var text: String
    }

    func resetNav() {
        self.navigationState.path.append(Lev1(text: "b"))
        self.navigationState.path.append(Lev2(text: "ba"))
        self.navigationState.path.append(Lev3(text: "bbb"))
    }

    @ObservedObject var navigationState: NavState

    var body: some View {
        NavigationStack(path: $navigationState.path) {
            List {
                NavigationLink("a", value: Lev1(text: "a"))
                NavigationLink("b", value: Lev1(text: "b"))
                Button(action: self.resetNav) {
                    Text("Set Nav")
                }
            }
            .navigationDestination(for: Lev1.self) {
                Place(text: $0.text, childValues: navigationState.level1[$0.text] ?? [])
            }
            .navigationDestination(for: Lev2.self) {
                NextPlace(text: $0.text, childValues: navigationState.level2[$0.text] ?? [])
            }
            .navigationDestination(for: Lev3.self) {
                FinalPlace(text: $0.text)
            }
            .navigationBarTitle("Start")
        }
        .navigationViewStyle(StackNavigationViewStyle())
        .environmentObject(navigationState)
    }
    
    // MARK: -
    struct Place: View {
        var text: String
        var childValues: [String]

        var body: some View {
            List(childValues, id: \.self) {
                NavigationLink($0, value: Lev2(text: $0))
            }
            .navigationTitle(text)
        }
    }
    struct NextPlace: View {
        var text: String
        var childValues: [String]

        var body: some View {
            List(childValues, id: \.self) {
                NavigationLink($0, value: Lev3(text: $0))
            }
            .navigationTitle(text)
        }
    }
    struct FinalPlace: View {
        var text: String
        @EnvironmentObject var navigationState: NavState

        var body: some View {
            VStack {
                Text(text)
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

原来的

这是因为动画完成时会形成新的堆栈级别,这就是它在手动点击的情况下起作用的原因。

通过以下修改,它可以工作:

func resetNav() {
    self.navigationState.firstLevel = "b"
    DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
        self.navigationState.secondLevel = "ba"
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            self.navigationState.thirdLevel = "bbb"
        }
    }
}
Run Code Online (Sandbox Code Playgroud)