SequenceBuilder 是一个通用的函数构建器 / 结果构建器,适用于 Swift。 它允许您构建任意的异构序列,而不会丢失有关底层类型的信息。
如果您曾经遇到过错误消息 Protocol 'X' can only be used as a generic constraint because it has Self or associated type requirements
,那么 SequenceBuilder 可能会对您有所帮助。 例如:
protocol Key {
associatedtype Value
var value: Value { get }
}
struct Foo {
let keys: [Key]
// ❌ Protocol 'Key' can only be used as a generic constraint because it has Self or associated type requirements
}
使用 SequenceBuilder,您可以拥有具有关联类型要求的类型序列。
import SequenceBuilder
struct Foo<S: Sequence> where S.Element: Key {
let keys: S
init(@SequenceBuilder builder: () -> S) {
keys = builder()
}
}
为此,您必须通过扩展 Either
类型来定义如何处理异构的 Value
。
extension Either: Key where Left: Key, Right: Key {
var value: Either<Left.Value, Right.Value> {
bimap(left: \.value, right: \.value)
}
}
然后,您可以像这样使用 Foo
:
struct StringKey: Key { let value: String }
struct IntKey: Key { let value: Int }
struct FloatKey: Key { let value: Float }
let foo = Foo {
StringKey(value: "-")
IntKey(value: 42)
FloatKey(value: 3.14)
}
// and later:
let description = foo.keys
.map(\.value.description)
.joined(separator: ", ")
// description is: "-, 42, 3.14"
此技术可用于任何类型的异构序列,但它对于在 SwiftUI 中构建自定义容器视图特别有用。 对于视图,Either
类型已被扩展。
您可以像在 SwiftUI 中使用 @ViewBuilder
属性一样使用 @SequenceBuilder
。 因此,您不仅会得到一个视图,而且会得到一系列单独的视图,而无需求助于 AnyView
。 SequenceBuilder 与 ViewBuilder 具有相同的限制,即目前仅支持 10 个元素,无需嵌套。
作为一个简单的例子,此视图将其子视图包装在一个类似于有序 HTML 列表的枚举中 (完整示例)。 通过约束序列元素 (… where Content.Element: View
),我们可以存储和迭代任意视图的序列。
import SequenceBuilder
import SwiftUI
struct EnumerationView<Content: Sequence>: View where Content.Element: View {
let content: Content
init(@SequenceBuilder builder: () -> Content) {
self.content = builder()
}
var body: some View {
VStack(alignment: .leading, spacing: 8) {
ForEach(sequence: content) { (index, content) in
HStack(alignment: .top) {
Text("\(index + 1). ")
content
}
}
}
}
}
// Usage:
EnumerationView {
Text("Some text")
VStack {
ForEach(0..<10, id: \.self) { _ in
Text("Lorem ipsum dolet.")
}
}
if true {
Text("More text")
Text("Enough text")
}
HStack {
Text("With image:")
Image(systemName: "checkmark")
}
Text("The ending")
}
异构序列的元素不必一定是视图。 想要了解使用 SequenceBuilder 从列构建表的更复杂示例,请参见 SwiftTableView
。
您可能想知道这是如何实现的,我鼓励您查看源代码。 它非常小。
序列的元素类型为 Either
。
public enum Either<Left, Right> {
case left(Left)
case right(Right)
}
对于序列中的每个附加元素,另一个 Either
会嵌套在其他元素中。 例如:
Text, Image
变为 [Either<Text, Image>]
Text, Image, Text
变为 [Either<Text, Either<Image, Text>>]
Text, Image, Text, Button
变为 [Either<Text, Either<Image, Either<Text, Button>>>]
这种类型的增长类似于你在 SwiftUI 中得到的那种。 它似乎工作正常,但可能不适合非常大的集合。
如果您只想要一个视图集合,则不必关心此实现细节,因为如果 Left
和 Right
也符合 View
,则 Either
符合 View
。
但是,如果您想使用自己的带有或不带有关联类型的协议,则必须自己扩展 Either
。
在 Xcode 中,选择菜单 File > Swift Packages > Add Package Dependency...
并输入存储库 URL。
Repository: "https://github.com/andtie/SequenceBuilder"