← Alle Beitrage

SwiftUI-Performance und Interop in iOS 27

Ein LazyVStack weiß nicht, wie hoch er ist. Er schätzt seine eigene Höhe aus der durchschnittlichen Größe der bereits platzierten Views und der Anzahl der voraussichtlich verbleibenden, und korrigiert diese Schätzung live, während Sie scrollen.1 Drei WWDC26-Sessions des UI-Frameworks-Teams nehmen diese eine Tatsache (und ihre Entsprechungen in Grafik und Interop) und machen daraus ein funktionierendes Modell davon, wie sich SwiftUI in iOS 27 unter Last verhält: wie das Scrollen flüssig bleibt, wie GPU-Effekte komponiert werden und wie SwiftUI sich in eine bereits ausgelieferte AppKit- oder UIKit-App einfügt.

Die drei Sessions lesen sich wie ein Argument, das auf drei Arten vorgetragen wird. Lazy Stacks performen gut, wenn Sie aufhören, gegen ihre Schätzung zu kämpfen; Shader-Effekte komponieren, wenn Sie jeden Modifier als Stufe in einer Pipeline behandeln; und Interop funktioniert, wenn Sie @Observable und die Representable-Protokolle die Naht tragen lassen. Keine der drei ist eine Feature-Ankündigung. Jede ist ein Mechanismus, der gut genug erklärt wird, dass Sie das Framework vorhersagen können, statt zu raten.

TL;DR / Wichtigste Erkenntnisse

  • Ein LazyVStack wertet nur die Views aus, die das sichtbare Rechteck füllen; Höhen außerhalb des Bildschirms und der Content-Offset werden geschätzt, daher sind absolute Content-Offset-Lesungen instabil, und Sie sollten relative Sichtbarkeits-APIs wie .onScrollTargetVisibilityChange bevorzugen.12
  • Prefetching verteilt die Arbeit, eine View anzuzeigen, über mehrere Frames, bevor sie erscheint; richten Sie Views in ihrem Initializer ein (nicht in onAppear), damit die vorgeladene Arbeit nicht verworfen wird.1
  • Vermeiden Sie eine dynamische Anzahl von Subviews in einem ForEach-Blatt: Filtern mit einer Bedingung im body hält Views nach Index am Leben, also filtern Sie stattdessen auf Datenebene (ein Predicate auf einer Query).1
  • SwiftUI stellt drei Shader-Einstiegspunkte bereit (colorEffect, distortionEffect, layerEffect), die an Leistungsfähigkeit zunehmen; nur layerEffect kann benachbarte Pixel sampeln, was Blur und Domain-Warp benötigen.3456
  • Shader sind zustandslos, daher entsteht Animation, indem man einen TimelineView-Zeitstempel als Parameter hineingibt; nichts wird zwischen Frames übertragen.37
  • AppKit und UIKit erhalten automatisches Neuzeichnen durch @Observable (kein manuelles needsDisplay mehr), und SwiftUI lässt sich über NSHostingView, NSGestureRecognizerRepresentable, NSHostingMenu und NSHostingSceneRepresentation in eine bestehende App einfügen.8910

Lazy Stacks arbeiten mit Schätzungen

Watch on Apple Developer ↗
Rens, ein UI-Frameworks-Ingenieur, erklärt, dass ein LazyVStack von oben nach unten layoutet und stoppt, sobald das sichtbare Rechteck gefüllt ist, und den Rest schätzt.

Das Erste, was man über einen Lazy Stack verinnerlichen muss, ist, dass er bewusst Korrektheit gegen Effizienz eintauscht. Anders als ein VStack wertet ein LazyVStack keine Views aus oder rendert sie, die nicht sichtbar sind; er layoutet seine Views von oben nach unten und stoppt, sobald das sichtbare Rechteck gefüllt ist, fügt Views hinzu, sobald sie hereinscrollen, und entfernt sie, sobald sie herausscrollen.1 Der Nutzen liegt auf der Hand. Der Preis ist subtil: Weil der Stack nie alle Views lädt, werden die Höhen der Views außerhalb des Bildschirms aus dem Durchschnitt des Vorangegangenen geschätzt, die ideale Breite kollabiert auf die Breite der ersten Subview, und der Raum oberhalb der sichtbaren Region ist selbst nur eine Näherung.1

Diese Schätzung ist kein Bug, den man einmal umgeht; sie ist das Substrat, auf dem jede andere Lazy-Stack-Entscheidung sitzt. Session 321 macht die Konsequenz mit einer Orientierungsänderung konkret. Drehen Sie ein iPhone, und die oberste sichtbare View bleibt verankert, aber der Stack hat das exakte neue Layout der Views darüber noch nicht gemessen. Scrollen Sie zurück nach oben, und der Stack muss abgleichen: Er korrigiert den geschätzten Raum oberhalb der sichtbaren Region und aktualisiert den Content-Offset der Scroll View um denselben Betrag, sodass der Content-Offset oben bei null landet.1 Der Lazy Stack und die ihn umschließende Scroll View koordinieren Position und Offset präzise, sodass die relative Position der sichtbaren Subviews nie springt, während sich die Schätzungen aktualisieren.1

