Ответ 1
Как говорит Мартин, если вы посмотрите на документацию по VStack
init(alignment:spacing:content:)
, вы увидите, что у параметра content:
есть атрибут @ViewBuilder
:
init(alignment: HorizontalAlignment = .center, spacing: Length? = nil,
@ViewBuilder content: () -> Content)
Этот атрибут относится к типу ViewBuilder
, который, если вы посмотрите на сгенерированный интерфейс, выглядит следующим образом:
@_functionBuilder 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
}
Это дает ему возможность обрабатывать операторы if:
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
для ясности).