不完整的向后滑动手势导致 NavigationPath 管理不善

And*_*dre 9 drag swiftui swiftui-navigationlink ios16 swiftui-navigationstack

我正在下面的示例代码中寻找以下错误的解决方案。我尝试使用 SwiftUI 4 和 iOS 16.0 导航 API 变更集实现导航器模式。

下面的示例将在 Xcode 14.0+ 中编译,如果在模拟器或 iOS 16.0 设备中运行,将会产生我所描述的错误。我想知道这是缺乏知识还是平台错误。通过我的日志,我可以看到,当我用不完整的向后滑动手势引发错误时,导航路径的元素计数上升到 2,而实际上它应该在根处返回到 0,并且仅在第一层保留 1 个元素看法。

有没有办法可以更好地管理此类视图层次结构的路径?或者,这是一个平台级错误吗?

import SwiftUI

enum AppViews: Hashable {
    case kombuchaProductsView
    case coffeeProductsView
    case customerCartView
}

struct RootView: View {
    @StateObject var drinkProductViewModel = DrinkProductViewModel()
    
    var body: some View {
        NavigationStack(path: self.$drinkProductViewModel.navPath) {
            List {
                Section("Products") {
                    NavigationLink(value: AppViews.kombuchaProductsView) {
                        HStack {
                            Text("View all Kombuchas")
                            Spacer()
                            Image(systemName: "list.bullet")
                        }
                    }
                    NavigationLink(value: AppViews.coffeeProductsView) {
                        HStack {
                            Text("View all Coffees")
                            Spacer()
                            Image(systemName: "list.bullet")
                        }
                    }
                }
                Section("Checkout") {
                    NavigationLink(value: AppViews.customerCartView) {
                        HStack {
                            Text("Cart")
                            Spacer()
                            Image(systemName: "cart")
                        }
                    }
                }
            }
            .navigationDestination(for: AppViews.self) { appView in
                switch appView {
                    case .kombuchaProductsView:
                        KombuchaProductsView(drinkProductViewModel: self.drinkProductViewModel)
                    case .coffeeProductsView:
                        CoffeeProductsView(drinkProductViewModel: self.drinkProductViewModel)
                    case .customerCartView:
                        Text("Not implemented")
                }
            }
        }
        .onAppear {
            print("RootView appeared.")
            print("Nav stack count: \(self.drinkProductViewModel.navPath.count) (RootView)")
        }
    }
}

struct KombuchaProductsView: View {
    @ObservedObject var drinkProductViewModel: DrinkProductViewModel
    var body: some View {
        ScrollView {
            VStack(spacing: 16) {
                ForEach(drinkProductViewModel.kombuchaProducts, id: \.self) { kombucha in
                    NavigationLink {
                        KombuchaView(
                            drinkProductViewModel: self.drinkProductViewModel,
                            kombucha: kombucha
                        )
                    } label: {
                        HStack {
                            Text(kombucha.name)
                            Spacer()
                            Text("$\(kombucha.price)")
                            Image(systemName: "chevron.right")
                                .foregroundColor(.gray)
                        }
                    }
                    Divider()
                }
                .padding()
            }
        }
        .navigationTitle("Kombucha Selection")
        .onAppear {
            print("KombuchaProductsView appeared.")
            print("Nav stack count: \(self.drinkProductViewModel.navPath.count) (KombuchaProductsView)")
        }
        .onDisappear {
            print("KombuchaProductsView disappeared")
        }
    }
}

struct CoffeeProductsView: View {
    @ObservedObject var drinkProductViewModel: DrinkProductViewModel
    var body: some View {
        ScrollView {
            VStack(spacing: 16) {
                ForEach(drinkProductViewModel.coffeeProducts, id: \.self) { coffee in
                    NavigationLink {
                        CoffeeView(
                            drinkProductViewModel: self.drinkProductViewModel,
                            coffee: coffee
                        )
                    } label : {
                        HStack {
                            Text(coffee.name)
                            Spacer()
                            Text("$\(coffee.price)")
                            Image(systemName: "chevron.right")
                                .foregroundColor(.gray)
                        }
                    }
                    Divider()
                }
                .padding()
            }
        }
        .navigationTitle("Coffee Selection")
        .onAppear {
            print("CoffeeProductsView appeared")
            print("Nav stack count: \(self.drinkProductViewModel.navPath.count) (CoffeeProductsView)")
        }
        .onDisappear {
            print("CoffeeProductsView disappeared")
        }
    }
}

struct KombuchaView: View {
    @ObservedObject var drinkProductViewModel: DrinkProductViewModel
    @State var kombucha: Kombucha
    var body: some View {
        VStack {
            Text("Price:")
                .font(.title)
            Text("\(kombucha.price)")
                .font(.callout)
        }
        .navigationTitle(kombucha.name)
        .onAppear {
            print("Nav stack count: \(self.drinkProductViewModel.navPath.count) (KombuchaView)")
        }
    }
}