Das handlungsleitende Korollar ist eine Regel darüber, welchen Scroll-APIs man vertrauen sollte. Weil der absolute Content-Offset geschätzt ist, ergibt das Lesen davon (etwa mit .onScrollGeometryChange, um eine Schaltfläche nach 100 Punkten auszublenden) einen Schwellenwert, der driftet, während sich die Schätzungen einpendeln.2 Das stabile Signal ist die relative Sichtbarkeit. Der .onScrollTargetVisibilityChange-Modifier feuert, wenn sich die Menge der in der Scroll View sichtbaren Subviews ändert, sodass eine „Scroll zum Vorzeigen”-Schaltfläche ihre Sichtbarkeit daran koppeln kann, welche Zeilen auf dem Bildschirm sind, mit einem Schwellenwert (die Session verwendet 80 %) statt an einer instabilen Pixelzahl.21 Dieselbe Logik klagt .scrollTransition an: Eine Transformation, die eine View aus ihrem ursprünglichen Rahmen schiebt, kann den Stack glauben lassen, eine sichtbare View sei außerhalb des Bildschirms, und sie zu früh fallen lassen, sodass jede Scroll-Transition verhindern muss, dass Views, die normalerweise nicht sichtbar wären, in das sichtbare Rechteck geschoben werden.111

Warum Ihre View-Structs nicht die Subviews sind

Die Subviews, die ein Lazy Stack lädt, bilden sich nicht eins zu eins auf die von Ihnen geschriebenen View-Structs ab. Ein ForEach von StepView löst sich zu einem StepView pro Schritt auf, aber wenn jeder StepView-body zwei Views auf oberster Ebene zurückgibt (ein Diagramm und Anweisungen) ohne ein umschließendes Layout, lädt der Stack jede davon einzeln.1 Die Zahl, die für den Stack zählt, ist die aufgelöste Subview-Anzahl, nicht die Struct-Anzahl.

Die Falle ist eine dynamische Subview-Anzahl. Wenn ein StepView je nach Umgebungswert eine Subview oder null zurückgibt, kann der Stack den Indizes nicht mehr trauen, weil sich die Anzahl früherer Views ändern könnte. Also hält er frühere StepView-Instanzen vorsorglich am Leben, was bedeutet, dass eine unverwandte Umgebungsänderung body-Auswertungen für aus dem Bildschirm gescrollte Views auslösen kann, und der Stack ihren Zustand nicht freigibt.1 Die Lösung besteht darin, den Filter von der View weg und auf die Daten zu verlagern: Wenn Sie SwiftData verwenden, packen Sie die Bedingung in ein Predicate auf der Query, sodass die Subview-Anzahl bekannt ist, ohne irgendwelche Views zu konstruieren.1 Das Auspacken eines Optionals in einem body hat denselben Länger-am-Leben-Effekt; der sauberere Zug ist, weiter oben eine ContentUnavailableView zu zeigen, statt den Lazy Stack teilweise aufgelöste Zeilen halten zu lassen.1

Prefetching ist der Mechanismus, der die Schätzungen schnell anfühlen lässt. Während des Scrollens hat eine Scroll View nur bis zur Frame-Deadline Zeit, den Offset zu aktualisieren, Views zu rendern und Ihre Offset-Änderungsarbeit auszuführen; wenn das Anzeigen einer neuen View dieses Budget sprengt, fällt der Frame aus, und Sie sehen einen Ruckler.1 Um das zu verhindern, prüft der Lazy Stack, ob Zeit übrig ist, und erledigt, falls ja, einen Teil der Arbeit einer bald erscheinenden View frühzeitig (Auswertung ihres body und Layouts, sogar das Verteilen eines verschachtelten LazyHStack über Frames), sodass zum Zeitpunkt des Erscheinens der View der Großteil der Arbeit bereits erledigt ist.1 Deshalb ist die Session beim Thema onAppear so nachdrücklich: Wenn Sie eine View in onAppear einrichten, verwerfen Sie die vorgeladene Arbeit und erzwingen eine Wiederholung beim Erscheinen, manchmal werden dabei mehr Views als nötig hereingezogen und das Scrollen verschlechtert sich. Richten Sie die View in ihrem Initializer ein, sodass sie in einem vernünftigen Zustand ankommt, und reservieren Sie onAppear für wirklich erscheinungsgebundene Arbeit wie das Laden der nächsten Seite in einem unendlichen Scroll.1 Bei einem umgekehrten Scroll kann ein body sogar während des Prefetchings laufen, während onAppear überhaupt nicht feuert.1

Fortgeschrittene Grafik ist nur eine Pipeline

Watch on Apple Developer ↗
Haotian, ein UI-Frameworks-Ingenieur, fasst fortgeschrittene Effekte als Standard-Rohre auf, die miteinander verbunden sind: Das „Fortgeschrittene” liegt in der Konstruktion, nicht in der Komplexität.

