swift - 如何为每个数组元素生成绑定(bind)

标签 swift swiftui

如果我有一个@State@ObservedObject具有数组属性的变量,我想使用 List并将数组的每个元素的绑定(bind)传递到某个 subview (例如 ToggleTextField ),是否有标准方法可以做到这一点?

简化示例:

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
        }
     }
  }
}

这似乎是一个相当常见的场景,但除了手动构建单独的绑定(bind)数组之外,我找不到明显的解决方案。

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

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

  // more required extensions to Collection and Sequence here

最佳答案

更新

iOS13 release notes (deprecation section) ,SwiftUI 放弃了 Binding 的一致性至Collection ,并提供了一种解决方法,因此我将根据他们的建议更新此答案。

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

用法是:

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)
    }
}

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

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

也就是说,传递一个数组的绑定(bind),得到List中某个元素的绑定(bind)的关闭。

为了实现这一目标,我创建了 Binding 的扩展这使得 Binding任何RandomAccessCollection进入RandomAccessCollection绑定(bind)数量:

// 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
  }
}

这还需要明确遵守继承的协议(protocol):

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 ,那么它使 Binding 符合 Identifiable也不需要使用 id: :

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

关于swift - 如何为每个数组元素生成绑定(bind),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62103647/

相关文章:

ios - 使用 Image 加载 1000 条记录到 Collection View CloudKit Swift

ios - 创建一个具有前导/尾随褪色边缘的 UIView

ios - 如何确定当前在 SwiftUI 中是否选择了 View

swift - Swift(UI) 中的 "some"关键字是什么?

swift 4 : Cannot use instance member 'MAIN_URL' within property initializer when trying to concatenate two strings

swift - 获取由不同类的代码创建的 UI 元素

swift - 如何使三次样条插值算法更快?

swiftui - SwiftUI中的ForEach问题

ios - 如何在SwiftUI的底部创建日期选择器?

swiftui - 如何在 VisionOS 的 SwiftUI 应用程序中创建多个窗口?