Il semble que le nouveau SwiftUI
cadre d' Apple utilise un nouveau type de syntaxe qui construit efficacement un tuple, mais a une autre syntaxe:
var body: some View {
VStack(alignment: .leading) {
Text("Hello, World") // No comma, no separator ?!
Text("Hello World!")
}
}
En essayant de comprendre ce qu'est vraiment cette syntaxe , j'ai découvert que l' VStack
initialiseur utilisé ici prend une fermeture du type () -> Content
comme deuxième paramètre, où Content
est un paramètre générique conforme à View
celui qui est déduit via la fermeture. Pour savoir quel type Content
est déduit, j'ai légèrement modifié le code, en conservant ses fonctionnalités:
var body: some View {
let test = VStack(alignment: .leading) {
Text("Hello, World")
Text("Hello World!")
}
return test
}
Avec cela, se test
révèle être de type VStack<TupleView<(Text, Text)>>
, c'est-à- Content
dire de type TupleView<Text, Text>
. En levant les yeux TupleView
, j'ai trouvé que c'était un type de wrapper provenant de SwiftUI
lui-même qui ne pouvait être initialisé qu'en passant le tuple qu'il devrait envelopper.
Question
Maintenant, je me demande comment dans le monde les deux Text
instances de cet exemple sont converties en un fichier TupleView<(Text, Text)>
. Est-ce piraté SwiftUI
et donc invalide la syntaxe Swift régulière? TupleView
être un SwiftUI
type soutient cette hypothèse. Ou est-ce une syntaxe Swift valide? Si oui, comment peut-on l' utiliser à l'extérieur SwiftUI
?
@ViewBuilder
developer.apple.com/documentation/swiftui/viewbuilder .Réponses:
Comme le dit Martin , si vous regardez la documentation de
VStack
'sinit(alignment:spacing:content:)
, vous pouvez voir que lecontent:
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 { /// Builds an empty view from an block containing no statements, `{ }`. public static func buildBlock() -> EmptyView /// Passes a single view written as a child view (e..g, `{ Text("Hello") }`) /// through unmodified. 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: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émentebuildBlock
de 1 à 10View
paramètres conformes, en consolidant plusieurs vues en une seuleTupleView
:@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) extension ViewBuilder { /// Passes a single view written as a child view (e..g, `{ Text("Hello") }`) /// through unmodified. 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 parTupleView<(Text, Text)>
.Vous noterez que
ViewBuilder
ne définit quebuildBlock
jusqu'à 10 paramètres, donc si nous essayons de définir 11 sous-vues:var body: some View { // error: Static member 'leading' cannot be used on instance of // type 'HorizontalAlignment' 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") // ... // up to 10 views } Group { Text("Hello world") // ... // up to 10 more views } // ... }
ViewBuilder
implémente également d'autres méthodes de générateur de fonctions telles que:extension ViewBuilder { /// Provides support for "if" statements in multi-statement closures, producing /// ConditionalContent for the "then" branch. public static func buildEither<TrueContent, FalseContent>(first: TrueContent) -> ConditionalContent<TrueContent, FalseContent> where TrueContent : View, FalseContent : View /// Provides support for "if-else" statements in multi-statement closures, /// producing ConditionalContent for the "else" branch. 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é).la source
ViewBuilder
ne définit quebuildBlock
jusqu'à 10 paramètres - cela signifie-t-ilvar body: some View
ne pas pouvoir avoir plus de 11 sous-vues?ForEach
vue à la place. Vous pouvez cependant utiliser laGroup
vue pour contourner cette restriction, j'ai modifié ma réponse pour le montrer.Une chose analogue est décrite dans la vidéo Nouveautés de Swift WWDC dans la section sur les DSL (commence à ~ 31:15). L'attribut est interprété par le compilateur et traduit en code associé:
la source