我想通过使用Section
修饰符在SwiftUI .onDelete
上实现刷卡删除功能。问题在于它总是删除列表中的第一项。
我的视图有一个列表,其中包含用ForEach
创建的动态部分。
struct SetListView : View {
var setlist: Setlist
var body : some View {
List {
ForEach(setlist.sets) {
SetSection(number: $0.id, songs: $0.songs)
}
}
.listStyle(.grouped)
}
}
在每个部分中,还有一个ForEach
用于创建动态行:
private struct SetSection : View {
var number: Int
@State var songs: [Song]
var body : some View {
Section (header: Text("Set \(number)"), footer: Spacer()) {
ForEach(songs) { song in
SongRow(song: song)
}
.onDelete { index in
self.songs.remove(at: index.first!)
}
}
}
}
在调试时,我发现IndexSet
是指当前部分而不是行。因此,从第一部分删除项目时,总是删除第一项(因为第一部分的索引为0)。
这是SwiftUI中的错误吗?
如果没有,那么我如何获得该行的索引?
答案 0 :(得分:4)
我有完全一样的问题。事实证明,SwiftUI的(当前?)实现无法识别嵌套列表。这意味着SetSection
中的每个List
都被解释为单行,即使其中有一个带有实际ForEach
的{{1}}。因此,SongRow
(IndexSet
)始终返回零。
我还注意到的是,即使是扁平的层次结构,例如..
index.first!
..单个行不能在节之间移动。当直接使用两个List {
Section {
ForEach(...) {
...
}
}
Section {
ForEach(...) {
...
}
}
}
,即不使用ForEach
包装器时,也是如此。
我们可能应该针对每种现象提交报告。
答案 1 :(得分:1)
简单来说,解决此问题的方法是通过以下方式将该部分传递给您的删除方法:
RandomAccessCollection
。ForEach
中,然后在内部ForEach
中使用它,并将其传递给删除方法:List {
ForEach(someGroups.indices) { section in
bind(self.someGroups[section]) { someGroup in
Section(header: Text(someGroup.displayName)) {
ForEach(someGroup.numbers) { number in
Text("\(number)")
}
.onDelete { self.delete(at: $0, in: section) }
}
}
}
}
func delete(at offsets: IndexSet, in section: Int) {
print("\(section), \(offsets.first!)")
}
(Also available in Gist form for convenience):
import SwiftUI
func bind<Value, Answer>(_ value: Value, to answer: (Value) -> Answer) -> Answer { answer(value) }
struct Example: View {
struct SomeGroup: Identifiable, RandomAccessCollection {
typealias Indices = CountableRange<Int>
public typealias Index = Int;
var id: Int
var displayName: String
var numbers: [Int]
public var endIndex: Index {
return numbers.count - 1
}
public var startIndex: Index {
return 0
}
public subscript(position: Int) -> Int {
get { return numbers[position] }
set { numbers[position] = newValue }
}
}
var someGroups: [SomeGroup] = {
return [
SomeGroup(id: 0, displayName: "First", numbers: [1, 2, 3, 4]),
SomeGroup(id: 1, displayName: "Second", numbers: [1, 3, 5, 7])
]
}()
var body: some View {
List {
ForEach(someGroups.indices) { section in
bind(self.someGroups[section]) { someGroup in
Section(header: Text(someGroup.displayName)) {
ForEach(someGroup.numbers) { number in
Text("\(number)")
}
.onDelete { self.delete(at: $0, in: section) }
}
}
}
}
.listStyle(.grouped)
}
func delete(at offsets: IndexSet, in section: Int) {
print("\(section), \(offsets.first!)")
}
}
非常感谢@rob-mayoff通过Twitter向我指出了该解决方案的正确方向!
答案 2 :(得分:0)
它似乎在 Xcode 12.5 上运行良好
我是这样使用它的:
struct Sections: View {
var items: [SomeData]
private var sections: [Date: [SomeData]] {
Dictionary(grouping: items, by: { $0.date })
}
private var headers: [Date] {
sections.map({ $0.key }).sorted().reversed()
}
var body: some View {
List {
ForEach(headers, id: \.self) { date in
Section(header: Text(date.friendly) {
AList(items: sections[date]!)
}
}
}
}
}
struct AList: View {
var items: [SomeData]
var body: some View {
ForEach(items) { data in
...
}
.onDelete(perform: delete)
}
private func delete(at offsets: IndexSet) {
// You can use `items.remove(atOffsets: offsets)`
for offset in offsets {
let data = items[offset]
print("\(data)")
// You can check here that this is the item that you want to remove and then you need to remove it from your data source.
// I'm using Realm and @Published vars that works fine, you should adapt to your logic.
}
}
}