Session 322 fasst „fortgeschrittene Grafik” als Komposition neu. Jeder SwiftUI-Modifier ist ein Rohr, das Daten aufnimmt, transformiert und weiterreicht; das fortgeschrittene Ergebnis liegt darin, wie Sie die Rohre verbinden, nicht in einer einzelnen komplexen API.3 Die Session baut eine Apple-Music-artige Live-Lyrics-Ansicht, indem sie gewöhnliche Stufen verkettet: das Cover-Artwork blurren, sodass es zurücktritt, einen Shader darüber laufen lassen, diesen Shader mit Zeit antreiben und einen Transkript-Scroll mit derselben Zeitquelle synchronisieren.3

Die Shader-Stufe ist der Ort, an dem die eigentliche Wahl liegt. SwiftUI ruft Metal-Shader-Funktionen über drei Effekt-Einstiegspunkte auf, die in ihrer Leistungsfähigkeit aufsteigen. colorEffect transformiert die Farbe jedes Pixels anhand seiner Position und ursprünglichen Farbe, was für so etwas wie eine Graustufen-Umwandlung ausreicht.4 distortionEffect bildet stattdessen eine Position auf eine andere ab (Sie sagen SwiftUI, die Farbe dieser Position von jener Position zu sampeln), was geometrische Verzerrungen ohne Farbbeteiligung handhabt.5 layerEffect ist am flexibelsten: Es übergibt dem Shader den gesamten Layer der View, sodass das Ausgabepixel seine Nachbarn oder die ganze Region sampeln kann, was genau das ist, was Blur und reichhaltigere Verzerrungen erfordern.63

Der Domain-Warp-Hintergrund der Session verwendet layerEffect. Ein einheitlicher float2-Offset verschiebt jedes Pixel um denselben Betrag, was das Bild nur verschiebt; organische Bewegung benötigt Variation pro Pixel, also sampelt der Shader eine vorberechnete NoiseTexture (als Bild hineingereicht, das auf der Metal-Seite als texture2d ankommt), deren rote und grüne Kanäle an jeder UV-Koordinate einen unterschiedlichen X- und Y-Offset liefern.3 Das Noise einmal zu sampeln verdreht das Bild; es zweimal zu sampeln, das zweite Mal an einer Position, die das erste Sample verschoben hat, erzeugt fließende Blobs. Diese Technik zweiter Ordnung ist Domain Warping, und die Session verweist auf ihre herunterladbare Beispiel-App mit einer Live-Vorschau für die Parameter.3

Zwei Framework-Tatsachen lassen die Animation funktionieren. Shader sind zustandslos: Sie behalten keine Erinnerung an den vorherigen Frame, und die Ausgabe hängt nur von den Parametern ab, die Sie übergeben.3 Bewegung kann also nicht aus dem Inneren des Shaders kommen; sie muss hineingefüttert werden, und ein TimelineView ist das Rohr, das sie liefert, indem es bei jedem Frame mit einem Zeitstempel auf einem Animationsplan feuert.73 Übergeben Sie diesen Zeitstempel an den Shader, addieren Sie ihn zur Noise-Sample-Position, und das Muster fließt. Die Transkript-Seite nutzt dieselbe Zeitquelle aus der anderen Richtung wieder: Der Wiedergabe-Zeitstempel wählt die aktuelle Zeile (fett und klar, der Rest verblasst), und ein onChange hält diese Zeile zentriert, während die Zeit voranschreitet.123 Der schwebende Zeitstempel auf der aktiven Zeile wird nicht mit offset positioniert (was die Größen beider Views benötigen würde), sondern mit einer Alignment-Guide-Überschreibung, die den Punkt eines Alignments semantisch neu definiert, sodass die obere Kante der Subview an der unteren Kante ihres Containers ansetzt, ohne einen manuellen Offset.133

SwiftUI fügt sich in eine AppKit- oder UIKit-App ein

Watch on Apple Developer ↗
David Nadoba, ein UI-Frameworks-Ingenieur, merkt an, dass SwiftUI von Anfang an dafür entworfen wurde, neben AppKit und UIKit zu funktionieren, so wie Swift dafür entworfen wurde, mit Objective-C zu funktionieren.

Die Interop-Session beginnt mit einem Punkt, der die gesamte Adoptionsfrage neu fasst: Die meisten Apps verwenden SwiftUI bereits implizit. Im neuen Design werden AppKit-Controls wie NSSlider, NSSwitch und NSSegmentedControl unter der Haube mit SwiftUI gerendert, und auch Liquid Glass teilt sich über SwiftUI große Teile seiner Implementierung über Frameworks hinweg.8 „SwiftUI zu adoptieren” ist also weniger ein Neuschreiben als eine Entscheidung darüber, wo man die Naht explizit macht.

