129 lines
3.2 KiB
Swift
129 lines
3.2 KiB
Swift
// Copyright 2020 Tokamak contributors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
/// A protocol that allows matching against type-erased `ForEach` at run time.
|
|
protocol ForEachProtocol: GroupView {
|
|
var elementType: Any.Type { get }
|
|
func element(at: Int) -> Any
|
|
}
|
|
|
|
/// A structure that computes `View`s from a collection of identified data.
|
|
///
|
|
/// Available when `Data` conforms to `RandomAccessCollection`,
|
|
/// `ID` conforms to `Hashable`, and `Content` conforms to `View`.
|
|
///
|
|
/// The children computed by `ForEach` are directly passed to the encapsulating `View`.
|
|
/// Similar to `TupleView` and `Group`.
|
|
///
|
|
/// HStack {
|
|
/// ForEach(0..<5) {
|
|
/// Text("\($0)")
|
|
/// }
|
|
/// }
|
|
public struct ForEach<Data, ID, Content>: View where Data: RandomAccessCollection, ID: Hashable,
|
|
Content: View
|
|
{
|
|
let data: Data
|
|
let id: KeyPath<Data.Element, ID>
|
|
public let content: (Data.Element) -> Content
|
|
|
|
public init(
|
|
_ data: Data,
|
|
id: KeyPath<Data.Element, ID>,
|
|
@ViewBuilder content: @escaping (Data.Element) -> Content
|
|
) {
|
|
self.data = data
|
|
self.id = id
|
|
self.content = content
|
|
}
|
|
|
|
public var body: Never {
|
|
neverBody("ForEach")
|
|
}
|
|
}
|
|
|
|
extension ForEach: ForEachProtocol where Data.Index == Int {
|
|
var elementType: Any.Type { Data.Element.self }
|
|
func element(at index: Int) -> Any { data[index] }
|
|
}
|
|
|
|
public extension ForEach where Data.Element: Identifiable, ID == Data.Element.ID {
|
|
init(
|
|
_ data: Data,
|
|
@ViewBuilder content: @escaping (Data.Element) -> Content
|
|
) {
|
|
self.init(data, id: \.id, content: content)
|
|
}
|
|
}
|
|
|
|
public extension ForEach where Data == Range<Int>, ID == Int {
|
|
init(
|
|
_ data: Range<Int>,
|
|
@ViewBuilder content: @escaping (Data.Element) -> Content
|
|
) {
|
|
self.data = data
|
|
id = \.self
|
|
self.content = content
|
|
}
|
|
}
|
|
|
|
extension ForEach: ParentView {
|
|
public var children: [AnyView] {
|
|
data.map { AnyView(IDView(content($0), id: $0[keyPath: id])) }
|
|
}
|
|
}
|
|
|
|
extension ForEach: GroupView {}
|
|
|
|
struct _IDKey: EnvironmentKey {
|
|
static let defaultValue: AnyHashable? = nil
|
|
}
|
|
|
|
extension EnvironmentValues {
|
|
public var _id: AnyHashable? {
|
|
get {
|
|
self[_IDKey.self]
|
|
}
|
|
set {
|
|
self[_IDKey.self] = newValue
|
|
}
|
|
}
|
|
}
|
|
|
|
public protocol _AnyIDView {
|
|
var anyId: AnyHashable { get }
|
|
}
|
|
|
|
struct IDView<Content, ID>: View, _AnyIDView where Content: View, ID: Hashable {
|
|
let content: Content
|
|
let id: ID
|
|
var anyId: AnyHashable { AnyHashable(id) }
|
|
|
|
init(_ content: Content, id: ID) {
|
|
self.content = content
|
|
self.id = id
|
|
}
|
|
|
|
var body: some View {
|
|
content
|
|
.environment(\._id, AnyHashable(id))
|
|
}
|
|
}
|
|
|
|
extension View {
|
|
public func id<ID>(_ id: ID) -> some View where ID: Hashable {
|
|
IDView(self, id: id)
|
|
}
|
|
}
|