IndexSet引用节的索引而不是行的索引

时间:2019-06-14 19:32:09

标签: swift list foreach swiftui

我想通过使用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中的错误吗?

如果没有,那么我如何获得该行的索引?

3 个答案:

答案 0 :(得分:4)

我有完全一样的问题。事实证明,SwiftUI的(当前?)实现无法识别嵌套列表。这意味着SetSection中的每个List都被解释为单行,即使其中有一个带有实际ForEach的{​​{1}}。因此,SongRowIndexSet)始终返回零。

我还注意到的是,即使是扁平的层次结构,例如..

index.first!

..单个行不能在节之间移动。当直接使用两个List { Section { ForEach(...) { ... } } Section { ForEach(...) { ... } } } ,即不使用ForEach包装器时,也是如此。

我们可能应该针对每种现象提交报告。

答案 1 :(得分:1)

简单来说,解决此问题的方法是通过以下方式将该部分传递给您的删除方法:

  1. 在源数据上采用RandomAccessCollection
  2. 将该部分绑定到外部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.
        }
    }
}