Comme le dit Martin , si vous regardez la documentation de VStack
's init(alignment:spacing:content:)
, vous pouvez voir que le content:
paramètre a l'attribut @ViewBuilder
:
init(alignment: HorizontalAlignment = .center, spacing: Length? = nil,
@ViewBuilder content: () -> Content)
Cet attribut fait référence au ViewBuilder
type qui, si vous regardez l'interface générée, ressemble à:
@_functionBuilder public struct ViewBuilder {
public static func buildBlock() -> EmptyView
public static func buildBlock(_ content: Content) -> Content
where Content : View
}
L' @_functionBuilder
attribut fait partie d'une fonctionnalité non officielle appelée " constructeurs de fonctions ", qui a été lancée ici sur l'évolution de Swift , et implémentée spécialement pour la version de Swift livrée avec Xcode 11, lui permettant d'être utilisée dans SwiftUI.
Le marquage d'un type @_functionBuilder
permet de l'utiliser comme attribut personnalisé sur diverses déclarations telles que des fonctions, des propriétés calculées et, dans ce cas, des paramètres de type de fonction. Ces déclarations annotées utilisent le générateur de fonctions pour transformer des blocs de code:
- Pour les fonctions annotées, le bloc de code qui est transformé est l'implémentation.
- Pour les propriétés calculées annotées, le bloc de code qui est transformé est le getter.
- Pour les paramètres annotés de type fonction, le bloc de code qui est transformé est toute expression de fermeture qui lui est passée (le cas échéant).
La manière dont un générateur de fonctions transforme le code est définie par son implémentation de méthodes de générateur telles que buildBlock
, qui prend un ensemble d'expressions et les consolide en une valeur unique.
Par exemple, ViewBuilder
implémente buildBlock
de 1 à 10 View
paramètres conformes, en consolidant plusieurs vues en une seule 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
}
Cela permet à un ensemble d'expressions de vue dans une fermeture transmise à VStack
l'initialiseur de être transformé en un appel à buildBlock
qui prend le même nombre d'arguments. Par exemple:
struct ContentView : View {
var body: some View {
VStack(alignment: .leading) {
Text("Hello, World")
Text("Hello World!")
}
}
}
se transforme en un appel à buildBlock(_:_:)
:
struct ContentView : View {
var body: some View {
VStack(alignment: .leading) {
ViewBuilder.buildBlock(Text("Hello, World"), Text("Hello World!"))
}
}
}
résultant en le type de résultat opaque some View
satisfait par TupleView<(Text, Text)>
.
Vous noterez que ViewBuilder
ne définit que buildBlock
jusqu'à 10 paramètres, donc si nous essayons de définir 11 sous-vues:
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!")
}
}
nous obtenons une erreur du compilateur, car il n'y a pas de méthode de générateur pour gérer ce bloc de code (notez que, comme cette fonctionnalité est toujours en cours de travail, les messages d'erreur qui l'entourent ne seront pas très utiles).
En réalité, je ne pense pas que les gens se heurteront à cette restriction si souvent, par exemple, l'exemple ci-dessus serait mieux servi en utilisant la ForEach
vue à la place:
var body: some View {
VStack(alignment: .leading) {
ForEach(0 ..< 20) { i in
Text("Hello world \(i)")
}
}
}
Si toutefois vous avez besoin de plus de 10 vues définies statiquement, vous pouvez facilement contourner cette restriction en utilisant la Group
vue:
var body: some View {
VStack(alignment: .leading) {
Group {
Text("Hello world")
}
Group {
Text("Hello world")
}
}
ViewBuilder
implémente également d'autres méthodes de générateur de fonctions telles que:
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
}
Cela lui donne la possibilité de gérer les instructions if:
var body: some View {
VStack(alignment: .leading) {
if .random() {
Text("Hello World!")
} else {
Text("Goodbye World!")
}
Text("Something else")
}
}
qui se transforme en:
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")
)
}
}
(émettre des appels redondants à 1 argument ViewBuilder.buildBlock
pour plus de clarté).
@ViewBuilder
developer.apple.com/documentation/swiftui/viewbuilder .