Der erste Schritt braucht überhaupt kein SwiftUI. AppKit und UIKit beobachten jetzt @Observable-Typen automatisch: Markieren Sie eine Model-Klasse als @Observable, lesen Sie ihre Eigenschaften innerhalb einer Zeichenmethode wie drawKnob, und AppKit verfolgt jeden Zugriff und zeichnet neu, wenn sich eine zugegriffene Eigenschaft ändert, womit das manuelle needsDisplay = true ausgemustert wird, das Sie früher schreiben mussten, wann immer der Wert eines Sliders das Erscheinungsbild eines anderen beeinflusste.814 Observation reicht über draw(_:) hinaus zu updateConstraints(), layout(), updateLayer() und den NSViewController-Äquivalenten, und UIKit reicht noch weiter in UIButton, UICollectionViewCell und mehr hinein.8 Es ist in den 2026er-Releases standardmäßig aktiviert und per Info.plist rückwärtskompatibel zu macOS 15 (NSObservationTrackingEnabled) und iOS 18 (UIObservationTrackingEnabled).8

Sobald das Model @Observable ist, ist die eigentliche SwiftUI-Naht klein. Die Session baut einen slider-basierten Farbwähler als kreisförmiges SwiftUI-Control nach, das mit Canvas gezeichnet wird (eine Immediate-Mode-API analog zu drawRect, mit withCGContext, um bestehenden Core-Graphics-Code wiederzuverwenden), und nutzt dasselbe @Observable ColorModel wieder.158 Um es dort einzubetten, wo AppKit eine View erwartet, wickeln Sie es in NSHostingView, eine Unterklasse von NSView; weil das Model bereits die Aktualisierungen antreibt, ist dieses Einwickeln alles, was erforderlich ist.168 Bestehender Gesten-Code wird ohne Neuschreiben übernommen: Ein ForceClickGestureRecognizer erreicht eine SwiftUI-View über NSGestureRecognizerRepresentable (implementieren Sie makeNSGestureRecognizer und handleNSGestureRecognizerAction), wird dann mit dem gewöhnlichen .gesture-Modifier angeheftet und koexistiert mit SwiftUIs eigener Drag-Geste.178 Dieselbe Representable-Familie umfasst NSViewRepresentable, um NSViews in die andere Richtung einzubetten.8

Die Naht skaliert hinauf zu Menüs und Scenes. Eine SwiftUI-View, die einen Button und einen Picker enthält, wird über NSHostingMenu (eine NSMenu-Unterklasse) zu einem echten Menü, das als Untermenü eines NSMenuItem gesetzt wird, das dem Hauptmenü hinzugefügt ist, wobei keyboardShortcut der Aktion einen Nicht-Gesten-Pfad für Eingabegeräte gibt, die nicht force-clicken können.188 Auch ganze SwiftUI-Scenes lassen sich anheften: Ein MenuBarExtra erreicht eine bestehende App über NSHostingSceneRepresentation, hinzugefügt über addSceneRepresentation in applicationWillFinishLaunching, wobei der Toggle einer Settings-Scene steuert, ob das Extra eingefügt wird, und die openSettings()-Environment-Aktion die Einstellungen von einer @IBAction aus öffnet.198 Der abschließende Punkt der Session ist der tragende: Jede API, die sie abdeckt, wird in den 2026er-Releases oder früher ausgeliefert, und es gibt keine Erwartung, dass eine App vollständig SwiftUI sein müsse, um zu profitieren.8 Der Modernisierungsdruck hat anderswo im Zyklus jedoch eine härtere Kante: iOS 27 macht den UIKit-szenenbasierten Lebenszyklus zur Start-Voraussetzung, sodass eine App, die Sie mit dem neuesten SDK neu bauen, nicht startet, wenn sie nie Scenes adoptiert hat.

Was das SwiftUI-Team in den Labs ergänzte

Zwei Klarstellungen aus den Group Labs der WWDC26-UI-Frameworks-Gruppe schärfen das Invalidierungsmodell, das die Sessions beschreiben. Beide sind aus einer lokal transkribierten Aufnahme paraphrasiert; Apple veröffentlicht keine offiziellen Untertitel für die Labs, dieselbe Untertitel-Lücke, die durch das eigene Group Lab des Swift-Teams lief, wo die Sprach-Ingenieure die Fragen zu Concurrency und Roadmap beantworteten.

Code aus body in eine berechnete Eigenschaft zu verschieben, bringt keinen Invalidierungsvorteil. SwiftUI führt die Eigenschaft weiterhin aus, wann immer es body erneut ausführt, sodass der Umzug ein Gewinn an Lesbarkeit ist und nichts weiter.20 Die Performance-Grenze erscheint eine Ebene höher: Extrahieren Sie einen separaten View-Typ, und SwiftUI kann ihn unabhängig invalidieren, indem es nur diese View erneut ausführt, wenn sich ihre Eingaben ändern, statt des gesamten umschließenden body. Greifen Sie zu einer neuen View, nicht zu einer neuen Eigenschaft, wenn SwiftUI weniger Arbeit leisten soll.

Jede Umgebungsänderung invalidiert alle Views, die diesen Umgebungswert lesen. Das Lesen der Umgebung ist günstig; Umgebungs-Churn nicht.21 Halten Sie sich schnell ändernde Werte aus der Umgebung heraus (das Beispiel des Panels ist die aktuelle Zeit), denn ein Wert, der jeden Frame aktualisiert, zieht jeden Leser bei jeder Bewegung in eine Neuauswertung. Reichen Sie flüchtige Werte den Pfad hinunter, der sie braucht, und lassen Sie die Umgebung die Dinge tragen, die unverändert bleiben.

