gpt4 book ai didi

swift - 在SwiftUI中,如何在ForEach中使用.enumerated()?

转载 作者:行者123 更新时间:2023-12-05 00:11:50 32 4
gpt4 key购买 nike

这是一个可以按预期工作的简单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)
}
}
但是当我尝试通过将 items替换为 items.enumerated()来枚举项目时,出现以下错误:

Referencing initializer 'init(_:id:content:)' on 'ForEach' requires that '(offset: Int, element: Int)' conform to 'Hashable'

Referencing initializer 'init(_:id:content:)' on 'ForEach' requires that 'EnumeratedSequence<[Int]>' conform to 'RandomAccessCollection'


我该如何工作?

最佳答案

TL; DR
警告:如果您习惯于将enumerated()ForEach一起使用,则有一天您可能会遇到EXC_BAD_INSTRUCTIONFatal 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
}
(如果您的商品符合 id: \.1,则也可以使用 Identifiable。)

Point-Free mentioned that it's not safe to rely on enumerated() with ForEach in production since not all collections are zero-index based上的人们:

This is technically not the most correct way to do this. It would be more correct, and more verbose, to zip the todos array with its indices collection. In this case we are safe because we are dealing with a simple 0-based index array, but if we were doing this in production we should probably zip-based approach.


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)
在特定情况下,由于您使用的是基于0的索引数组,因此 enumerated()可以很好地使用,但是由于上面的详细信息,始终依赖 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])
}
}
首先要注意的是,我们用 Button(item...躲过了一个项目符号,因为 enumerated()保证了 item可以直接访问而不会引起异常。但是,如果我们使用 item代替 items[offset],则很容易引发异常。
最后,由于索引(实际上是偏移量)可能超出范围,所以 print(items[index])行很容易导致异常。
因此,一种更安全的方法是始终使用本文顶部提到的 zip方法。
偏好 zip的另一个原因是,如果您尝试将相同的代码用于不同的Collection(例如Set),则在索引到类型( items[index])时可能会遇到以下语法错误:

Cannot convert value of type 'Int' to expected argument type 'Set.Index'


通过使用基于 zip的方法,您仍然可以索引到集合中。
如果您打算经常使用它,也可以 create an extension on collection

您可以在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)

关于swift - 在SwiftUI中,如何在ForEach中使用.enumerated()?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59295206/

32 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com