Как говорит Мартин , если вы посмотрите документацию для VStack's init(alignment:spacing:content:), вы увидите, что content:параметр имеет атрибут @ViewBuilder:
init(alignment: HorizontalAlignment = .center, spacing: Length? = nil,
@ViewBuilder content: () -> Content)
Этот атрибут относится к ViewBuilderтипу, который, если вы посмотрите на сгенерированный интерфейс, выглядит так:
@_functionBuilder public struct ViewBuilder {
public static func buildBlock() -> EmptyView
public static func buildBlock(_ content: Content) -> Content
where Content : View
}
@_functionBuilderАтрибут является частью неофициальной функции , называемой « функции строителей », которая была станом на эволюцию Swift здесь и реализованного специально для версии 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 {
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 {
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")
}
Group {
Text("Hello world")
}
}
ViewBuilder также реализует другие методы построения функций, такие как:
extension ViewBuilder {
public static func buildEither<TrueContent, FalseContent>(first: TrueContent)
-> ConditionalContent<TrueContent, FalseContent>
where TrueContent : View, FalseContent : View
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")
)
}
}
(выдача избыточных вызовов с ViewBuilder.buildBlockодним аргументом для ясности).
@ViewBuilderdeveloper.apple.com/documentation/swiftui/viewbuilder .