Eine spätere Lab-Session ergänzt drei weitere Mechanismus-Details, alle paraphrasiert aus einer lokal transkribierten Aufnahme des WWDC-2026-SwiftUI-Group-Lab, für das Apple keine offiziellen Untertitel veröffentlicht.

Partielle Graph-Auswertung erklärt, wohin die vorgeladene Arbeit tatsächlich geht. Das Panel beschrieb einen Lazy Stack, der die bodys kommender Zellen in der Frame-Zeit auswertet, die übrig bleibt, nachdem der aktuelle Frame gerendert wurde, und dann genau vor Beginn des nächsten Frames stoppt.22 Der Haken, den sie hervorhoben: Ein onAppear, das eine Zelle so umkonfiguriert, dass sie erneut layoutet werden muss, weil es die Größe der Zelle ändert, verwirft diese vorgeladene Arbeit. Ihr Rat war, die Größenarbeit im Initializer der Zelle zu erledigen, nicht in body oder onAppear, damit das Prefetch überlebt.22

Das mentale Interop-Modell kam von einem Panelisten mit mehr als einem Jahrzehnt UIKit-Erfahrung. UIKit layoutet von oben nach unten, vom Fenster nach innen zu den Blättern, während SwiftUI von unten nach oben baut, vom innersten Knoten nach außen.22 Wenn die beiden sich verschachteln, nannte das Panel die Anordnung wechselnder Schichten ein Sandwich oder eine Torte, weshalb tiefe Verschachtelung subtil wird: Jedes Framework will den Layout-Durchlauf vom entgegengesetzten Ende antreiben.22

Der obige Umgebungspunkt bekam ein anschauliches Beispiel aus der Praxis. Nach dem schlimmsten Umgebungsmissbrauch gefragt, den sie gesehen hätten, nannte das Panel das Hineinlegen der Scroll-Position in die Umgebung, die sich beim Scrollen bei jedem Frame aktualisiert und so jeden Leser bei jedem Frame invalidiert.22

Eine zweite SwiftUI-Group-Lab-Session ergänzte vier weitere Mechanismus-Details, alle paraphrasiert aus einer lokal transkribierten Aufnahme des WWDC-2026-SwiftUI-Group-Lab (Session 2); Apple veröffentlicht keine offiziellen Untertitel für die Labs.

Der onGeometryChange(for:of:action:)-Modifier liest sich besser, als es klingt, wegen der Art, wie seine beiden Closures die Arbeit aufteilen. Die Transform-Closure läuft mit Live-Geometrie bei jedem Frame, aber nur der Wert, den sie zurückgibt, entscheidet, ob die Aktion feuert, da der Ergebnistyp Equatable ist und die Aktion nur läuft, wenn sich dieser Wert ändert.23 Einen groben Wert zurückzugeben (einen Größen-Bucket oder einen Layout-Breakpoint, nicht die rohe Größe) wandelt also ein Frame-Rate-Signal in eines um, das zweimal feuert, an den Schwellenwerten, statt kontinuierlich. Das Panel verband dies mit einer Warnung, dass GeometryReader für die von ihm umschlossenen Subviews teuer ist und auf einen Hintergrund beschränkt werden sollte, sodass er misst, ohne das Haupt-Layout anzutreiben.23

Eine gute Dynamic Property kann die meiste onChange-Arbeit direkt ersetzen. Die update()-Methode einer DynamicProperty läuft unmittelbar vor dem body der View, sodass ein eigener Property-Wrapper an diesem Punkt synchron einen bereits gecachten Wert (das Beispiel des Panels war ein Bild) liefern und den onAppear-Umweg sowie das dadurch ausgelöste Neu-Rendern überspringen kann.24 Die Rahmung des Panels war, dass die meisten Verwendungen von onChange durch eine gut gebaute Dynamic Property ersetzt werden können.24

Eine View, die innerhalb einer ScrollView außerhalb ihrer gemeldeten Layout-Grenzen zeichnet, kann gecullt werden, weil das System anhand der Grenzen, die ihm mitgeteilt wurden, entscheidet, dass sie außerhalb des Bildschirms ist, nicht anhand dessen, wo sie tatsächlich zeichnet.25 Das Panel nannte dies als das konkrete Fehlerverhalten hinter eigenen Dropdowns und Overlays, die ihre Host-View überlaufen und dann mitten im Scrollen verschwinden, und merkte an, dass dieselbe Gefahr für overlay-Inhalte gilt, die über ihren Anker hinausreichen.25

