类型“unknown”上不存在属性“foreach”_【译】SwiftUI中使用ForEach枚举列表

首发: cc log

译自: Enumerating elements in ForEach


假设我们使用SwiftUIlist展示数组中的内容,我们使用ForEach。

struct PeopleList:View {
    var people: [Person]

    var body: some View {
        List {
            ForEach(people) { person in
                Text(person.name)
            }
        }
    }
}

57274b577545876ff4d5c42f6eb80c52.png

Person是遵循Identifiable的结构体:

struct Person: Identifiable {
    var id: UUID = UUID()
    var name: String
}

FrEach使用Identifiable来确保数组改变的时,知晓要插入的元素,或者删除的元素,以此来实施正确的动画。

ForEach中元素的序号

如果我们需要像下图一样,需要展示数组的序号。

e4413e791e3bc55a14961f10d4870e11.png

有两个方案:

  • 使用enumerated(),为每个元素提供一个(offset: Int, element: element)的元组。
  • 另外,使用zip(1…, people),提供一样的元组, 但是我们定制起始数子,而不是固定为0。

我更喜欢zip:

ForEach(zip(1…, people)) { number, person in
  Text("(number). (person.name)")
}

但是这样不会编译通过

d53b06c1d03242c16626ad7bbdaab88a.png
  1. 传给ForEach必须是一个randomaccesscollection,但是zip产出的是一个Sequence。 我们通过zip过的序列转换回数组来fix。
  2. 序列的元素类型,(Int, Person)不遵循Identifiable的协议——这个没法修复,元组不能遵循协议。

因此我们需要使用href="https://zhuanlan.zhihu.com/p/105491886/[https://developer.apple.com/documentation/swiftui/foreach/3364100-init">另外的ForEach初始化方法init(_:id:content:) 来使用key path显示的指定元素的ID。这里例子中是.1.id,其中.1表示元组中第二个元素person,.id自然是person的唯一值key。

如此才能正常工作:

ForEach(Array(zip(1…, people)), id: .1.id) { number, person in
  Text(“(number). (person.name)")
}

让代码更易读易用(Clarity at the point of use)

上述的代码明显不太易读,特别是.1Array(…)的部分,为了增加易读性(Clarity at the point of use), 我们可以再进一步。

可以给Sequence增加一个扩展:

extension Sequence {
  /// Numbers the elements in `self`, starting with the specified number.
  /// - Returns: An array of (Int, Element) pairs.
  func numbered(startingAt start: Int = 1) -> [(number: Int, element: Element)] {
    Array(zip(start..., self))
  }
}

这样用,会让代码清晰一点

ForEach(people.numbered(), id: .element.id) { number, person in
  Text(“(number). (person.name)”)
}

不使用key path

元组不能遵循协议Identifiable,因此key path不能丢弃,但是我们可以能够动态查找成员的结构体来抽离。

@dynamicMemberLookup
struct Numbered<Element> {
  var number: Int
  var element: Element

  subscript<T>(dynamicMember keyPath: WritableKeyPath<Element, T>) -> T {
    get { element[keyPath: keyPath] }
    set { element[keyPath: keyPath] = newValue }
  }
}

根据Min Kim的建议,这里我们使用基于key path动态查找下标器, 然后改变numbered(startingAt:)方法:

extension Sequence {
  func numbered(startingAt start: Int = 1) -> [Numbered<Element>] {
    zip(start..., self)
      .map { Numbered(number: $0.0, element: $0.1) }
  }
}

然后,当element能够Identifiable时候,Numbered自然也能够遵循Identifiable协议。

extension Numbered: Identifiable where Element: Identifiable {
  var id: Element.ID { element.id }
}

这样我们就能在使用ForEach的时候省略key path了。

ForEach(people.numbered()) { numberedPerson in
  Text(“(numberedPerson.number). (numberedPerson.name)”)
}

上述numberedPerson类型是Numbered<Person>,但是行为却很像Person, 只是多了number的属性, 如果没有动态属性查找dynamicMemberLookup,我们只能这样numberedPerson.element.name获取了,这样只能获取属性,对方法不行。


在这里,不得不惊叹Swift的精巧与强大,或者说,把事情搞复杂了?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值