这是一个按预期工作的简单 SwiftUI 列表:
struct App: View {
let items = Array(100...200)
var body: some View {
List {
ForEach(items, id: \.self) { index, item in
Text("Item \(item)")
}
}.frame(width: 200, height: 200)
}
}
Run Code Online (Sandbox Code Playgroud)
但是当我试图枚举项目通过更换items与items.enumerated()我得到这些错误:
在 'ForEach' 上引用初始值设定项 'init(_:id:content:)' 要求 '(offset: Int, element: Int)' 符合 'Hashable'
在“ForEach”上引用初始值设定项“init(_:id:content:)”要求“EnumeratedSequence<[Int]>”符合“RandomAccessCollection”
我如何使这项工作?
Sen*_*ful 50
TL; 博士
警告:如果你养成使用enumerated()with的习惯ForEach,你可能有一天会遇到EXC_BAD_INSTRUCTION或Fatal error: Index out of bounds例外。这是因为并非所有集合都具有从 0 开始的索引。
更好的默认值是使用zip:
ForEach(Array(zip(items.indices, items)), id: \.0) { index, item in
// index and item are both safe to use here
}
Run Code Online (Sandbox Code Playgroud)
(id: \.1如果您的物品符合 ,您也可以使用Identifiable。)
超过乡亲点免费提到,这不是安全的依靠enumerated()与ForEach生产,因为不是所有集合零基于索引:
从技术上讲,这不是执行此操作的最正确方法。
todos使用索引集合压缩数组会更正确,也更详细。在这种情况下,我们是安全的,因为我们正在处理一个简单的基于 0 的索引数组,但如果我们在生产中这样做,我们可能应该使用zip基于方法。
Apple 的枚举函数文档也提到了这一点:
/// Returns a sequence of pairs (*n*, *x*), where *n* represents a
/// consecutive integer starting at zero and *x* represents an element of
/// the sequence.
///
/// This example enumerates the characters of the string "Swift" and prints
/// each character along with its place in the string.
///
/// for (n, c) in "Swift".enumerated() {
/// print("\(n): '\(c)'")
/// }
/// // Prints "0: 'S'"
/// // Prints "1: 'w'"
/// // Prints "2: 'i'"
/// // Prints "3: 'f'"
/// // Prints "4: 't'"
///
/// When you enumerate a collection, the integer part of each pair is a counter
/// for the enumeration, but is not necessarily the index of the paired value.
/// These counters can be used as indices only in instances of zero-based,
/// integer-indexed collections, such as `Array` and `ContiguousArray`. For
/// other collections the counters may be out of range or of the wrong type
/// to use as an index. To iterate over the elements of a collection with its
/// indices, use the `zip(_:_:)` function.
///
/// This example iterates over the indices and elements of a set, building a
/// list consisting of indices of names with five or fewer letters.
///
/// let names: Set = ["Sofia", "Camilla", "Martina", "Mateo", "Nicolás"]
/// var shorterIndices: [Set<String>.Index] = []
/// for (i, name) in zip(names.indices, names) {
/// if name.count <= 5 {
/// shorterIndices.append(i)
/// }
/// }
///
/// Now that the `shorterIndices` array holds the indices of the shorter
/// names in the `names` set, you can use those indices to access elements in
/// the set.
///
/// for i in shorterIndices {
/// print(names[i])
/// }
/// // Prints "Sofia"
/// // Prints "Mateo"
///
/// - Returns: A sequence of pairs enumerating the sequence.
///
/// - Complexity: O(1)
Run Code Online (Sandbox Code Playgroud)
在您的特定情况下enumerated()可以使用,因为您使用的是基于 0 的索引数组,但是由于上述细节,一直依赖enumerated()可能会导致不明显的错误。
以这个片段为例:
ForEach(Array(items.enumerated()), id: \.offset) { offset, item in
Button(item, action: { store.didTapItem(at: offset) })
}
// ...
class Store {
var items: ArraySlice<String>
func didTapItem(at index: Int) {
print(items[index])
}
}
Run Code Online (Sandbox Code Playgroud)
首先请注意,我们躲过了一个子弹,Button(item...因为enumerated()它保证item可以直接访问而不会导致异常。但是,如果不是item我们使用items[offset],则很容易引发异常。
最后,该行print(items[index])很容易导致异常,因为索引(实际上是偏移量)可能超出范围。
因此,更安全的方法是始终使用zip本文顶部提到的方法。
另一个更喜欢的原因zip是,如果您尝试将相同的代码与不同的集合(例如 Set)一起使用,则在对类型 ( items[index]) 进行索引时可能会出现以下语法错误:
无法将“Int”类型的值转换为预期的参数类型“Set.Index”
通过使用zip基于方法,您仍然可以索引到集合中。
如果您打算经常使用它,您还可以在集合上创建扩展。
您可以在 Playground 中测试这一切:
import PlaygroundSupport
import SwiftUI
// MARK: - Array
let array = ["a", "b", "c"]
Array(array.enumerated()) // [(offset 0, element "a"), (offset 1, element "b"), (offset 2, element "c")]
Array(zip(array.indices, array)) // [(.0 0, .1 "a"), (.0 1, .1 "b"), (.0 2, .1 "c")]
let arrayView = Group {
ForEach(Array(array.enumerated()), id: \.offset) { offset, element in
PrintView("offset: \(offset), element: \(element)")
Text("value: \(array[offset])")
}
// offset: 0, element: a
// offset: 1, element: b
// offset: 2, element: c
ForEach(Array(zip(array.indices, array)), id: \.0) { index, element in
PrintView("index: \(index), element: \(element)")
Text("value: \(array[index])")
}
// index: 0, element: a
// index: 1, element: b
// index: 2, element: c
}
// MARK: - Array Slice
let arraySlice = array[1...2] // ["b", "c"]
Array(arraySlice.enumerated()) // [(offset 0, element "b"), (offset 1, element "c")]
Array(zip(arraySlice.indices, arraySlice)) // [(.0 1, .1 "b"), (.0 2, .1 "c")]
// arraySlice[0] // ? EXC_BAD_INSTRUCTION
arraySlice[1] // "b"
arraySlice[2] // "c"
let arraySliceView = Group {
ForEach(Array(arraySlice.enumerated()), id: \.offset) { offset, element in
PrintView("offset: \(offset), element: \(element)")
// Text("value: \(arraySlice[offset])") ? Fatal error: Index out of bounds
}
// offset: 0, element: b
// offset: 1, element: c
ForEach(Array(zip(arraySlice.indices, arraySlice)), id: \.0) { index, element in
PrintView("index: \(index), element: \(element)")
Text("value: \(arraySlice[index])")
}
// index: 1, element: b
// index: 2, element: c
}
// MARK: - Set
let set: Set = ["a", "b", "c"]
Array(set.enumerated()) // [(offset 0, element "b"), (offset 1, element "c"), (offset 2, element "a")]
Array(zip(set.indices, set)) // [({…}, .1 "a"), ({…}, .1 "b"), ({…}, .1 "c")]
let setView = Group {
ForEach(Array(set.enumerated()), id: \.offset) { offset, element in
PrintView("offset: \(offset), element: \(element)")
// Text("value: \(set[offset])") // ? Syntax error: Cannot convert value of type 'Int' to expected argument type 'Set<String>.Index'
}
// offset: 0, element: a
// offset: 1, element: b
// offset: 2, element: c
ForEach(Array(zip(set.indices, set)), id: \.0) { index, element in
PrintView("index: \(index), element: \(element)")
Text("value: \(set[index])")
}
// index: Index(_variant: Swift.Set<Swift.String>.Index._Variant.native(Swift._HashTable.Index(bucket: Swift._HashTable.Bucket(offset: 0), age: -481854246))), element: a
// index: Index(_variant: Swift.Set<Swift.String>.Index._Variant.native(Swift._HashTable.Index(bucket: Swift._HashTable.Bucket(offset: 2), age: -481854246))), element: b
// index: Index(_variant: Swift.Set<Swift.String>.Index._Variant.native(Swift._HashTable.Index(bucket: Swift._HashTable.Bucket(offset: 3), age: -481854246))), element: c
}
// MARK: -
struct PrintView: View {
init(_ string: String) {
print(string)
self.string = string
}
var string: String
var body: some View {
Text(string)
}
}
let allViews = Group {
arrayView
arraySliceView
setView
}
PlaygroundPage.current.setLiveView(allViews)
Run Code Online (Sandbox Code Playgroud)
Gil*_*man 29
当你枚举这个集合时,枚举中的每个元素都是一个类型的元组:
(offset: Int, element: Int)
Run Code Online (Sandbox Code Playgroud)
所以 id 参数应该从 更改id: \.self为id: \.element。
ForEach(items.enumerated(), id: \.element) { ...
Run Code Online (Sandbox Code Playgroud)
但是,在此更改后,您仍然会收到错误:
在“ForEach”上引用初始值设定项“init(_:id:content:)”要求“EnumeratedSequence<[Int]>”符合“RandomAccessCollection”
因为ForEach需要随机访问数据,但枚举只允许按顺序访问。要解决此问题,请将枚举转换为数组。
ForEach(Array(items.enumerated()), id: \.element) { ...
Run Code Online (Sandbox Code Playgroud)
这是一个扩展程序,您可以使用它来简化此操作:
extension Collection {
func enumeratedArray() -> Array<(offset: Int, element: Self.Element)> {
return Array(self.enumerated())
}
}
Run Code Online (Sandbox Code Playgroud)
以及一个可以在(macos)Xcode游乐场中运行的示例:
import AppKit
import PlaygroundSupport
import SwiftUI
extension Collection {
func enumeratedArray() -> Array<(offset: Int, element: Self.Element)> {
return Array(self.enumerated())
}
}
struct App: View {
let items = 100...200
var body: some View {
List {
ForEach(items.enumeratedArray(), id: \.element) { index, item in
Text("\(index): Item \(item)")
}
}.frame(width: 200, height: 200)
}
}
PlaygroundPage.current.liveView = NSHostingView(rootView: App())
Run Code Online (Sandbox Code Playgroud)
zdr*_*kin 15
Apple SwiftUI 示例之一 enumerated() 正在使用 inside Array,然后您可以添加偏移量作为 id,这在枚举 Array 时是唯一的。
ForEach(Array(data.enumerated()), id: \.offset) { index, observation in
Run Code Online (Sandbox Code Playgroud)
在大多数情况下,您不需要enumerate它,因为它有点慢。
struct App: View {
let items = Array(100...200)
var body: some View {
List {
ForEach(items.indices, id: \.self) { index in
Text("Item \(self.items[index])")
}
}.id(items).frame(width: 200, height: 200)
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
13159 次 |
| 最近记录: |