Es scheint, als ob Apples neues SwiftUI
Framework eine neue Art von Syntax verwendet , die effektiv ein Tupel erstellt, aber eine andere Syntax hat:
var body: some View {
VStack(alignment: .leading) {
Text("Hello, World") // No comma, no separator ?!
Text("Hello World!")
}
}
Als ich versuchte herauszufinden, was diese Syntax wirklich ist , fand ich heraus, dass der VStack
hier verwendete Initialisierer einen Abschluss des Typs () -> Content
als zweiten Parameter verwendet, wobei Content
ein generischer Parameter, der dem entspricht View
, über den Abschluss abgeleitet wird. Um herauszufinden, auf welchen Typ geschlossen Content
wird, habe ich den Code geringfügig geändert und seine Funktionalität beibehalten:
var body: some View {
let test = VStack(alignment: .leading) {
Text("Hello, World")
Text("Hello World!")
}
return test
}
Damit test
offenbart sich, dass es vom Typ ist VStack<TupleView<(Text, Text)>>
, was bedeutet, dass Content
es vom Typ ist TupleView<Text, Text>
. Als TupleView
ich nach oben schaute, stellte ich fest, dass es sich um einen Wrapper-Typ handelt, der von sich SwiftUI
selbst stammt und nur durch Übergeben des Tupels initialisiert werden kann, das umbrochen werden soll.
Frage
Jetzt frage ich mich, wie in Text
aller Welt die beiden Instanzen in diesem Beispiel in a konvertiert werden TupleView<(Text, Text)>
. Ist dies in die reguläre Swift-Syntax gehackt SwiftUI
und daher ungültig? TupleView
ein SwiftUI
Typ zu sein, unterstützt diese Annahme. Oder ist diese Swift-Syntax gültig? Wenn ja, wie kann man es draußen benutzen SwiftUI
?
@ViewBuilder
developer.apple.com/documentation/swiftui/viewbuilder .Antworten:
Wie Martin sagt , wenn Sie sich die Dokumentation für
VStack
's anseheninit(alignment:spacing:content:)
, können Sie sehen, dass dercontent:
Parameter das Attribut hat@ViewBuilder
:init(alignment: HorizontalAlignment = .center, spacing: Length? = nil, @ViewBuilder content: () -> Content)
Dieses Attribut bezieht sich auf den
ViewBuilder
Typ, der bei Betrachtung der generierten Schnittstelle folgendermaßen aussieht:@_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 }
Das
@_functionBuilder
Attribut ist Teil einer inoffiziellen Funktion namens " Funktionsersteller ", die hier in der Swift-Evolution vorgestellt und speziell für die mit Xcode 11 gelieferte Swift-Version implementiert wurde, sodass sie in SwiftUI verwendet werden kann.Durch Markieren eines Typs
@_functionBuilder
kann er als benutzerdefiniertes Attribut für verschiedene Deklarationen wie Funktionen, berechnete Eigenschaften und in diesem Fall Parameter des Funktionstyps verwendet werden. Solche mit Anmerkungen versehenen Deklarationen verwenden den Funktionsgenerator, um Codeblöcke zu transformieren:Die Art und Weise, wie ein Funktions-Builder Code transformiert, wird durch die Implementierung von Builder-Methoden definiert, z. B.
buildBlock
die eine Reihe von Ausdrücken verwenden und sie zu einem einzigen Wert zusammenfassen.Zum Beispiel
ViewBuilder
ArbeitsgerätebuildBlock
für 1 bis 10View
entsprechen , Parameter, mehrere Ansichten zu einem einzigen KonsolidierungTupleView
:@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 // ... }
Auf diese Weise kann eine Reihe von Ansichtsausdrücken innerhalb eines an den
VStack
Initialisierer übergebenen Abschlusses in einen Aufruf umgewandelt werdenbuildBlock
, der dieselbe Anzahl von Argumenten benötigt. Zum Beispiel:struct ContentView : View { var body: some View { VStack(alignment: .leading) { Text("Hello, World") Text("Hello World!") } } }
verwandelt sich in einen Aufruf an
buildBlock(_:_:)
:struct ContentView : View { var body: some View { VStack(alignment: .leading) { ViewBuilder.buildBlock(Text("Hello, World"), Text("Hello World!")) } } }
was dazu führt, dass der undurchsichtige Ergebnistyp
some View
erfüllt wird durchTupleView<(Text, Text)>
.Sie werden feststellen, dass
ViewBuilder
nurbuildBlock
bis zu 10 Parameter definiert werden. Wenn wir also versuchen, 11 Unteransichten zu definieren: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!") } }
Wir erhalten einen Compilerfehler, da es keine Builder-Methode gibt, um diesen Codeblock zu verarbeiten (beachten Sie, dass die Fehlermeldungen nicht so hilfreich sind, da diese Funktion noch in Arbeit ist).
In Wirklichkeit glaube ich nicht, dass Menschen so oft auf diese Einschränkung stoßen werden, zum Beispiel wäre das obige Beispiel besser, wenn man stattdessen die
ForEach
Ansicht verwendet:var body: some View { VStack(alignment: .leading) { ForEach(0 ..< 20) { i in Text("Hello world \(i)") } } }
Wenn Sie jedoch mehr als 10 statisch definierte Ansichten benötigen, können Sie diese Einschränkung mithilfe der
Group
Ansicht leicht umgehen :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
implementiert auch andere Funktionserstellungsmethoden wie: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 }
Dies gibt ihm die Möglichkeit, if-Anweisungen zu verarbeiten:
var body: some View { VStack(alignment: .leading) { if .random() { Text("Hello World!") } else { Text("Goodbye World!") } Text("Something else") } }
was verwandelt wird 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") ) } }
(Das Aussenden redundanter 1-Argument-Aufrufe
ViewBuilder.buildBlock
erfordert Klarheit).quelle
ViewBuilder
definiert nurbuildBlock
bis zu 10 Parameter - bedeutet das, dassvar body: some View
nicht mehr als 11 Unteransichten vorhanden sein können?ForEach
Ansicht verwenden möchten . Sie können dieGroup
Ansicht jedoch verwenden , um diese Einschränkung zu umgehen. Ich habe meine Antwort bearbeitet, um dies zu zeigen.Eine analoge Sache wird in Was ist neu in Swift WWDC-Video im Abschnitt über DSLs beschrieben (beginnt um ~ 31: 15). Das Attribut wird vom Compiler interpretiert und in verwandten Code übersetzt:
quelle