如果我有一个@State
或一个@ObservedObject
具有数组属性的变量,并且我想使用List
数组的每个元素的绑定并将其传递到某个子视图(例如Toggle
或TextField
),是否有标准方法可以做到这一点?
简化示例:
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
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
这似乎是一个相当常见的场景,但除了手动构建单独的绑定数组之外,我无法找出明显的解决方案。
我想出的唯一优雅的解决方案(如果没有更好的东西,我会将其添加为答案)是创建Binding
a的扩展,它RandomAccessCollection
本身符合 a RandomAccessCollection
,它具有绑定作为元素,如下所示:
extension Binding: RandomAccessCollection
where Value: RandomAccessCollection & MutableCollection {
// more code here
}
// more required extensions to Collection and Sequence here
Run Code Online (Sandbox Code Playgroud)
更新
在iOS13 发行说明(弃用部分)Binding
中,SwiftUI 放弃了to的一致性Collection
,而是提供了一种解决方法,因此我根据他们的建议更新了这个答案。
这个想法是扩展RandomAccessCollection
以添加一个.index()
方法,该方法的工作原理类似于.enumerated()
创建索引和元素元组的集合,但与符合.enumerated()
a RandomAccessCollection
、whichList
和ForEach
require 不同。
用法是:
List(people.indexed(), id: \.1.id) { (i, person) in
HStack() {
Toggle(person.name, isOn: $people[i].isFavorite)
}
Run Code Online (Sandbox Code Playgroud)
其实现.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)
}
}
Run Code Online (Sandbox Code Playgroud)
原创 这是我想要实现的目标:
List($people) { personBinding in
HStack() {
Text(personBinding.wrappedValue.name)
Spacer()
Toggle("", isOn: personBinding.isFavorite)
}
}
Run Code Online (Sandbox Code Playgroud)
换句话说,传递数组的绑定,并在 的List
闭包中获取元素的绑定。
为了实现这一点,我创建了一个扩展,Binding
使 a Binding
of anyRandomAccessCollection
变成 a RandomAccessCollection
of 绑定:
// 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
}
}
Run Code Online (Sandbox Code Playgroud)
这还需要明确遵守继承的协议:
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 {
}
Run Code Online (Sandbox Code Playgroud)
而且,如果基础值是 an Identifiable
,那么它也会使 Binding 符合Identifiable
,从而无需使用id:
:
extension Binding: Identifiable where Value: Identifiable {
public var id: Value.ID {
self.wrappedValue.id
}
}
Run Code Online (Sandbox Code Playgroud)