Come dice Martin , se guardi la documentazione per VStack
's init(alignment:spacing:content:)
, puoi vedere che il content:
parametro ha l'attributo @ViewBuilder
:
init(alignment: HorizontalAlignment = .center, spacing: Length? = nil,
@ViewBuilder content: () -> Content)
Questo attributo si riferisce al ViewBuilder
tipo, che se guardi l'interfaccia generata, ha il seguente aspetto:
@_functionBuilder public struct ViewBuilder {
public static func buildBlock() -> EmptyView
public static func buildBlock(_ content: Content) -> Content
where Content : View
}
L' @_functionBuilder
attributo fa parte di una funzionalità non ufficiale chiamata " costruttori di funzioni ", che è stata presentata qui su Swift evolution e implementata appositamente per la versione di Swift fornita con Xcode 11, permettendone l'utilizzo in SwiftUI.
Contrassegnare un tipo ne @_functionBuilder
consente l'utilizzo come attributo personalizzato su varie dichiarazioni come funzioni, proprietà calcolate e, in questo caso, parametri del tipo di funzione. Tali dichiarazioni annotate utilizzano il generatore di funzioni per trasformare blocchi di codice:
- Per le funzioni annotate, il blocco di codice che viene trasformato è l'implementazione.
- Per le proprietà calcolate annotate, il blocco di codice che viene trasformato è il getter.
- Per i parametri annotati del tipo di funzione, il blocco di codice che viene trasformato è qualsiasi espressione di chiusura che gli viene passata (se presente).
Il modo in cui un generatore di funzioni trasforma il codice è definito dalla sua implementazione di metodi di compilazione come buildBlock
, che prende un insieme di espressioni e le consolida in un unico valore.
Ad esempio, ViewBuilder
implementa buildBlock
da 1 a 10 View
parametri conformi, consolidando più viste in una singola 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
}
Ciò consente di VStack
trasformare un insieme di espressioni di visualizzazione all'interno di una chiusura passata all'inizializzatore di S in una chiamata a buildBlock
che accetta lo stesso numero di argomenti. Per esempio:
struct ContentView : View {
var body: some View {
VStack(alignment: .leading) {
Text("Hello, World")
Text("Hello World!")
}
}
}
si trasforma in una chiamata a buildBlock(_:_:)
:
struct ContentView : View {
var body: some View {
VStack(alignment: .leading) {
ViewBuilder.buildBlock(Text("Hello, World"), Text("Hello World!"))
}
}
}
con il risultato che il tipo di risultato opaco some View
viene soddisfatto da TupleView<(Text, Text)>
.
Noterai che ViewBuilder
definisce solo buildBlock
fino a 10 parametri, quindi se proviamo a definire 11 sottoview:
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!")
}
}
otteniamo un errore del compilatore, poiché non esiste un metodo di creazione per gestire questo blocco di codice (si noti che poiché questa funzione è ancora in corso, i messaggi di errore attorno ad essa non saranno così utili).
In realtà, non credo che le persone si imbatteranno in questa restrizione così spesso, ad esempio l'esempio sopra sarebbe meglio servito utilizzando ForEach
invece la vista:
var body: some View {
VStack(alignment: .leading) {
ForEach(0 ..< 20) { i in
Text("Hello world \(i)")
}
}
}
Se tuttavia hai bisogno di più di 10 viste definite staticamente, puoi facilmente aggirare questa restrizione utilizzando la Group
vista:
var body: some View {
VStack(alignment: .leading) {
Group {
Text("Hello world")
}
Group {
Text("Hello world")
}
}
ViewBuilder
implementa anche altri metodi di creazione di funzioni come:
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
}
Questo gli dà la capacità di gestire le istruzioni if:
var body: some View {
VStack(alignment: .leading) {
if .random() {
Text("Hello World!")
} else {
Text("Goodbye World!")
}
Text("Something else")
}
}
che si trasforma in:
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")
)
}
}
(emettendo chiamate ridondanti a 1 argomento ViewBuilder.buildBlock
per chiarezza).
@ViewBuilder
developer.apple.com/documentation/swiftui/viewbuilder .