Как говорит Мартин , если вы посмотрите документацию для 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
одним аргументом для ясности).
@ViewBuilder
developer.apple.com/documentation/swiftui/viewbuilder .