, Поскольку Martin говорит , если Вы смотрите на документацию для [1 135] 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 здесь и реализовал особенно для версии Swift, который поставлется с Xcode 11, позволяя этому использоваться в SwiftUI.
отмечание типа @_functionBuilder
позволяет этому использоваться в качестве пользовательского атрибута на различных объявлениях, таких как функции, вычисленные свойства и, в этом случае, параметры функционального типа. Такие аннотируемые объявления используют функционального разработчика для преобразования блоков кода:
путь, в который функциональный разработчик преобразовывает код, определяется его реализацией [1 140] методы разработчика такой как [1 118], который берет ряд выражений и консолидирует их в единственное значение.
, Например, 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
// ...
}
Это позволяет ряду выражений представления в рамках закрытия, переданного [1 123] инициализатор быть преобразованным в вызов к [1 124], который берет то же количество аргументов. Например:
struct ContentView : View {
var body: some View {
VStack(alignment: .leading) {
Text("Hello, World")
Text("Hello World!")
}
}
}
преобразовывается в вызов к [1 125]:
struct ContentView : View {
var body: some View {
VStack(alignment: .leading) {
ViewBuilder.buildBlock(Text("Hello, World"), Text("Hello World!"))
}
}
}
получающийся в непрозрачный тип some View
результата, удовлетворяемый [1 127].
Вы отметите, что 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
}
Это дает ему способность обработать если операторы:
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 аргумента звонит в [1 133] для ясности).
Аналогичная вещь описана в Новые функции и возможности в Быстром видео WWDC в разделе о DSLs (запускается в ~31:15). Атрибут интерпретируется компилятором и переводится в связанный код: