如何为每个数组元素生成绑定 [英] How to generate a binding for each array element

查看:28
本文介绍了如何为每个数组元素生成绑定的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如果我有一个带有数组属性的 @State@ObservedObject 变量,并且我想使用 List 并传递一个绑定将数组的每个元素转换为某个子视图(例如 ToggleTextField),是否有标准的方法来做到这一点?

If I had a @State or an @ObservedObject variable with an array property, and I wanted to use List and pass a binding of each element of the array into some child View (e.g. Toggle or TextField), is there a standard way to do that?

简化示例:

struct Person: Identifiable {
  var id: UUID = .init()
  var name: String
  var isFavorite: Bool = false
}

struct ContentView: View {
  @State var people = [Person(name: "Joey"), Person(name: "Chandler")]

  var body: some View {
     List(people) { person in
        HStack() {
           Text(person.name) 
           Spacer
           Toggle("", isOn: $person.isFavorite) // <- this obviously doesn't work
        }
     }
  }
}

这似乎是一个相当普遍的场景,但除了手动构建一个单独的绑定数组之外,我想不出一个明显的解决方案.

This seems like a fairly common scenario, but I can't figure out an obvious solution aside from manually building a separate array of bindings.

我想出的唯一优雅的解决方案(如果没有更好的方法,我会将其添加为答案)是创建 RandomAccessCollectionBinding 的扩展/code> 本身符合 RandomAccessCollection,它具有作为元素的绑定,如下所示:

The only elegant solution I came up with (I'll add it as an answer, if there isn't something better) was to create an extension of Binding of a RandomAccessCollection to itself conform to a RandomAccessCollection, which has bindings as elements, like so:

extension Binding: RandomAccessCollection 
  where Value: RandomAccessCollection & MutableCollection {
  // more code here
}

  // more required extensions to Collection and Sequence here

推荐答案

UPDATE

iOS13 发行说明(弃用部分) 中,SwiftUI 放弃了一致性BindingCollection,而是提供了一个解决方法,所以我用他们的建议更新了这个答案.

In iOS13 release notes (deprecation section), SwiftUI dropped the conformance of Binding to Collection, and instead offered a workaround, so I'm updating this answer with their suggestion.

这个想法是扩展 RandomAccessCollection 以添加一个 .index() 方法,它的工作原理类似于 .enumerated() 通过创建一个索引和元素元组的集合,但与 .enumerated() 不同的是,它符合 RandomAccessCollection,其中 ListForEach> 需要.

The idea is to extend RandomAccessCollection to add a .index() method, which works similarly to .enumerated() by creating a collection of tuples of index and element, but unlike .enumerated() conforms to a RandomAccessCollection, which List and ForEach require.

用法是:

List(people.indexed(), id: \.1.id) { (i, person) in
   HStack() {
      Toggle(person.name, isOn: $people[i].isFavorite)
   }

.indexed()的实现是:

struct IndexedCollection<Base: RandomAccessCollection>: RandomAccessCollection {
    typealias Index = Base.Index
    typealias Element = (index: Index, element: Base.Element)

    let base: Base

    var startIndex: Index { base.startIndex }

    var endIndex: Index { base.startIndex }

    func index(after i: Index) -> Index {
        base.index(after: i)
    }

    func index(before i: Index) -> Index {
        base.index(before: i)
    }

    func index(_ i: Index, offsetBy distance: Int) -> Index {
        base.index(i, offsetBy: distance)
    }

    subscript(position: Index) -> Element {
        (index: position, element: base[position])
    }
}

extension RandomAccessCollection {
    func indexed() -> IndexedCollection<Self> {
        IndexedCollection(base: self)
    }
}

<小时>

原件这是我想要实现的目标:


ORIGINAL Here's what I wanted to achieve:

List($people) { personBinding in 
  HStack() {
      Text(personBinding.wrappedValue.name) 
      Spacer()
      Toggle("", isOn: personBinding.isFavorite)
  }
}

也就是说,传递一个数组的绑定,得到List的闭包中一个元素的绑定.

In other words, pass the binding of an array, and get a binding of an element in List's closure.

为了实现这一点,我创建了一个 Binding 的扩展,它使任何 RandomAccessCollectionBinding 成为一个 RandomAccessCollection> 绑定:

To achieve that, I created an extension of Binding that makes a Binding of any RandomAccessCollection into a RandomAccessCollection of bindings:

// For all Bindings whose Value is a collection
extension Binding: RandomAccessCollection 
    where Value: RandomAccessCollection & MutableCollection {

  // The Element of this collection is Binding of underlying Value.Element 
  public typealias Element = Binding<Value.Element>
  public typealias Index = Value.Index
  public typealias SubSequence = Self
  public typealias Indices = Value.Indices

  // return a binding to the underlying collection element
  public subscript(position: Index) -> Element {
    get {
      .init(get: { self.wrappedValue[position] },
            set: { self.wrappedValue[position] = $0 })
    }
  }

  // other protocol conformance requirements routed to underlying collection ...

  public func index(before i: Index) -> Index {      
     self.wrappedValue.index(before: i)
  }

  public func index(after i: Index) -> Index {
     self.wrappedValue.index(after: i)
  }

  public var startIndex: Index {
     self.wrappedValue.startIndex
  }

  public var endIndex: Index {
     self.wrappedValue.endIndex
  }
}

这也需要明确遵守继承的协议:

This also requires explicit conformance to inherited protocols:

extension Binding: Sequence 
    where Value: RandomAccessCollection & MutableCollection {

  public func makeIterator() -> IndexingIterator<Self> {
    IndexingIterator(_elements: self)
  }
}

extension Binding: Collection 
    where Value: RandomAccessCollection & MutableCollection {

  public var indices: Value.Indices {
    self.wrappedValue.indices
  }
}

extension Binding: BidirectionalCollection 
    where Value: RandomAccessCollection & MutableCollection { 
}

而且,如果底层值是一个Identifiable,那么它也使绑定符合Identifiable,这就不需要使用id:代码>:

And, if the underlying value is an Identifiable, then it makes the Binding conform to Identifiable too, which removes the need to use id::

extension Binding: Identifiable where Value: Identifiable {
  public var id: Value.ID {
    self.wrappedValue.id
  }
}

这篇关于如何为每个数组元素生成绑定的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