Ein Vollbild-Overlay über allem zu präsentieren, einschließlich Sheets, hat keine saubere reine SwiftUI-Antwort. Der Rat des Panels war, über den UIKit-Szenen-Lebenszyklus auf ein neues UIWindow zurückzufallen, mit dem tieferen Prinzip, dass das letzte Fenster gewinnt und es eine einzige Quelle der Wahrheit dafür geben muss, was obenauf sitzt.26 Sie ergänzten, dass die bessere Lösung oft darin besteht, den vom Benutzer erwarteten Navigation-Stack wiederherzustellen, statt eine störende Abdeckung über alles zu werfen.26

Was man zuerst adoptieren sollte

Ein als Mechanismus erklärtes Release belohnt eine Ordnung nach Hebelwirkung, nicht nach Neuheit.

  1. Stellen Sie Model-Klassen in Ihrem AppKit/UIKit-Code auf @Observable um. Es löscht manuelle needsDisplay-Aufrufe, gibt jeder beobachtenden Zeichen- und Layout-Methode automatisches Neuzeichnen und ist die Voraussetzung, die einen späteren NSHostingView-Einbau trivial macht.814 Es ist der risikoärmste, sofort lohnendste Zug in allen drei Sessions.
  2. Prüfen Sie Lazy Stacks auf dynamische Subview-Anzahlen. Jedes ForEach-Blatt, das bedingt null oder eine Subview zurückgibt oder ein Optional in seinem body auspackt, hält Views nach Index am Leben; verlagern Sie den Filter auf ein Predicate auf einer Query (oder höher in der Hierarchie), und sowohl der Speicher als auch die Scroll-to-Item-Performance verbessern sich.1
  3. Verschieben Sie das View-Setup aus onAppear in Initializer. Prefetching hilft nur, wenn die vorgeladene Arbeit überlebt; ein Setup, das Größe oder Inhalt in onAppear verändert, verwirft diese Arbeit.1 Das ist ein stiller, breiter Gewinn an Scroll-Geschmeidigkeit.
  4. Ersetzen Sie absolute Scroll-Offset-Lesungen durch relative Sichtbarkeits-APIs. Alles, was an den Content-Offset gekoppelt ist, wird driften; .onScrollTargetVisibilityChange koppelt sich daran, welche Zeilen tatsächlich sichtbar sind.21
  5. Greifen Sie nur dort zu Shadern, wo ein kleiner Effekt seinen Platz verdient. Beginnen Sie bei colorEffect oder distortionEffect; eskalieren Sie zu layerEffect nur, wenn ein Effekt Nachbarn sampeln muss, und treiben Sie jede Bewegung mit einem TimelineView-Zeitstempel an, statt Zustand im Shader zu erwarten.4567

Der rote Faden durch die drei: Sagen Sie das Framework vorher, bevor Sie dagegen drücken. Lazy Stacks schätzen, Shader vergessen, und Interop ist eine Naht, kein Neuschreiben. Bauen Sie mit diesen drei Tatsachen im Sinn, und der Rest folgt.

FAQ

Warum springt oder driftet meine SwiftUI-Lazy-Stack-Scroll-Position?

Weil ein LazyVStack keine Views außerhalb des Bildschirms lädt, schätzt er ihre Höhen und den Raum oberhalb der sichtbaren Region, sodass der absolute Content-Offset eine Schätzung ist, die das Framework korrigiert, sobald es das echte Layout lernt (nach einer Orientierungsänderung etwa gleicht es die Schätzung ab, wenn Sie nach oben zurückscrollen).1 Wenn Sie UI an den absoluten Offset koppeln, driftet der Schwellenwert, während sich die Schätzungen einpendeln. Verwenden Sie stattdessen .onScrollTargetVisibilityChange, das anhand dessen feuert, welche Subviews tatsächlich sichtbar sind.2

Wie halte ich das Scrollen in einem SwiftUI-Lazy-Stack in iOS 27 flüssig?

Lassen Sie Prefetching seine Arbeit tun: Richten Sie Views in ihrem Initializer ein, sodass die Arbeit, die der Lazy Stack vor dem Erscheinen einer View leistet, nicht verworfen wird, und vermeiden Sie es, die Größe oder den Inhalt einer View in onAppear zu verändern.1 Vermeiden Sie außerdem eine dynamische Anzahl von Subviews in ForEach-Blättern und vermeiden Sie Layout-Änderungen (wie eine onGeometryChange-getriebene Höhe) nach dem Erscheinen einer View, da beides den Stack zwingt, Arbeit zu wiederholen oder Positionen mitten im Scrollen neu zu berechnen.1

Wann sollte ich colorEffect, distortionEffect oder layerEffect verwenden?

Verwenden Sie colorEffect, um die Farbe jedes Pixels aus seiner Position und ursprünglichen Farbe zu transformieren (zum Beispiel einen Graustufenfilter).4 Verwenden Sie distortionEffect für geometrische Effekte, bei denen Sie eine Ausgabeposition auf eine Quellposition abbilden, von der gesampelt wird.5 Verwenden Sie layerEffect, wenn das Ausgabepixel von mehr als einem Eingabepixel abhängt, weil es dem Shader den ganzen View-Layer gibt, um Nachbarn oder die gesamte Region zu sampeln, was Blur und Domain Warping benötigen.6

Wie animiere ich einen Metal-Shader in SwiftUI?

