SequenceBuilder

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

此技术可用于任何类型的异构序列,但它对于在 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")
}

enumeration view example

异构序列的元素不必一定是视图。 想要了解使用 SequenceBuilder 从列构建表的更复杂示例,请参见 SwiftTableView

这是什么魔法?

您可能想知道这是如何实现的,我鼓励您查看源代码。 它非常小。

序列的元素类型为 Either

public enum Either<Left, Right> {
    case left(Left)
    case right(Right)
}

对于序列中的每个附加元素,另一个 Either 会嵌套在其他元素中。 例如:

这种类型的增长类似于你在 SwiftUI 中得到的那种。 它似乎工作正常,但可能不适合非常大的集合。

如果您只想要一个视图集合,则不必关心此实现细节,因为如果 LeftRight 也符合 View,则 Either 符合 View
但是,如果您想使用自己的带有或不带有关联类型的协议,则必须自己扩展 Either

安装

Swift Package Manager

在 Xcode 中,选择菜单 File > Swift Packages > Add Package Dependency... 并输入存储库 URL。

Repository: "https://github.com/andtie/SequenceBuilder"