struct CoffeeView: View {
    @ObservedObject var drinkProductViewModel: DrinkProductViewModel
    @State var coffee: Coffee
    var body: some View {
        VStack {
            Text("Price:")
                .font(.title)
            Text("\(coffee.price)")
                .font(.callout)
        }
        .navigationTitle(coffee.name)
        .onAppear {
            print("Nav stack count: \(self.drinkProductViewModel.navPath.count) (CoffeeView)")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

对于那些有兴趣精确编译我的示例的人,下面是我的模拟 ViewModel(它只是保存静态数据 - 它纯粹是为了这种探索而构建的):

class DrinkProductViewModel: ObservableObject {
    
    @Published var navPath = NavigationPath()
    
    @Published var customerCart = [Any]()
    
    @Published var kombuchaProducts = [Kombucha]()
    
    @Published var coffeeProducts = [Coffee]()
    
    init() {
        // Let's ignore networking, and assume a bunch of static data
        self.kombuchaProducts = [
            Kombucha(name: "Ginger Blast", price: 4.99),
            Kombucha(name: "Cayenne Fusion", price: 6.99),
            Kombucha(name: "Mango Tango", price: 4.49),
            Kombucha(name: "Clear Mind", price: 5.39),
            Kombucha(name: "Kiwi Melon", price: 6.99),
            Kombucha(name: "Super Berry", price: 5.99)
        ]
        self.coffeeProducts = [
            Coffee(name: "Cold Brew", price: 2.99),
            Coffee(name: "Nitro Brew", price: 4.99),
            Coffee(name: "Americano", price: 6.99),
            Coffee(name: "Flat White", price: 5.99),
            Coffee(name: "Espresso", price: 3.99)
        ]
    }
    
    func addToCustomerCart() {
        
    }
    
    func removeFromCustomerCart() {
        
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意:通过不完整的滑动手势,我的意思是用户开始从前缘拖动屏幕,然后按住它,然后将其返回到起始位置并释放它,以便用户保持在当前视图中,而不是继续后退。然后返回到父视图(而不是根视图)将导致导航链接失效。

您可以通过未能从康普茶或咖啡详细视图(最深的子视图)完成向后滑动手势来观察我所描述的错误,然后返回到产品列表视图之一并尝试单击导航之一链接(应该是死的)。

返回根视图通常会在运行时清除此场景并恢复 NavigationLink 功能。

ivi*_*4el 8

我也有同样的问题。

看来 navigationStack 坏了。

即使在苹果官方示例中,“后半滑动”手势也存在同样的问题:https ://developer.apple.com/documentation/swiftui/bringing_robust_navigation_struction_to_your_swiftui_app

我认为在这样做之后,所谓的“后半滑动”导航路径在内部中断了。

如果您澄清 Apple 支持的一些反馈,我将不胜感激!


Tom*_*Tom 6

似乎在 iOS 16.1 中已修复。

基于 Xcode 14.1 构建,首先安装在 iOS 16.0.3 上,出现了问题。然后更新到 iOS 16.1,测试相同的应用程序(无需重新构建或重新安装),问题消失了。可能是 SwiftUI 的错误


And*_*dre 0

iOS 16.0+(在 iOS 16.1 上测试)

导航堆栈路径的模型(基于价值的基础NavigationLink):

enum ProductViews: Hashable {
    case allKombuchas([Kombucha])
    case allCoffees([Coffee])
}

enum DrinkProduct: Hashable {
    case kombucha(Kombucha)
    case coffee(Coffee)
}
Run Code Online (Sandbox Code Playgroud)

模型(符合是最佳实践,可以避免在s 或视图等中Identifiable使用的需要。不符合 的模型可能会导致竞争条件或其他问题):\.selfListForEachIdentifiableNavigationStack

struct Kombucha: Hashable, Identifiable {
    let id = UUID()
    var name: String
    var price: Double
}

struct Coffee: Hashable, Identifiable {
    let id = UUID()
    var name: String
    var price: Double
}
Run Code Online (Sandbox Code Playgroud)

根视图(导航路径可以存在于对象中,也可以作为视图中ViewModel自己的成员存在,这在技术上仍然是 MVVM - 请注意,您还可以为您的 使用自定义类型,例如 的数组,然后推送和弹出值到该自定义类型的路径上):@StateNavigationPath[MyCustomTypes]

struct ParentView: View {
    
    @StateObject var drinkProductViewModel = DrinkProductViewModel()
    
    var body: some View {
        ZStack {
            NavigationStack(path: self.$drinkProductViewModel.navPath) {
                List {
                    Section("Products") {
                        NavigationLink(value: ProductViews.allKombuchas(self.drinkProductViewModel.kombuchaProducts)) {
                            HStack {
                                Text("Kombuchas")
                                Spacer()
                                Image(systemName: "list.bullet")
                            }
                        }
                        NavigationLink(value: ProductViews.allCoffees(self.drinkProductViewModel.coffeeProducts)) {
                            HStack {
                                Text("Coffees")
                                Spacer()
                                Image(systemName: "list.bullet")
                            }
                        }
                    }
                }
                .navigationDestination(for: ProductViews.self) { productView in
                    switch productView {
                    case .allKombuchas(_):
                        KombuchaProductsView(drinkProductViewModel: self.drinkProductViewModel)
                    case .allCoffees(_):
                        CoffeeProductsView(drinkProductViewModel: self.drinkProductViewModel)
                    }
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

子视图(使用基于值的NavigationLink视图很重要,否则可能会导致新的导航 API 出现竞争条件或其他错误):

struct KombuchaProductsView: View {
    @State var drinkProductViewModel: DrinkProductViewModel
    var body: some View {
        ScrollView {
            VStack(spacing: 16) {
                ForEach(drinkProductViewModel.kombuchaProducts) { kombucha in
                    NavigationLink(value: kombucha) {
                        HStack {
                            Text(kombucha.name)
                            Spacer()
                            Text("$\(kombucha.price)")
                            Image(systemName: "chevron.right")
                                .foregroundColor(.gray)
                        }
                    }
                }
                .padding()
            }
        }
        .navigationDestination(for: Kombucha.self) { kombucha in
            KombuchaView(
                drinkProductViewModel: self.drinkProductViewModel,
                kombucha: kombucha
            )
        }
        .navigationTitle("Kombucha Selection")
        .onDisappear {
           print("KombuchaProductsView disappeared")
           print("Nav stack count: \(self.drinkProductViewModel.navPath.count) (KombuchaProductsView)")
        }
    }
}

struct CoffeeProductsView: View {
    @State var drinkProductViewModel: DrinkProductViewModel
    var body: some View {
        ScrollView {
            VStack(spacing: 16) {
                ForEach(drinkProductViewModel.coffeeProducts) { coffee in
                    NavigationLink(value: coffee) {
                        HStack {
                            Text(coffee.name)
                            Spacer()
                            Text("$\(coffee.price)")
                            Image(systemName: "chevron.right")
                                .foregroundColor(.gray)
                        }
                    }
                    Divider()
                }
                .padding()
            }
        }
        .navigationDestination(for: Coffee.self) { coffee in
            CoffeeView(
                drinkProductViewModel: self.drinkProductViewModel,
                coffee: coffee
            )
        }
        .navigationTitle("Coffee Selection")
        .onDisappear {
            print("CoffeeProductsView disappeared")
            print("Nav stack count: \(self.drinkProductViewModel.navPath.count) (CoffeeProductsView)")
        }
    }
}

struct KombuchaView: View {
    @ObservedObject var drinkProductViewModel: DrinkProductViewModel
    @State var kombucha: Kombucha
    var body: some View {
        VStack {
            Text("Price:")
                .font(.title)
            Text("\(kombucha.price)")
                .font(.callout)
        }
        .navigationTitle(kombucha.name)
        .onAppear {
            print("Nav stack count: \(self.drinkProductViewModel.navPath.count) (KombuchaView)")
        }
    }
}

struct CoffeeView: View {
    @ObservedObject var drinkProductViewModel: DrinkProductViewModel
    @State var coffee: Coffee
    var body: some View {
        VStack {
            Text("Price:")
                .font(.title)
            Text("\(coffee.price)")
                .font(.callout)
        }
        .navigationTitle(coffee.name)
        .onAppear {
            print("Nav stack count: \(self.drinkProductViewModel.navPath.count) (CoffeeView)")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

ViewModel(出于虚拟目的......再次,NavigationPath可以仅存在于根视图中,但这也显示了可能性):

class DrinkProductViewModel: ObservableObject {
    
    @Published var navPath = NavigationPath()
    
    @Published var customerCart = [Any]()
    
    @Published var kombuchaProducts = [Kombucha]()
    
    @Published var coffeeProducts = [Coffee]()
    
    init() {
        // Let's ignore networking, and assume a bunch of static data
        self.kombuchaProducts = [
            Kombucha(name: "Ginger Blast", price: 4.99),
            Kombucha(name: "Cayenne Fusion", price: 6.99),
            Kombucha(name: "Mango Tango", price: 4.49),
            Kombucha(name: "Clear Mind", price: 5.39),
            Kombucha(name: "Kiwi Melon", price: 6.99),
            Kombucha(name: "Super Berry", price: 5.99)
        ]
        self.coffeeProducts = [
            Coffee(name: "Cold Brew", price: 2.99),
            Coffee(name: "Nitro Brew", price: 4.99),
            Coffee(name: "Americano", price: 6.99),
            Coffee(name: "Flat White", price: 5.99),
            Coffee(name: "Espresso", price: 3.99)
        ]
    }
    
    func addToCustomerCart() {
        
    }
    
    func removeFromCustomerCart() {
        
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,重要的是您要考虑这样一个事实:您可以在整个代码库中使用多个枚举,以便您可以.navigationDestination正确利用。您不需要整个应用程序的视图层次结构存在于一个模型中,否则您可能被迫使用单个模型.navigationDestination,并且很难将属性或对象传递到子视图中。