Woraus SwiftUI gemacht ist
Genre: framework-explainer. Der Beitrag erklärt das Substrat, auf dem SwiftUI aufsetzt: Result Builder, opake Rückgabetypen und einen werttypisierten View-Baum. Sobald das Substrat sichtbar wird, verlieren jene Teile von SwiftUI, die Entwickler überraschen (AnyView, Group, ViewBuilder, @ViewBuilder-Parameter, der gefürchtete some View vs any View-Fehler), ihr Mysterium.
Eine SwiftUI-View ist ein Werttyp, der einem einzigen Protokoll mit einer einzigen Anforderung entspricht. Der Rest des Frameworks baut auf Swift-Sprachfeatures auf, die außerhalb von SwiftUI existieren: Result Builder, opake Typen, Generics mit Constraints, Property Wrapper. Wenn Sie die Sprachfeatures verstehen, liest sich das Framework wie normaler Swift-API. Wenn nicht, liest es sich wie Magie, die gelegentlich zubeißt.
Der Beitrag geht das Substrat durch. Es gibt hier keinen LiveActivityManager, keinen Get-Bananas-Screenshot. Es geht um das Framework, nicht um ein Projekt; sobald das Framework lesbar wird, liest sich jeder Shipped-Code-Beitrag im Cluster sauberer.
TL;DR
- Eine SwiftUI-View ist ein Swift-Werttyp, der
Viewentspricht. Das Protokoll hat eine Anforderung:var body: some View { get }. Alles Weitere baut auf Swift-Sprachfeatures auf. @ViewBuilderist ein Result Builder. Der Body jederViewist einer. Result Builder verwandeln durch Kommas getrennte Ausdrücke über vom Compiler synthetisierte Aufrufe in einen einzigen Rückgabewert.some Viewist ein opaker Rückgabetyp. Der Compiler kennt den konkreten Typ; der Aufrufer nicht. Der opake Typ macht View-Bodies zur Compile- und Laufzeit schnell;AnyViewist die typgelöschte Notluke für Fälle, in denen Opazität nicht funktioniert.Group,EmptyView,TupleView,_ConditionalContentsind die Implementierungstypen, die Result Builder synthetisieren. Sie sind dokumentiert, werden aber selten von Hand geschrieben.
Das Protokoll, mit dem alles beginnt
Das View-Protokoll hat eine Anforderung:1
public protocol View {
associatedtype Body : View
@ViewBuilder var body: Self.Body { get }
}
Zwei Teile dieses Protokolls sind wichtig, um den Rest von SwiftUI zu verstehen.
Der zugeordnete Typ Body : View. Der Body einer View ist selbst eine View. Diese Rekursion ist es, was das Framework komponierbar macht. Jede View gibt aus ihrem Body eine weitere View zurück, und so weiter, bis die Kette an einer der primitiven Views des Frameworks endet (etwa Text, Color, Image, EmptyView), deren Body Never ist. Primitive Views sind die Blätter des Baums; die Views, die Sie schreiben, sind die Zweige.
Das Attribut @ViewBuilder auf body. Jeder Body ist eine Result-Builder-Closure. Result Builder sind ein Swift-Sprachfeature, das in SE-0289 dokumentiert ist (formalisiert als @resultBuilder in Swift 5.4) und es einer Closure mit einer Folge von Ausdrücken erlaubt, vom Compiler durch synthetisierte Methodenaufrufe in einen einzigen Rückgabewert transformiert zu werden.2 Diese Transformation lässt die kommafreie, anweisungsförmige Syntax innerhalb eines SwiftUI-Bodys funktionieren.
Die Form des Protokolls ist aus zwei Gründen ungewöhnlich.
Erstens ist die Anforderung eine berechnete Eigenschaft, keine Methode. Der Body der View wird bei jedem Render-Durchlauf neu berechnet, wenn SwiftUI entscheidet, dass sich der Zustand der View geändert hat. Das Framework betrachtet body als kostengünstig aufrufbar; lange Berechnungen innerhalb von body sind ein Anti-Pattern, weil sie bei jedem Render laufen.
Zweitens ist Self.Body zugeordnet, nicht gelöscht. Der konkrete Body-Typ einer View ist zur Compile-Zeit Teil ihrer Signatur. Der Body-Typ von Text("Hello") ist Never; der Body-Typ einer benutzerdefinierten View ist das, was @ViewBuilder für den Body synthetisiert hat. Das Design mit zugeordneten Typen erlaubt dem Compiler, den View-Baum ohne Laufzeit-Typprüfungen zu optimieren. Es erzeugt zudem die some View-Anforderung, wenn eine benutzerdefinierte View bedingten Inhalt zurückgibt.
Result Builder: Die kommafreie DSL
Ein Result Builder ist ein Swift-Sprachfeature, das eine Closure durch Einfügen vom Compiler synthetisierter Methodenaufrufe in einen einzigen Rückgabewert transformiert. @ViewBuilder ist ein Result Builder. Der Body jeder SwiftUI-View ist seine Closure.2
Betrachten Sie diese View:
struct ExampleView: View {
var body: some View {
Text("Title")
Text("Subtitle")
Image(systemName: "star")
}
}
Der Body hat drei Anweisungen ohne Trennzeichen. In normalem Swift ist das ein Compiler-Fehler: Eine Closure kann nur einen Wert zurückgeben. Result Builder schreiben die Closure vor der Kompilierung um. Der eigentliche Code, den der Compiler nach der Expansion durch @ViewBuilder sieht, lautet ungefähr:
struct ExampleView: View {
var body: some View {
ViewBuilder.buildBlock(
Text("Title"),
Text("Subtitle"),
Image(systemName: "star")
)
}
}
ViewBuilder.buildBlock(_:_:_:) ist eine statische Methode, die drei Views entgegennimmt und ein TupleView<(Text, Text, Image)> zurückgibt. Der Body gibt diesen einzelnen Tuple-View-Wert zurück. Älteres SwiftUI lieferte einen festen Satz von buildBlock-Überladungen für 1, 2, 3, … bis zu 10 Kinder; aktuelles SwiftUI nutzt Swifts Unterstützung für variadische Generics (buildBlock<each Content>), sodass ein Body mit elf oder mehr Geschwister-Views kein Sonderfall mehr ist.
Dasselbe Muster handhabt den Kontrollfluss. Ein View-Body mit einer if-Anweisung sieht so aus:
struct ConditionalView: View {
let isActive: Bool
var body: some View {
if isActive {
Text("Active")
} else {
Text("Inactive")
}
}
}
@ViewBuilder schreibt das durch Aufrufe von buildEither(first:) / buildEither(second:) um und erzeugt ein _ConditionalContent<Text, Text>. Der Compiler kennt den Ergebnistyp zur Compile-Zeit, auch wenn bei jedem Render nur ein Zweig läuft.
if let, switch, das Auspacken von Optionals und einige weitere Konstrukte werden von den verschiedenen statischen buildXxx-Methoden des Result Builders behandelt.3 Wiederholter Inhalt ist der eine bemerkenswerte Fall, den das Sprachfeature über buildArray unterstützt, @ViewBuilder jedoch nicht: Eine rohe for-Schleife innerhalb eines body schlägt fehl mit „closure containing control flow statement cannot be used with result builder ‘ViewBuilder’.” Die SwiftUI-typische Antwort ist ForEach, das eine RandomAccessCollection und eine Content-Closure entgegennimmt und die Iteration als einzelne werttypisierte View synthetisiert. Die DSL ist nicht maßgeschneidert; es sind Swift-Result-Builder, konfiguriert für Views.
some View und das Opazitätsproblem
Der Body einer benutzerdefinierten View gibt üblicherweise some View zurück. Das Schlüsselwort steht für opaker Rückgabetyp und wurde in Swift 5.1 hinzugefügt.4
some View sagt: „Ich gebe einen bestimmten Typ zurück, der View entspricht, aber ich verrate Ihnen nicht, welchen.” Der Compiler verfolgt den konkreten Typ intern zur Optimierung; der Aufrufer Ihrer View sieht nur den Protokoll-Witness. Dieses Muster ermöglicht es, dass der Body einer View einen komplexen Typ wie VStack<TupleView<(Text, Image, Spacer)>> zurückgibt, ohne dass Sie diesen Typ in Ihrem Quellcode schreiben müssen.
Zwei Dinge an some View, die neue SwiftUI-Entwickler verwirren:
some View ist ein bestimmter Typ, auch wenn Sie verschiedene Dinge zurückgeben. Der Ausdruck if condition { Text("A") } else { Image("b") } ist innerhalb eines @ViewBuilder-Bodys erlaubt, weil der Result Builder beide Zweige in _ConditionalContent einwickelt und so einen einzelnen konkreten Typ erzeugt. Der Ausdruck if condition { return Text("A") } else { return Image("b") } außerhalb eines Result Builders ist hingegen ein Compiler-Fehler: Die beiden Zweige geben unterschiedliche konkrete Typen zurück, und some View verlangt einen einzigen. Result Builder sind es, die bedingte Rückgabeformen funktionieren lassen; explizite Returns verlieren die Result-Builder-Transformation.
some View ist nicht dasselbe wie any View. some View ist opak (ein bestimmter Typ, verborgen); any View ist existenziell (eine Box, die jeden konformen Typ aufnehmen kann, mit Laufzeit-Overhead). Eine freie Funktion oder Eigenschaft kann legal any View zurückgeben. Der body des View-Protokolls jedoch nicht: Das Protokoll verlangt associatedtype Body: View, und any View selbst entspricht nicht View, sodass var body: any View das Protokoll nicht erfüllt und der Compiler some View vorschlägt. Die praktische Regel: Verwenden Sie some View für View-Bodies und greifen Sie zu AnyView (dem typgelöschten Wrapper) für laufzeitvariante View-Typen. Die Fehlermeldung „function declares an opaque return type but the return statements in its body do not have matching underlying types” bedeutet fast immer, dass Sie versucht haben, verschiedene konkrete Typen aus einer some View-Funktion zurückzugeben, und dass Sie entweder Result-Builder-Verzweigung oder AnyView benötigen.
AnyView: Die Notluke
AnyView ist ein typgelöschter View-Wrapper. Die Konstruktion lautet AnyView(myView). Der Wrapper hält jede konforme View, und SwiftUI akzeptiert ihn dort, wo eine View erwartet wird.5
Der Notluken-Anwendungsfall ist eine Funktion, die basierend auf Laufzeitdaten verschiedene konkrete Typen zurückgibt und sich nicht durch Result-Builder-Verzweigung ausdrücken lässt:
func viewForKind(_ kind: Kind) -> AnyView {
switch kind {
case .text: return AnyView(Text("hello"))
case .image: return AnyView(Image("photo"))
case .custom: return AnyView(MyCustomView())
}
}
Die Kosten von AnyView bestehen darin, dass der zugrunde liegende Typ nicht Teil der statischen Identität der View ist. Apples Dokumentation beschreibt die Konsequenz direkt: Wenn sich der innerhalb eines AnyView eingewickelte Typ über Renders hinweg ändert, wird die bestehende View-Hierarchie zerstört und an ihrer Stelle eine neue Hierarchie erstellt, was zu verlorenem Zustand, neu gestarteten Animationen und verlorener Identität führt. Das erneute Einwickeln desselben konkreten Typs löst diese Zerstörung nicht aus, aber die statisch-typgesteuerte Differenzbildung, die das Framework bevorzugt, ist so oder so nicht mehr verfügbar.
Die richtige Regel lautet: Bevorzugen Sie @ViewBuilder-Result-Builder-Verzweigung für bedingte Views (if, switch, for), bevorzugen Sie parametrisierte Views für variierende Typen, greifen Sie nur dann zu AnyView, wenn weder das eine noch das andere funktioniert. Eine func viewForKind, die AnyView zurückgibt, ist üblicherweise ein Zeichen dafür, dass Sie viewForKind some View zurückgeben lassen und ein switch innerhalb einer Result-Builder-Closure platzieren sollten.
Group, EmptyView, TupleView: Die Implementierungstypen
Der Result Builder synthetisiert spezifische konkrete View-Typen. Drei davon sind nützlich zu erkennen:6
Group ist ein transparenter Container. Er akzeptiert bis zu zehn Views als Inhalt und präsentiert sie als Geschwister gegenüber dem übergeordneten Layout. Der Container selbst fügt keine visuelle Struktur hinzu; die Inhalte werden genau so gerendert, wie sie es einzeln würden. Der Anwendungsfall ist das Einwickeln mehrerer Views in einem Kontext, der eine einzelne View erwartet (ein .if-Modifier, eine bedingte Rückgabe, eine Funktion, die „eine View” produziert). Group { Text("A"); Text("B") } ist eine einzelne View, die zwei enthält; es ist die explizite Form dessen, was Result Builder implizit tun.
EmptyView ist eine View, die nichts rendert. Der Result Builder verwendet sie als bedingten Falsch-Zweig, wenn ein if kein else hat. Die Rückgabe von EmptyView() aus Ihrem eigenen Code ist eine Möglichkeit, sich vom Rendern abzumelden, ohne den Rückgabetyp der Funktion zu ändern.
TupleView ist der konkrete Typ, den Result Builder erzeugen, wenn ein Body mehrere Geschwister-Views hat. Der Ausdruck am Anfang dieses Beitrags, der drei Geschwister-Views zurückgibt, gibt tatsächlich ein TupleView<(Text, Text, Image)> zurück. Sie schreiben TupleView fast nie direkt; Sie lesen es in Fehlermeldungen.
_ConditionalContent (mit dem führenden Unterstrich) ist der Typ, der if/else-Zweige behandelt. Der Typ taucht in der öffentlichen Oberfläche von ViewBuilder auf, aber der unterstrichene Name signalisiert „nicht leichtfertig dagegen schreiben”; lassen Sie den Result Builder ihn aus if/else synthetisieren, anstatt ihn von Hand zu konstruieren.
@ViewBuilder auf Ihren eigenen Funktionen
Result Builder sind nicht nur für body. Jede Funktion oder jeder Closure-Parameter kann mit @ViewBuilder annotiert werden, und dieselbe DSL-Syntax wird darin legal.2
struct Card<Content: View>: View {
let content: Content
init(@ViewBuilder content: () -> Content) {
self.content = content()
}
var body: some View {
VStack(alignment: .leading, spacing: 8) {
content
}
.padding()
.background(.regularMaterial)
.clipShape(RoundedRectangle(cornerRadius: 12))
}
}
// Usage: callers get the result-builder DSL inside the closure.
Card {
Text("Title")
Text("Subtitle")
Image(systemName: "star")
}
Dieses Muster ist der Weg, wie SwiftUIs eigene VStack, HStack, ZStack, List, Form, Section, Group und NavigationStack mehrere Kinder akzeptieren. Jeder einzelne dieser Typen nimmt einen @ViewBuilder content: () -> Content-Parameter entgegen. Wer das Muster erkennt, kann eigene Container-Views mit derselben Ergonomie wie die des Frameworks schreiben, ohne dass spezielle Compiler-Unterstützung erforderlich wäre.
Der Grund, warum Sie init(@ViewBuilder content:) schreiben und nicht nur init(content:), ist, dass das Attribut auf dem Parameter die Result-Builder-Transformation innerhalb des Closure-Bodys aktiviert, den der Aufrufer übergibt. Ohne das Attribut ist Card { Text("A"); Text("B") } ein Compiler-Fehler, weil die Closure zwei Anweisungen hat und kein @ViewBuilder vorhanden ist, um sie zu transformieren.
State, Bindings und die Property-Wrapper-Schicht
Alles bisher Beschriebene betrifft die Form des View-Baums. Die andere Hälfte von SwiftUI ist State, und diese Hälfte baut auf Swift Property Wrappern auf.7
Die für die View-Erstellung relevantesten Property Wrapper:
@State besitzt ein Stück werttypisierten Zustand innerhalb einer einzelnen View. Das Lesen der Eigenschaft liest den zugrunde liegenden Speicher; eine Zuweisung löst ein erneutes Rendern der View aus. Der Wrapper eignet sich für einfachen, view-lokalen Zustand (Ein/Aus eines Toggles, der Entwurfstext eines Textfelds).
@Binding ist eine bidirektionale Referenz auf den Zustand einer anderen View. Eine Kind-View, die den Zustand eines Elternteils lesen und schreiben muss, nimmt einen Binding<T>-Parameter entgegen. Der Elternteil konstruiert das Binding über $state (die Dollarzeichen-Projektion auf @State).
@Observable (iOS 17+) ist ein Makro, das das ältere Konformitätsmuster ObservableObject ersetzt. Auf eine Klasse angewendet, generiert das Makro Tracking des Observation-Frameworks, sodass Eigenschaften der Klasse erneutes Rendern der View auslösen, wenn sie innerhalb eines Bodys gelesen und später geändert werden. Der View-seitige Besitz einer @Observable-Klasse wandert von @StateObject zu schlichtem @State; nachgelagerte Views, die ein bidirektionales Handle benötigen, verwenden @Bindable anstelle von @ObservedObject.
@Environment liest dependency-injizierte Werte aus der Environment-Kette aus. SwiftUI stellt eingebaute Environment-Schlüssel bereit (Locale, Farbschema, Dismiss-Aktion); Apps fügen benutzerdefinierte Schlüssel für domänenspezifische Dependency Injection hinzu.
Die Property-Wrapper-Schicht ermöglicht es, dass der body einer View bei Zustandsänderungen erneut ausgeführt wird. SwiftUI verfolgt Lesevorgänge innerhalb von body über zwei verschiedene Mechanismen: AttributeGraph (Apples privater Abhängigkeitsgraph, der @State, @Binding und @Environment zugrunde liegt) für den älteren Property-Wrapper-Pfad sowie das Observation-Framework der Standardbibliothek (withObservationTracking, öffentlich ab iOS 17+) für @Observable-Typen.8 Wird eine verfolgte Eigenschaft mutiert, werden die entsprechenden Bodies erneut ausgeführt, und die Differenzbildungsmaschinerie berechnet die minimale Änderung am View-Baum.
Die beiden Hälften (die View-Baum-Schicht und die State-Schicht) sind lose gekoppelt. Der View-Baum ist werttypisiert und schnell neu zu berechnen. Die State-Schicht ist referenztypisiert (für @Observable) oder werttypisiert-mit-Speicherzeiger (für @State) und verfolgt Lesevorgänge. Zusammen erzeugen sie das „Beschreibe, was auf dem Bildschirm sein soll, als Funktion des Zustands, und das Framework ermittelt das Diff”-Modell des Frameworks.
Was Sie in Fehlermeldungen jetzt erkennen
So lesen sich SwiftUI-Compiler-Fehler mit sichtbar gewordenem Substrat:
„Function declares an opaque return type, but the return statements in its body do not have matching underlying types.” Zwei Return-Anweisungen mit unterschiedlichen konkreten Typen in einer some View-Funktion. Lösung: Verwenden Sie @ViewBuilder, damit der Result Builder beide in _ConditionalContent einwickelt, oder packen Sie beide Returns in AnyView.
„The compiler is unable to type-check this expression in reasonable time.” Lange Body-Ketten mit vielen Modifiern erschöpfen den Type Checker. Lösung: Zerlegen Sie den Body in kleinere berechnete Eigenschaften oder Sub-Views; jedes some View-zurückgebende Stück vereinfacht die Inferenzarbeit.
„Cannot convert value of type ‘TupleView<…>’ to expected type ‘some View’.” Eine Funktion, die eine View erwartet, hat das Ergebnis eines mehrteiligen Bodys ohne @ViewBuilder erhalten. Lösung: Fügen Sie @ViewBuilder zum Closure-Parameter hinzu, der den mehrteiligen Inhalt entgegennimmt.
„Generic parameter ‘Content’ could not be inferred.” Ein benutzerdefinierter Container nimmt @ViewBuilder content: () -> Content entgegen, und die Aufrufstelle hat eine leere Closure. Lösung: Result Builder benötigen mindestens einen Ausdruck, um Content zu inferieren; leere Closures fallen auf EmptyView() zurück, sofern die Aufrufstelle es explizit bereitstellt.
Die Fehlermeldungen sind unfreundlich, weil das Substrat unsichtbar ist. Liest man sie mit sichtbar gewordenem Substrat, werden die meisten zu „aha, der Result Builder kann das nicht transformieren” oder „aha, ich brauche entweder Verzweigung oder AnyView.”
Wann man über das Substrat hinausgreifen sollte
Einige Muster, die das Substrat nicht sauber abdeckt:
Variadische konkrete Typen. Eine Funktion, die je Zweig einen anderen View-Typ zurückgibt und die Sie nicht in eine Result-Builder-Verzweigung einwickeln können, benötigt AnyView. Akzeptieren Sie die Kosten (verlorene Differenzbildung, keine Animation) und dokumentieren Sie die Aufrufstelle.
Plattformübergreifende bedingte Views. Compile-Zeit-#if os(iOS) funktioniert innerhalb eines @ViewBuilder-Bodys, begrenzt aber die Verzweigungsanzahl des Result Builders; mehr-OS-bedingte Bodies stoßen mitunter an die „expression too complex”-Grenze. Die Lösung besteht darin, plattformspezifische Sub-Views in separate Funktionen zu extrahieren, die jeweils some View zurückgeben.
Imperative View-Konstruktion. Das Framework erwartet Views als Ausdrücke, nicht als konstruiert-und-dann-mutierte Objekte. Der UIKit-Stil „Label erzeugen, Text setzen, als Subview hinzufügen” lässt sich nicht übersetzen; das SwiftUI-Äquivalent ist ein werttypisiertes Text("..."), das aus einem Body zurückgegeben wird. Muster, die imperative Konstruktion erfordern, sind üblicherweise ein Zeichen dafür, dass die Arbeit in eine UIViewRepresentable-Brücke zu UIKit gehört.
Was das Muster für Apps bedeutet, die auf iOS 26+ ausgeliefert werden
Drei Erkenntnisse.
-
SwiftUI ist Swift, keine Magie. Result Builder, opake Rückgabetypen und Property Wrapper finden sich allesamt in der Swift-Sprachreferenz. Liest man das Framework als Swift-Code und nicht als spezielle DSL, werden die überraschenden Teile vorhersehbar.
-
some ViewundAnyViewlösen unterschiedliche Probleme. Opake Rückgabetypen sind die Standardwahl; Typlöschung ist die Notluke. Der Griff zuAnyViewsollte der seltene Fall sein; der Griff zusome Viewplus Result-Builder-Verzweigung der häufige. -
Result Builder sind die gesamte DSL. Überall dort, wo eine Funktion oder ein Parameter
@ViewBuilderist, steht die kommafreie, anweisungsförmige Syntax zur Verfügung. Eigene Container-Views mit derselben Ergonomie wieVStackzu schreiben, sind ein Attribut und ein Closure-Parameter.
Lesen Sie diesen Beitrag zusammen mit der Shipped-Code-Reihe des Clusters: plattformübergreifendes SwiftUI-Shipping (Return läuft auf fünf Plattformen mit einem gemeinsamen SwiftUI-Kern); die visuelle Schicht Liquid Glass; die Zustandsmaschine der Live Activities auf iOS; der Vertrag der watchOS-Laufzeit auf der Apple Watch. Der Hub befindet sich auf der Apple Ecosystem Series. Für umfassenderen Kontext zu iOS-mit-AI-Agenten siehe den iOS Agent Development Guide.
FAQ
Was ist das View-Protokoll in SwiftUI?
Das View-Protokoll hat eine Anforderung: var body: some View { get }. Jede SwiftUI-View ist ein Swift-Werttyp, der View entspricht, mit einer berechneten body-Eigenschaft, die eine weitere View zurückgibt (oder Never für primitive Views wie Text, Color, Image, EmptyView). Der Body ist mit @ViewBuilder annotiert, sodass er die kommafreie DSL-Syntax von SwiftUI verwenden kann.
Was bedeutet some View?
some View ist ein opaker Rückgabetyp (Swift 5.1+). Der Compiler kennt den konkreten Typ; der Aufrufer sieht nur den Protokoll-Witness. Opake Typen erlauben es View-Bodies, komplexe Typen wie VStack<TupleView<(Text, Image, Spacer)>> zurückzugeben, ohne sie auszuschreiben, und bewahren dabei die Compile-Zeit-Optimierung. some View ist ein bestimmter Typ, auch wenn dieser Typ an der Aufrufstelle nicht sichtbar ist.
Wann sollte ich AnyView verwenden?
Verwenden Sie AnyView nur dann, wenn weder @ViewBuilder-Result-Builder-Verzweigung (if, switch, for) noch parametrisierte Generics das Problem lösen. Wenn sich der eingewickelte konkrete Typ über Renders hinweg ändert, wird die bestehende View-Hierarchie zerstört und an ihrer Stelle eine neue erstellt; das ist der Moment, in dem Animationen neu starten und View-State zurückgesetzt wird. Das erneute Einwickeln desselben konkreten Typs löst diese Zerstörung nicht aus, aber die statisch-typgesteuerte Differenzbildung, die das Framework bevorzugt, ist so oder so nicht mehr verfügbar. Wenn Sie sich häufig nach AnyView greifen sehen, gehört das zu ändernde Muster nach oben: Bevorzugen Sie parametrisierte Views oder verlagern Sie die Bedingung in einen Result-Builder-Body.
Was ist @ViewBuilder und wo kann ich es verwenden?
@ViewBuilder ist ein Result Builder (Swift-Sprachfeature). Er transformiert eine Closure mit mehreren Ausdrücken in einen einzigen Rückgabewert, indem er vom Compiler synthetisierte Aufrufe von buildBlock, buildEither, buildOptional etc. einfügt. Der Body jeder SwiftUI-View ist standardmäßig @ViewBuilder. Sie können @ViewBuilder auf jede Funktion oder jeden Closure-Parameter anwenden, um Aufrufern dieselbe DSL-Syntax zu geben; VStack, Card und Section verwenden dasselbe Muster, um mehrere Kinder zu akzeptieren.
Warum wird mein View-Body neu gerendert, obwohl ich es nicht erwartet habe?
SwiftUI führt body immer dann erneut aus, wenn eine vom Body gelesene Zustandseigenschaft mutiert wird. Property Wrapper (@State, @Binding, @Observable, @Environment) verfolgen Lesevorgänge und lösen bei Schreibvorgängen erneutes Rendern aus. Unerwartete Re-Renders lassen sich üblicherweise auf eine sich ändernde Zustandseigenschaft einer übergeordneten View, einen sich ändernden Environment-Wert oder eine modifizierte gelesene Eigenschaft eines @Observable-Objekts zurückführen. Die Differenzbildung des Frameworks berechnet daraufhin die minimale Baumänderung.
Quellen
-
Apple Developer, „View” und „Configuring views”. Das
View-Protokoll, der zugeordnete TypBodyund das@ViewBuilder-Attribut aufbody. ↩ -
Swift Evolution, „SE-0289: Result builders”. Der Sprachvorschlag, der Result Builder formalisierte (eingeführt als
_functionBuilderin 5.1, formalisiert als@resultBuilderin 5.4). DefiniertbuildBlock,buildEither,buildOptional,buildArray,buildExpression,buildFinalResultund Verwandtes. ↩↩↩ -
Apple Developer, „ViewBuilder” und „ForEach”. Der Result-Builder-Typ, den SwiftUI für View-Bodies verwendet (variadisch-generisches
buildBlock,buildEither, Optional-Auspacken).ViewBuildermachtbuildArraynicht öffentlich, daher istForEachdas Iterations-Primitiv, um eine View über eine Collection zu wiederholen. ↩ -
Swift Evolution, „SE-0244: Opaque result types”. Das
some-Schlüsselwort für opake Rückgabetypen, hinzugefügt in Swift 5.1. ↩ -
Apple Developer, „AnyView”. Typgelöschter View-Wrapper, Konstruktion und der Differenzbildungs-Trade-off. ↩
-
Apple Developer, „Group”, „EmptyView” und „TupleView”. Implementierungstypen, die Result Builder synthetisieren. ↩
-
Apple Developer, „State and Data Flow”. Die Property-Wrapper-Schicht:
@State,@Binding,@Observable,@Environment. SwiftUIs Observation-System und das@Observable-Makro ab iOS 17+. ↩ -
Apple Developer, „Observation” und „Migrating from the Observable Object protocol to the Observable macro”. Das Observation-Framework der Standardbibliothek, einschließlich
withObservationTracking(_:onChange:), sowie der iOS-17-Migrationspfad vonObservableObjectzu@Observable. ↩