Shader sind zustandslos: Sie behalten keine Erinnerung an den vorherigen Frame und hängen nur von ihren Parametern ab, sodass Sie nicht aus dem Inneren des Shaders animieren können.3 Füttern Sie einen Wert ein, der sich über die Zeit ändert. Ein TimelineView auf einem Animationsplan feuert bei jedem Frame mit einem Zeitstempel; übergeben Sie diesen Zeitstempel als Parameter an den Shader (die Session addiert ihn zur Noise-Sample-Position), und der Effekt animiert.73

Kann ich SwiftUI zu einer bestehenden AppKit- oder UIKit-App hinzufügen, ohne sie neu zu schreiben?

Ja, und die Session ist ausdrücklich darin, dass keine App vollständig SwiftUI sein muss, um zu profitieren.8 Markieren Sie Ihr Model als @Observable, sodass AppKit und UIKit automatisch neu zeichnen, betten Sie dann SwiftUI-Views mit NSHostingView ein, bringen Sie bestehende Gesture Recognizer mit NSGestureRecognizerRepresentable herüber, bauen Sie Menüs mit NSHostingMenu und heften Sie SwiftUI-Scenes von Ihrem App-Delegate aus mit NSHostingSceneRepresentation an; all dies wird in den 2026er-Releases oder früher ausgeliefert.816171819

Der vollständige Apple-Ecosystem-Cluster: das SwiftUI-Substrat (Result Builders, Opaque Types, der wertbasierte View-Baum), das erklärt, warum ein Lazy Stack View-Structs in eine andere Menge von Subviews auflöst; die iOS-27-SwiftUI-Oberfläche (Reordering, Documents, Toolbars, Errors), neben der diese Performance-und-Interop-Geschichte steht; die @Observable-Interna, die jetzt auch in AppKit und UIKit das automatische Neuzeichnen antreiben; und die Liquid-Glass-Patterns, deren frameworkübergreifende Implementierung die Interop-Session auf geteiltes SwiftUI zurückführt. Der Hub ist die Apple Ecosystem Series. Für einen breiteren Kontext zu iOS mit KI-Agenten siehe den iOS Agent Development guide.

