在同一 NavigationView 中更新 NavigationLinks 时 SwiftUI 视图崩溃

SwiftUI view crashes when updating NavigationLinks in same NavigationView

为了解释这种行为,我构建了一个简化的项目,它创建了与我的工作项目相同的行为。请记住,我的真实项目比这个大得多,限制也多。

我有一个简单的 struct,只有一个 Int 作为 属性(名为 MyObject)和一个存储 6 个 MyObject 的模型。

struct MyObject: Identifiable {
    let id: Int
}

final class Model: ObservableObject {
    let objects: [MyObject] = [
        MyObject(id: 1),
        MyObject(id: 2),
        MyObject(id: 3),
        MyObject(id: 4),
        MyObject(id: 5),
        MyObject(id: 6)
    ]
}

请注意,在我的真实项目中,Model更复杂(从JSON加载)。

然后我有一个 FavoritesManager 可以添加和删除 MyObject 作为收藏夹。它有一个 @Published id 数组 (Int):

final class FavoritesManager: ObservableObject {
    @Published var favoritesIds = [Int]()
    
    func addToFavorites(object: MyObject) {
        guard !favoritesIds.contains(object.id) else { return }
        favoritesIds.append(object.id)
    }
    
    func removeFromFavorites(object: MyObject) {
        if let index = favoritesIds.firstIndex(of: object.id) {
            favoritesIds.remove(at: index)
        }
    }
}

我的 App 结构创建了 Model 和 FavoritesManager 并在我的第一个视图中注入它们:

@main
struct testAppApp: App {
    @StateObject private var model = Model()
    @StateObject private var favoritesManager = FavoritesManager()    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(model)
                .environmentObject(favoritesManager)
        }
    }
}

现在我几乎没有显示我的 objects 的视图:

ContentView 使用 NavigationLinkFavoritesView 显示 link 到 ObjectsListView。在我的真实项目中,ObjectsListView不会显示所有object,只显示一个选择(例如按类别)。

struct ContentView: View {
    @EnvironmentObject var model: Model
    var body: some View {
        NavigationView {
            VStack {
                NavigationLink(
                    destination: ObjectsListView(objects: model.objects), label: {
                        Text("List of objects")
                    }
                )
                FavoritesView()
                    .padding()
            }
        }
    }
}

FavoritesView 显示当前所选收藏夹的 id。当最喜欢的是 added/removed.

时,它观察 FavoritesManager 更新它的主体

重要提示:它会创建一个新的 NavigationLink 到收藏夹详细信息视图。如果删除此 link,则不会发生错误。

struct FavoritesView: View {
    @EnvironmentObject var model: Model
    @EnvironmentObject var favoritesManager: FavoritesManager
    var body: some View {
        HStack {
            Text("Favorites:")
            let favoriteObjects = model.objects.filter( { favoritesManager.favoritesIds.contains([=14=].id) })
            ForEach(favoriteObjects) { object in
                NavigationLink(destination: DetailsView(object: object), label: {
                    Text("\(object.id)")
                })
            }
        }
    }
}

ObjectsListView 只是显示 objects 并使 link 到 DetailsView.

struct ObjectsListView: View {
    @State var objects: [MyObject]
    var body: some View {
        List {
            ForEach(objects) { object in
                NavigationLink(destination: DetailsView(object: object), label: { Text("Object \(object.id)")})
            }
        }
    }
}

DetailsView 显示 object 详细信息并将按钮添加到 add/remove 当前 object 收藏。 如果您按下星标,当前 object 将被添加为收藏夹(如预期的那样)并且 FavoritesView 将更新为 display/remove 这个新 ID(如预期的那样)。

但问题在于:只要按下星标,当前视图 (DetailsView) 就会消失,然后返回(没有动画)到上一个视图 (ObjectsListView)。

struct DetailsView: View {
    @EnvironmentObject var favoritesManager: FavoritesManager
    @State var object: MyObject
    var body: some View {
        VStack {
            HStack {
                Text("You're on object \(object.id) detail page")
                
                Button(action: {
                    if favoritesManager.favoritesIds.contains(object.id) {
                        favoritesManager.removeFromFavorites(object: object)
                    },
                    else {
                        favoritesManager.addToFavorites(object: object)
                    }
                }, label: {
                    Image(systemName: favoritesManager.favoritesIds.contains(object.id) ? "star.fill" : "star")
                        .foregroundColor(.yellow)
                })
            }
            
            Text("Bug is here: if your press the star, it will go back to previous view.")
                .font(.caption)
                .padding()
        }
    }
}

您可以下载complete example project here.

你可能会问为什么我不直接在ContentView中显示object的列表。这是因为我的真实项目。它在列表中显示按类别分类的 objects。我用 ObjectsListView.

重现了这种行为

我还需要将 ModelFavoritesManager 分开,因为在我的实际项目中,我不希望我的 ContentView 的正文每次都更新 [=83] =].这就是为什么我移动了 FavoritesView.

中显示的所有收藏夹

我认为这个问题的发生是因为我 add/remove NavigationLinks 当前 NavigationView。看起来它正在重新加载整个导航层次结构。但是我无法弄清楚如何使用相同的视图层次结构获得预期结果(按下星号按钮时停留在 DetailsView 中)。我可能在某处做错了...

.navigationViewStyle(.stack) 添加到 ContentView 中的 NavigationView。 这样,您的代码对我来说效果很好,我可以按 DetailsView 中的星号,它仍然显示。它不是 返回上一个视图。使用 macOS 12.2,Xcode 13.2, 目标 ios 15.2 和 macCatalyst 12.1。在真实设备上测试。