在同一 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
使用 NavigationLink
和 FavoritesView
显示 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
.
重现了这种行为
我还需要将 Model
和 FavoritesManager
分开,因为在我的实际项目中,我不希望我的 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。在真实设备上测试。
为了解释这种行为,我构建了一个简化的项目,它创建了与我的工作项目相同的行为。请记住,我的真实项目比这个大得多,限制也多。
我有一个简单的 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
使用 NavigationLink
和 FavoritesView
显示 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
.
我还需要将 Model
和 FavoritesManager
分开,因为在我的实际项目中,我不希望我的 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。在真实设备上测试。