在 SwiftUI 中,如何删除嵌套在两个 ForEach 语句中的列表行?
In SwiftUI, how do you delete a list row that is nested within two ForEach statements?
我有两个嵌套的 ForEach 语句,它们一起创建了一个包含 headers 部分的列表。 headers 部分是从列表中的 属性 项中收集的。 (在我下面的示例中,headers 部分是类别。)
我希望让用户能够从列表下方的数组中删除一个项目。这里的复杂性在于 .onDelete() returns 部分 行的索引 ,因此要正确识别要删除的项目,我还需要使用 section/category,如 中所述。不过,此 posting 中的代码对我不起作用 - 出于某种原因,category/territorie 变量不可用于 onDelete() 命令。在遍历第一个 ForEach 的类别(即 ForEach(0..< appData.sortedByCategories.count) { i in
)时,我确实尝试将类别转换为索引,但这也没有用。一个额外的问题是,我理想情况下希望使用 appData.deleteItem
函数来执行删除(见下文),但我无法从 Whosebug post 获得有关此问题的代码甚至可以正常工作当我不这样做的时候。
我忽略了什么?下面的例子说明了这个问题。非常感谢您抽出宝贵时间和提供任何见解!
@main
struct NestedForEachDeleteApp: App {
var appData = AppData()
var body: some Scene {
WindowGroup {
ContentView().environmentObject(appData)
}
}
}
import Foundation
import SwiftUI
class AppData: ObservableObject {
static let appData = AppData()
@Published var items = [Item]()
// Create a dictionary based upon the category
var dictGroupedByCategory: [String: [Item]] {
Dictionary(grouping: items.sorted(), by: {[=10=].category})
}
// Sort the category-based dictionary alphabetically
var sortedByCategories: [String] {
dictGroupedByCategory.map({ [=10=].key }).sorted(by: {[=10=] < })
}
func deleteItem(index: Int) {
items.remove(at: index)
}
init() {
items = [
Item(id: UUID(), name: "Item 1", category: "Category A"),
Item(id: UUID(), name: "Item 2", category: "Category A"),
Item(id: UUID(), name: "Item 3", category: "Category B"),
Item(id: UUID(), name: "Item 4", category: "Category B"),
Item(id: UUID(), name: "Item 5", category: "Category C"),
]
} // End of init()
}
class Item: ObservableObject, Identifiable, Comparable, Equatable {
var id = UUID()
@Published var name: String
@Published var category: String
// Implement Comparable and Equable conformance
static func <(lhs: Item, rhs: Item) -> Bool {
return lhs.name < rhs.name
}
static func == (lhs: Item, rhs: Item) -> Bool {
return lhs.category < rhs.category
}
// MARK: - Initialize
init(id: UUID, name: String, category: String) {
// Initialize stored properties.
self.id = id
self.name = name
self.category = category
}
}
struct ContentView: View {
@EnvironmentObject var appData: AppData
var body: some View {
List {
ForEach(appData.sortedByCategories, id: \.self) { category in
Section(header: Text(category)) {
ForEach(appData.dictGroupedByCategory[category] ?? []) { item in
Text(item.name)
} // End of inner ForEach (items within category)
.onDelete(perform: self.deleteItem)
} // End of Section
} // End of outer ForEach (for categories themselves)
} // End of List
} // End of body View
func deleteItem(at offsets: IndexSet) {
for offset in offsets {
print(offset)
// print(category) // error = "Cannot find 'category' in scope
// let indexToDelete = appData.items.firstIndex(where: {[=10=].id == item.id }) // Error = "Cannot find 'item' in scope
appData.deleteItem(index: offset) // this code will run but it removes the wrong item because the offset value is the offset *within the category*
}
}
} // End of ContentView
我已经找到了解决上述问题的方法,并将其张贴在这里以防其他人遇到同样的问题。解决方案涉及使用 .swipeActions()
而不是 .onDelete()
。由于我不明白的原因,我可以将 .swipeActions()
(但不是 .onDelete()
)附加到 Text(item.name)
代码行。这使得每次 ForEach 迭代的“项目”可用于 .swipeAction 代码,其他一切都非常简单。修改后的 ContentView 代码现在如下所示:
struct ContentView: View {
@EnvironmentObject var appData: AppData
var body: some View {
List {
ForEach(appData.sortedByCategories, id: \.self) { category in
Section(header: Text(category)) {
ForEach(appData.dictGroupedByCategory[category] ?? []) { item in
Text(item.name)
.swipeActions(allowsFullSwipe: false) {
Button(role: .destructive) {
print(category)
print(item.name)
if let indexToDelete = appData.items.firstIndex(where: {[=10=].id == item.id }) {
appData.deleteItem(index: indexToDelete)
} // End of action to perform if there is an indexToDelete
} label: {
Label("Delete", systemImage: "trash.fill")
}
} // End of .swipeActions()
} // End of inner ForEach (items within category)
} // End of Section
} // End of outer ForEach (for categories themselves)
} // End of List
} // End of body View
} // End of ContentView
我有两个嵌套的 ForEach 语句,它们一起创建了一个包含 headers 部分的列表。 headers 部分是从列表中的 属性 项中收集的。 (在我下面的示例中,headers 部分是类别。)
我希望让用户能够从列表下方的数组中删除一个项目。这里的复杂性在于 .onDelete() returns 部分 行的索引 ,因此要正确识别要删除的项目,我还需要使用 section/category,如 ForEach(0..< appData.sortedByCategories.count) { i in
)时,我确实尝试将类别转换为索引,但这也没有用。一个额外的问题是,我理想情况下希望使用 appData.deleteItem
函数来执行删除(见下文),但我无法从 Whosebug post 获得有关此问题的代码甚至可以正常工作当我不这样做的时候。
我忽略了什么?下面的例子说明了这个问题。非常感谢您抽出宝贵时间和提供任何见解!
@main
struct NestedForEachDeleteApp: App {
var appData = AppData()
var body: some Scene {
WindowGroup {
ContentView().environmentObject(appData)
}
}
}
import Foundation
import SwiftUI
class AppData: ObservableObject {
static let appData = AppData()
@Published var items = [Item]()
// Create a dictionary based upon the category
var dictGroupedByCategory: [String: [Item]] {
Dictionary(grouping: items.sorted(), by: {[=10=].category})
}
// Sort the category-based dictionary alphabetically
var sortedByCategories: [String] {
dictGroupedByCategory.map({ [=10=].key }).sorted(by: {[=10=] < })
}
func deleteItem(index: Int) {
items.remove(at: index)
}
init() {
items = [
Item(id: UUID(), name: "Item 1", category: "Category A"),
Item(id: UUID(), name: "Item 2", category: "Category A"),
Item(id: UUID(), name: "Item 3", category: "Category B"),
Item(id: UUID(), name: "Item 4", category: "Category B"),
Item(id: UUID(), name: "Item 5", category: "Category C"),
]
} // End of init()
}
class Item: ObservableObject, Identifiable, Comparable, Equatable {
var id = UUID()
@Published var name: String
@Published var category: String
// Implement Comparable and Equable conformance
static func <(lhs: Item, rhs: Item) -> Bool {
return lhs.name < rhs.name
}
static func == (lhs: Item, rhs: Item) -> Bool {
return lhs.category < rhs.category
}
// MARK: - Initialize
init(id: UUID, name: String, category: String) {
// Initialize stored properties.
self.id = id
self.name = name
self.category = category
}
}
struct ContentView: View {
@EnvironmentObject var appData: AppData
var body: some View {
List {
ForEach(appData.sortedByCategories, id: \.self) { category in
Section(header: Text(category)) {
ForEach(appData.dictGroupedByCategory[category] ?? []) { item in
Text(item.name)
} // End of inner ForEach (items within category)
.onDelete(perform: self.deleteItem)
} // End of Section
} // End of outer ForEach (for categories themselves)
} // End of List
} // End of body View
func deleteItem(at offsets: IndexSet) {
for offset in offsets {
print(offset)
// print(category) // error = "Cannot find 'category' in scope
// let indexToDelete = appData.items.firstIndex(where: {[=10=].id == item.id }) // Error = "Cannot find 'item' in scope
appData.deleteItem(index: offset) // this code will run but it removes the wrong item because the offset value is the offset *within the category*
}
}
} // End of ContentView
我已经找到了解决上述问题的方法,并将其张贴在这里以防其他人遇到同样的问题。解决方案涉及使用 .swipeActions()
而不是 .onDelete()
。由于我不明白的原因,我可以将 .swipeActions()
(但不是 .onDelete()
)附加到 Text(item.name)
代码行。这使得每次 ForEach 迭代的“项目”可用于 .swipeAction 代码,其他一切都非常简单。修改后的 ContentView 代码现在如下所示:
struct ContentView: View {
@EnvironmentObject var appData: AppData
var body: some View {
List {
ForEach(appData.sortedByCategories, id: \.self) { category in
Section(header: Text(category)) {
ForEach(appData.dictGroupedByCategory[category] ?? []) { item in
Text(item.name)
.swipeActions(allowsFullSwipe: false) {
Button(role: .destructive) {
print(category)
print(item.name)
if let indexToDelete = appData.items.firstIndex(where: {[=10=].id == item.id }) {
appData.deleteItem(index: indexToDelete)
} // End of action to perform if there is an indexToDelete
} label: {
Label("Delete", systemImage: "trash.fill")
}
} // End of .swipeActions()
} // End of inner ForEach (items within category)
} // End of Section
} // End of outer ForEach (for categories themselves)
} // End of List
} // End of body View
} // End of ContentView