Referenzen


  1. Apple, WWDC26 session 321, “Dive into lazy stacks and scrolling with SwiftUI.” developer.apple.com/videos/play/wwdc2026/321. Covers lazy-stack layout and height estimation, the estimated content offset, view-struct-to-subview resolution, the dynamic-subview-count trap, prefetching across frame deadlines, and onAppear versus initializer setup. 

  2. Apple, WWDC26 session 321, “Dive into lazy stacks and scrolling with SwiftUI”. The session presents onScrollTargetVisibilityChange (a modifier whose closure runs when the set of visible scroll targets changes) as the stable, relative-visibility alternative to absolute content-offset reads. 

  3. Apple, WWDC26 session 322, “Compose advanced graphics effects with SwiftUI.” developer.apple.com/videos/play/wwdc2026/322. Frames effects as a composable pipeline; covers blur, the three shader entry points, the NoiseTexture domain-warp technique, stateless shaders driven by time, and alignment-guide attachment. 

  4. Apple Developer Documentation: colorEffect(_:isEnabled:). Returns a new view that applies a shader transforming each pixel’s color, given its position and original color. 

  5. Apple Developer Documentation: distortionEffect(_:maxSampleOffset:isEnabled:). Applies a shader that maps the position of each pixel to a source position to sample from, for geometric effects. 

  6. Apple Developer Documentation: layerEffect(_:maxSampleOffset:isEnabled:). Applies a shader as a layer effect with access to the entire view layer, allowing each output pixel to sample multiple input pixels. 

  7. Apple Developer Documentation: TimelineView. A view that updates its content according to a schedule; on an animation schedule it supplies the per-frame timestamp session 322 feeds into its shader. 

  8. Apple, WWDC26 session 272, “Use SwiftUI with AppKit and UIKit.” developer.apple.com/videos/play/wwdc2026/272. Covers automatic @Observable redraw in AppKit/UIKit, observation back-deployment via Info.plist, Canvas, NSHostingView, NSGestureRecognizerRepresentable, NSHostingMenu, and NSHostingSceneRepresentation

  9. Apple Developer Documentation: NSGestureRecognizerRepresentable. A protocol that wraps an NSGestureRecognizer for use as a SwiftUI gesture, implemented with makeNSGestureRecognizer and handleNSGestureRecognizerAction

  10. Apple Developer Documentation: MenuBarExtra. A scene that renders a menu bar item; session 272 attaches it to an existing AppKit app through NSHostingSceneRepresentation

  11. Apple Developer Documentation: scrollTransition(_:axis:transition:). Applies a transition as a view scrolls within a scroll view; session 321 warns that a transform pushing a view into the visible rect can desync a lazy stack. 

  12. Apple Developer Documentation: onChange(of:initial:_:). Runs an action when a value changes; session 322 uses it to recenter the current transcript line. 

  13. Apple Developer Documentation: alignmentGuide(_:computeValue:). Sets a view’s alignment guide so the layout system positions it semantically; session 322 overrides a bottom guide to attach a subview’s top edge to its container’s bottom edge. 

  14. Apple Developer Documentation: Observable. The macro that makes a class’s mutable properties participate in the Observation system, which AppKit and UIKit track for automatic redraw in the 2026 releases. 

  15. Apple Developer Documentation: Canvas. An immediate-mode drawing view whose closure receives a GraphicsContext; session 272 uses it to redraw the circular color picker and notes withCGContext for reusing Core Graphics code. 

  16. Apple Developer Documentation: NSHostingView. An NSView subclass that hosts a SwiftUI view hierarchy inside an AppKit view tree. 

  17. Apple Developer Documentation: NSViewRepresentable. A wrapper that lets an NSView participate in a SwiftUI view hierarchy; session 272 names it alongside NSGestureRecognizerRepresentable as part of the representable family. 

  18. Apple Developer Documentation: NSHostingMenu. An NSMenu subclass that renders a SwiftUI view as menu content, added to the main menu as the submenu of an NSMenuItem

  19. Apple Developer Documentation: keyboardShortcut(_:modifiers:). Assigns a keyboard shortcut to a control’s action; session 272 adds one to the menu button so input devices that cannot force-click still reach the feature. 

  20. Apple, WWDC 2026 UI Frameworks group lab, session 8002. Paraphrased from a locally transcribed recording; no official transcript is published. The team clarified that moving code from body into a computed property is a readability change only, and that the independent-invalidation boundary appears when you extract a separate view type. 

  21. Apple, WWDC 2026 UI Frameworks group lab, session 8003. Paraphrased from a locally transcribed recording; no official transcript is published. The team noted that every environment change invalidates all views reading that value, and advised keeping fast-changing values (the panel’s example was the current time) out of the environment. 

  22. Apple, WWDC26 session 8006, “SwiftUI Group Lab.” developer.apple.com/videos/play/wwdc2026/8006. Paraphrased from a locally transcribed recording of the WWDC 2026 SwiftUI Group Lab; Apple publishes no official captions for the labs. Source for partial graph evaluation in leftover frame time (and the onAppear-resize warning, with sizing done in init), the UIKit-top-down-versus-SwiftUI-bottom-up interop “sandwich or cake” arrangement, and the scroll-position-in-the-environment example that invalidates every reader on every frame. 

  23. Apple, WWDC 2026 SwiftUI Group Lab (session 2). Paraphrased from a locally transcribed recording of the WWDC 2026 SwiftUI Group Lab (session 2); Apple publishes no official captions for the labs. Source for the onGeometryChange transform-gates-action mechanism (return a coarse value to fire at thresholds) and the guidance to confine an expensive GeometryReader to a background. The two-closure shape is documented at onGeometryChange(for:of:action:): the of: transform closure derives an Equatable value from the geometry proxy and the action: closure runs only when that value changes. 

  24. Apple, WWDC 2026 SwiftUI Group Lab (session 2). Paraphrased from a locally transcribed recording of the WWDC 2026 SwiftUI Group Lab (session 2); Apple publishes no official captions for the labs. Source for replacing most onChange work with a dynamic property that vends a cached value synchronously. Apple’s DynamicProperty protocol defines an update() method that SwiftUI calls immediately before rendering a view’s body so the property holds its most recent value. 

  25. Apple, WWDC 2026 SwiftUI Group Lab (session 2). Paraphrased from a locally transcribed recording of the WWDC 2026 SwiftUI Group Lab (session 2); Apple publishes no official captions for the labs. Source for the culling of views that draw outside their reported layout bounds inside a ScrollView (the failure behind overflowing custom dropdowns and overlays), and the note that the same hazard applies to overlay content extending past its anchor. 

  26. Apple, WWDC 2026 SwiftUI Group Lab (session 2). Paraphrased from a locally transcribed recording of the WWDC 2026 SwiftUI Group Lab (session 2); Apple publishes no official captions for the labs. Source for the lack of a clean pure-SwiftUI full-screen-over-sheets overlay, the drop to a new UIWindow through the UIKit scene life cycle, the “last window wins / single source of truth” principle, and the preference for restoring the navigation stack over a disruptive cover. 

Verwandte Beiträge

Barrierefreiheit in iOS 27: Lese-Apps und benutzerdefinierte Steuerelemente

Barrierefreiheit in iOS 27 für Lese-Apps und benutzerdefinierte Steuerelemente: Verknüpfung der Textnavigation, causesPa…

10 Min. Lesezeit

Was ist neu in SwiftUI für iOS 27

iOS 27 überarbeitet SwiftUI-Listen, -Dokumente, -Toolbars und -Fehler: Ziehen zum Neuordnen, ein les-/schreibbares Dokum…

21 Min. Lesezeit

Von 76 auf 100: Ein perfekter Lighthouse-Score

Wie eine persönliche Portfolio-Website von einem mobilen Lighthouse-Performance-Score von 76 mit 0,493 CLS zu einem perf…

6 Min. Lesezeit