简体   繁体   English

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

[英]SwiftUI view crashes when updating NavigationLinks in same NavigationView

To explain this behavior, I have built a simplified project that creates same behavior as my working project.为了解释这种行为,我构建了一个简化的项目,它创建了与我的工作项目相同的行为。 Please keep in mind that my real project is much bigger and have more constraints than this one.请记住,我的真实项目比这个更大,并且有更多的限制。

I have a simple struct with only an Int as property (named MyObject ) and a model that stores 6 MyObject .我有一个简单的struct ,只有一个Int作为属性(名为MyObject )和一个存储 6 个MyObject的 model 。

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)
    ]
}

Please note that in my real project, Model is more complicated (loading from JSON).请注意,在我的实际项目中, Model更复杂(从 JSON 加载)。

Then I have a FavoritesManager that can add and remove a MyObject as favorite.然后我有一个FavoritesManager管理器,它可以添加和删除一个MyObject作为收藏夹。 It has a @Published array of ids ( Int ):它有一个@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)
        }
    }
}

My App struct creates Model and FavoritesManager and injects them in my first view:我的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)
        }
    }
}

Now I have few views that display my objects:现在我有几个视图显示我的对象:

ContentView displays a link to ObjectsListView using NavigationLink and a FavoritesView . ContentView使用NavigationLinkFavoritesView显示指向ObjectsListView的链接。 In my real project, ObjectsListView does not display all objects, only a selection (by category for example).在我的真实项目中, ObjectsListView不显示所有对象,只显示一个选择(例如按类别)。

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 displays ids of current selected favorites. FavoritesView显示当前所选收藏夹的 id。 It observes FavoritesManager to update its body when a favorite is added/removed.它观察FavoritesManager夹管理器在添加/删除收藏夹时更新其主体。

Important: it creates a new NavigationLink to favorites details view.重要提示:它会创建一个新的NavigationLink到收藏夹详细信息视图。 If you remove this link, bug does not happen.如果您删除此链接,则不会发生错误。

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($0.id) })
            ForEach(favoriteObjects) { object in
                NavigationLink(destination: DetailsView(object: object), label: {
                    Text("\(object.id)")
                })
            }
        }
    }
}

ObjectsListView simply displays objects and makes a link to DetailsView . ObjectsListView只是显示对象并链接到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 displays object details and add a button to add/remove current object as favorite. DetailsView显示 object 详细信息,并添加一个按钮以添加/删除当前 object 作为收藏夹。 If you press the star, current object will be added as favorite (as expected) and FavoritesView will be updated to display/remove this new id (as expected).如果您按下星号,当前的 object 将被添加为收藏夹(如预期的那样), FavoritesView将更新以显示/删除此新 ID(如预期的那样)。

But here is the bug: as soon as you press the star, current view ( DetailsView ) disappears and it goes back (without animation) to previous view ( ObjectsListView ).但这里有一个错误:一旦您按下星号,当前视图 ( 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()
        }
    }
}

You can download complete example project here .您可以在此处下载完整的示例项目

You may ask why I don't display objects list in ContentView directly.你可能会问为什么我不直接在ContentView中显示对象列表。 It is because of my real project.这是因为我的真实项目。 It displays objects classified by categories in lists.它在列表中显示按类别分类的对象。 I have reproduce this behavior with ObjectsListView .我已经用ObjectsListView重现了这种行为。

I also need to separated Model and FavoritesManager because in my real project I don't want that my ContentView 's body is updated everytime a favorite is added/removed.我还需要将ModelFavoritesManager分开,因为在我的实际项目中,我不希望每次添加/删除收藏夹时都会更新我的ContentView的正文。 That's why I have moved all favorites displaying in FavoritesView .这就是为什么我移动了 FavoritesView 中显示的所有FavoritesView

I believe that this issue happens because I add/remove NavigationLinks in current NavigationView .我相信这个问题的发生是因为我在当前NavigationView NavigationLinks And it looks like it is reloading whole navigation hierarchy.看起来它正在重新加载整个导航层次结构。 But I can't figure out how to get expected result (staying in DetailsView when pressing star button) with same views hierarchy.但我不知道如何获得具有相同视图层次结构的预期结果(按下星形按钮时留在DetailsView中)。 I may do something wrong somewhere...我可能在某个地方做错了什么......

Add .navigationViewStyle(.stack) to your NavigationView in ContentView .ContentView .navigationViewStyle(.stack)添加到NavigationView中。 With that, your code works well for me, I can press the star in DetailsView , and it remains displayed.这样,您的代码对我来说效果很好,我可以按DetailsView中的星号,它仍然显示。 It does not go back to the previous View.它不会 go 回到之前的视图。 Using macos 12.2, Xcode 13.2, targets ios 15.2 and macCatalyst 12.1.使用 macos 12.2、Xcode 13.2,针对 ios 15.2 和 macCatalyst 12.1。 Tested on real devices.在真实设备上测试。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM