Jak mówi Martin , jeśli spojrzysz na dokumentację dla VStack
's init(alignment:spacing:content:)
, zobaczysz, że content:
parametr ma atrybut @ViewBuilder
:
init(alignment: HorizontalAlignment = .center, spacing: Length? = nil,
@ViewBuilder content: () -> Content)
Ten atrybut odnosi się do ViewBuilder
typu, który patrząc na wygenerowany interfejs wygląda następująco:
@_functionBuilder public struct ViewBuilder {
public static func buildBlock() -> EmptyView
public static func buildBlock(_ content: Content) -> Content
where Content : View
}
@_functionBuilder
Atrybut jest częścią nieoficjalną funkcję o nazwie „ budowniczych funkcyjne ”, który został dwuspadowym o Swift ewolucji tutaj i realizowane specjalnie dla wersji Swift że statki z Xcode 11, co pozwala na stosowanie go w SwiftUI.
Oznaczenie typu @_functionBuilder
pozwala na użycie go jako atrybutu niestandardowego w różnych deklaracjach, takich jak funkcje, obliczone właściwości i, w tym przypadku, parametry typu funkcji. Takie deklaracje z adnotacjami używają konstruktora funkcji do przekształcania bloków kodu:
- W przypadku funkcji z adnotacjami implementacją jest blok kodu, który jest przekształcany.
- W przypadku obliczonych właściwości z adnotacjami blok kodu, który jest transformowany, jest funkcją pobierającą.
- W przypadku parametrów typu funkcji z adnotacjami blok kodu, który jest transformowany, to każde przekazane do niego wyrażenie zamykające (jeśli istnieje).
Sposób, w jaki konstruktor funkcji przekształca kod, jest definiowany przez implementację metod konstruktora, takich jak buildBlock
, która pobiera zestaw wyrażeń i konsoliduje je w jedną wartość.
Na przykład ViewBuilder
implementuje buildBlock
od 1 do 10 View
zgodnych parametrów, konsolidując wiele widoków w jeden 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
}
Pozwala to VStack
na przekształcenie zestawu wyrażeń widoku w zamknięciu przekazanym do inicjalizatora w wywołanie, buildBlock
które pobiera taką samą liczbę argumentów. Na przykład:
struct ContentView : View {
var body: some View {
VStack(alignment: .leading) {
Text("Hello, World")
Text("Hello World!")
}
}
}
zmienia się w połączenie z buildBlock(_:_:)
:
struct ContentView : View {
var body: some View {
VStack(alignment: .leading) {
ViewBuilder.buildBlock(Text("Hello, World"), Text("Hello World!"))
}
}
}
w wyniku czego nieprzezroczysty typ wyniku some View
zostanie spełniony przez TupleView<(Text, Text)>
.
Zauważysz, że ViewBuilder
definiuje tylko buildBlock
do 10 parametrów, więc jeśli spróbujemy zdefiniować 11 podglądów podrzędnych:
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!")
}
}
otrzymujemy błąd kompilatora, ponieważ nie ma metody konstruktora do obsługi tego bloku kodu (zwróć uwagę, że ponieważ ta funkcja jest nadal w toku, komunikaty o błędach wokół niej nie będą tak pomocne).
W rzeczywistości nie sądzę, aby ludzie często napotykali na to ograniczenie, na przykład powyższy przykład byłby lepszy przy użyciu ForEach
widoku:
var body: some View {
VStack(alignment: .leading) {
ForEach(0 ..< 20) { i in
Text("Hello world \(i)")
}
}
}
Jeśli jednak potrzebujesz więcej niż 10 statycznie zdefiniowanych widoków, możesz łatwo obejść to ograniczenie za pomocą Group
widoku:
var body: some View {
VStack(alignment: .leading) {
Group {
Text("Hello world")
}
Group {
Text("Hello world")
}
}
ViewBuilder
implementuje również inne metody konstruktora funkcji, takie jak:
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
}
Daje to możliwość obsługi instrukcji if:
var body: some View {
VStack(alignment: .leading) {
if .random() {
Text("Hello World!")
} else {
Text("Goodbye World!")
}
Text("Something else")
}
}
który zostaje przekształcony w:
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")
)
}
}
(emitowanie zbędnych 1-argumentowych wezwań do ViewBuilder.buildBlock
jasności).
@ViewBuilder
developer.apple.com/documentation/swiftui/viewbuilder .