Як каже Мартін , якщо ви подивитесь на документацію до 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")
)
}
}
(випромінювання зайвих 1-аргументних дзвінків ViewBuilder.buildBlock
для ясності).
@ViewBuilder
developer.apple.com/documentation/swiftui/viewbuilder .