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

Joo*_*ila 7 foreach list swift swiftui

我想Section通过使用.onDelete修饰符在SwiftUI上实现“滑动到删除”功能。问题在于它总是删除列表中的第一项。

我的视图有一个列表,其中包含用创建的动态部分ForEach

struct SetListView : View {

    var setlist: Setlist
    var body : some View {

        List { 
            ForEach(setlist.sets) { 
                 SetSection(number: $0.id, songs: $0.songs) 
            }
        }
        .listStyle(.grouped)
    }
}
Run Code Online (Sandbox Code Playgroud)

在每个部分中,都有另一个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!)
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

调试时,我发现的IndexSet是指当前部分而不是行。因此,从第一节删除项目时,总是删除第一项(因为第一节的索引为0)。

这是SwiftUI中的错误吗?

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

gar*_*ray 8

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

  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!)")
}
Run Code Online (Sandbox Code Playgroud)

一个完整的,人为的工作示例

为方便起见,也可以以Gist形式提供):

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!)")
  }

}
Run Code Online (Sandbox Code Playgroud)

非常感谢@ rob-mayoff,他通过Twitter为我指出了解决方案的正确方向!


Nik*_*uer 6

我有完全一样的问题。事实证明,SwiftUI的(当前?)实现无法识别嵌套列表。这意味着,每个SetSection在你List被解释为单行,即使你有一个ForEach在它与实际SongRow秒。因此,IndexSetindex.first!)始终返回零。

我还注意到的是,即使使用诸如..

List {
   Section {
       ForEach(...) {
          ...
       }
   }
   Section {
       ForEach(...) {
          ...
       }
   }
}
Run Code Online (Sandbox Code Playgroud)

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

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