Как говорит Мартин , если вы посмотрите на документацию для VStack
init(alignment:spacing:content:)
, вы увидите, что параметр content:
имеет атрибут @ViewBuilder
:
init(alignment: HorizontalAlignment = .center, spacing: Length? = nil,
<b>@ViewBuilder</b> content: () -> Content)
Этот атрибут относится к типу ViewBuilder
, который, если вы посмотрите на сгенерированный интерфейс, будет выглядеть так:
<b>@_functionBuilder</b> public struct ViewBuilder {
/// Builds an empty view from an block containing no statements, `{ }`.
public static func buildBlock() -> EmptyView
/// Passes a single view written as a child view (e..g, `{ Text("Hello") }`)
/// through unmodified.
public static func buildBlock(_ content: Content) -> Content
where Content : View
}
Атрибут @_functionBuilder
является частью неофициальной функции, называемой " строители функций ", которая была представлена на Swift evolution и реализована специально для версии Swiftкоторый поставляется с Xcode 11, что позволяет использовать его в SwiftUI.
Маркировка типа @_functionBuilder
позволяет использовать его в качестве пользовательского атрибута в различных объявлениях, таких как функции, вычисляемые свойства и, в этом случае, параметры типа функции.Такие аннотированные объявления используют построитель функций для преобразования блоков кода:
- Для аннотированных функций преобразуемый блок кода является реализацией.
- Для аннотированных вычисляемых свойств блокпреобразуемого кода является получателем.
- Для аннотированных параметров типа функции преобразуемым блоком кода является любое переданное ему закрывающее выражение (если оно есть).
Способ, которым построитель функций преобразует код, определяется его реализацией методов построителя , таких как buildBlock
, который принимает набор выражений и объединяет их в одно значение.
Например, ViewBuilder
реализует buildBlock
для параметров от 1 до 10 View
, объединяя несколько представлений в одно TupleView
:
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension ViewBuilder {
/// Passes a single view written as a child view (e..g, `{ Text("Hello") }`)
/// through unmodified.
public static func buildBlock<Content>(_ content: Content)
-> Content where Content : View
public static func buildBlock<C0, C1>(_ c0: C0, _ c1: C1)
-> TupleView<(C0, C1)> where C0 : View, C1 : View
public static func buildBlock<C0, C1, C2>(_ c0: C0, _ c1: C1, _ c2: C2)
-> TupleView<(C0, C1, C2)> where C0 : View, C1 : View, C2 : View
// ...
}
Это позволяет использовать набор выражений представлений в замыканиипередается инициализатору VStack
для преобразования в вызов buildBlock
, который принимает то же количество аргументов.Например:
struct ContentView : View {
var body: some View {
VStack(alignment: .leading) {
Text("Hello, World")
Text("Hello World!")
}
}
}
преобразуется в вызов buildBlock(_:_:)
:
struct ContentView : View {
var body: some View {
VStack(alignment: .leading) {
ViewBuilder.buildBlock(Text("Hello, World"), Text("Hello World!"))
}
}
}
, в результате чего непрозрачный тип результата some View
удовлетворяется TupleView<(Text, Text)>
.
Вы заметите, что ViewBuilder
определяет только buildBlock
до 10 параметров, поэтому, если мы попытаемся определить 11 подпредставлений:
var body: some View {
// error: Static member 'leading' cannot be used on instance of
// type 'HorizontalAlignment'
VStack(alignment: .leading) {
Text("Hello, World")
Text("Hello World!")
Text("Hello World!")
Text("Hello World!")
Text("Hello World!")
Text("Hello World!")
Text("Hello World!")
Text("Hello World!")
Text("Hello World!")
Text("Hello World!")
Text("Hello World!")
}
}
, мы получим ошибку компилятора, поскольку нет метода конструктора для обработки этого блока кода (обратите внимание, что, поскольку эта функция все еще находится в стадии разработки, сообщения об ошибках вокруг нее не будут такими полезными).
На самом деле, яне верьте, что люди будут часто сталкиваться с этим ограничением, например, вышеприведенный пример лучше использовать, используя вместо этого представление ForEach
:
var body: some View {
VStack(alignment: .leading) {
ForEach(0 ..< 20) { i in
Text("Hello world \(i)")
}
}
}
Если вы все же это сделаетеЕсли вам нужно более 10 статически определенных представлений, вы можете легко обойти это ограничение, используя представление Group
:
var body: some View {
VStack(alignment: .leading) {
Group {
Text("Hello world")
// ...
// up to 10 views
}
Group {
Text("Hello world")
// ...
// up to 10 more views
}
// ...
}
ViewBuilder
, также реализует другие методы построителя функций, такие как:
extension ViewBuilder {
/// Provides support for "if" statements in multi-statement closures, producing
/// ConditionalContent for the "then" branch.
public static func buildEither<TrueContent, FalseContent>(first: TrueContent)
-> ConditionalContent<TrueContent, FalseContent>
where TrueContent : View, FalseContent : View
/// Provides support for "if-else" statements in multi-statement closures,
/// producing ConditionalContent for the "else" branch.
public static func buildEither<TrueContent, FalseContent>(second: FalseContent)
-> ConditionalContent<TrueContent, FalseContent>
where TrueContent : View, FalseContent : View
}
Это дает ему возможность обрабатывать, если сtatements:
var body: some View {
VStack(alignment: .leading) {
if .random() {
Text("Hello World!")
} else {
Text("Goodbye World!")
}
Text("Something else")
}
}
, который преобразуется в:
var body: some View {
VStack(alignment: .leading) {
ViewBuilder.buildBlock(
.random() ? ViewBuilder.buildEither(first: Text("Hello World!"))
: ViewBuilder.buildEither(second: Text("Goodbye World!")),
Text("Something else")
)
}
}
(для ясности генерируются избыточные вызовы с 1 аргументом ViewBuilder.buildBlock
).