Performance et interopérabilité de SwiftUI dans iOS 27
Un LazyVStack ne connaît pas sa propre hauteur. Il estime sa hauteur à partir de la taille moyenne des vues qu’il a déjà placées et du nombre de celles qu’il s’attend encore à placer, puis corrige cette estimation en direct au fur et à mesure que vous faites défiler.1 Trois sessions de la WWDC26 menées par l’équipe UI Frameworks prennent ce seul fait (et ses analogues dans le graphisme et l’interopérabilité) et en font un modèle de travail du comportement de SwiftUI sous charge dans iOS 27 : comment le défilement reste fluide, comment les effets GPU se composent et comment SwiftUI s’insère dans une application AppKit ou UIKit que vous avez déjà livrée.
Les trois sessions se lisent comme un seul argument exposé de trois manières. Les lazy stacks sont performants lorsque vous cessez de combattre leur estimation ; les effets de shaders se composent lorsque vous traitez chaque modificateur comme une étape d’un pipeline ; et l’interopérabilité fonctionne lorsque vous laissez @Observable et les protocoles representable porter la jointure. Aucune des trois n’est une annonce de fonctionnalité. Chacune est un mécanisme expliqué suffisamment bien pour que vous puissiez prédire le framework au lieu de le deviner.
TL;DR / Points clés à retenir
- Un
LazyVStackn’évalue que les vues qui remplissent le rectangle visible ; les hauteurs hors écran et le décalage de contenu sont estimés, de sorte que les lectures absolues du décalage de contenu sont instables et que vous devriez privilégier les API de visibilité relative comme.onScrollTargetVisibilityChange.12 - Le préchargement répartit sur plusieurs frames le travail d’affichage d’une vue avant son apparition ; configurez les vues dans leur initialiseur (et non dans
onAppear) afin que le travail préchargé ne soit pas jeté.1 - Évitez un nombre dynamique de sous-vues dans une feuille
ForEach: filtrer avec une condition dans lebodymaintient les vues en vie par index, alors filtrez plutôt au niveau des données (unPredicatesur uneQuery).1 - SwiftUI expose trois points d’entrée de shaders (
colorEffect,distortionEffect,layerEffect), de puissance croissante ; seullayerEffectpeut échantillonner les pixels voisins, ce dont le flou et le domain warp ont besoin.3456 - Les shaders sont sans état, donc l’animation provient de l’injection d’un horodatage
TimelineViewen tant que paramètre ; rien n’est transporté d’une frame à l’autre.37 - AppKit et UIKit obtiennent un redessin automatique grâce à
@Observable(fini leneedsDisplaymanuel), et SwiftUI s’intègre dans une application existante viaNSHostingView,NSGestureRecognizerRepresentable,NSHostingMenuetNSHostingSceneRepresentation.8910
Les lazy stacks fonctionnent sur des estimations
LazyVStack effectue sa disposition de haut en bas et s’arrête une fois le rectangle visible rempli, en estimant le reste.
La première chose à intégrer au sujet d’un lazy stack est qu’il échange délibérément la justesse contre l’efficacité. Contrairement à un VStack, un LazyVStack n’évalue ni ne rend les vues qui ne sont pas visibles ; il dispose ses vues de haut en bas et s’arrête une fois le rectangle visible rempli, ajoutant les vues à mesure qu’elles défilent dans le champ et les retirant à mesure qu’elles en sortent.1 Le bénéfice est évident. Le coût est subtil : parce que le stack ne charge jamais toutes les vues, les hauteurs des vues hors écran sont estimées à partir de la moyenne de ce qui précède, la largeur idéale se réduit à la largeur de la première sous-vue, et l’espace au-dessus de la région visible est lui-même approximatif.1
Cette estimation n’est pas un bug que l’on contourne une bonne fois pour toutes ; c’est le substrat sur lequel repose toute autre décision relative au lazy stack. La session 321 rend la conséquence concrète avec un changement d’orientation. Faites pivoter un iPhone et la vue visible la plus haute reste ancrée, mais le stack n’a pas encore mesuré la nouvelle disposition exacte des vues au-dessus d’elle. Faites défiler de nouveau vers le haut et le stack doit se réconcilier : il corrige l’espace estimé au-dessus de la région visible et met à jour le décalage de contenu de la scroll view du même montant, de sorte que le décalage de contenu en haut atterrisse à zéro.1 Le lazy stack et sa scroll view englobante coordonnent précisément position et décalage afin que, à mesure que les estimations se mettent à jour, la position relative des sous-vues visibles ne saute jamais.1
Le corollaire actionnable est une règle sur les API de défilement auxquelles se fier. Parce que le décalage de contenu absolu est estimé, le lire (avec .onScrollGeometryChange, par exemple, pour masquer un bouton après 100 points) vous donne un seuil qui dérive à mesure que les estimations se stabilisent.2 Le signal stable est la visibilité relative. Le modificateur .onScrollTargetVisibilityChange se déclenche lorsque l’ensemble des sous-vues visibles dans la scroll view change, de sorte qu’un bouton « défiler pour mettre en avant » peut conditionner sa visibilité aux lignes affichées à l’écran, avec un seuil (la session utilise 80 %), au lieu d’un compte de pixels instable.21 La même logique met en cause .scrollTransition : une transformation qui pousse une vue hors de son cadre d’origine peut faire croire au stack qu’une vue visible est hors écran et la retirer prématurément, donc toute transition de défilement doit empêcher des vues qui ne seraient normalement pas visibles d’être poussées dans le rectangle visible.111
Pourquoi vos structures de vue ne sont pas les sous-vues
Les sous-vues qu’un lazy stack charge ne correspondent pas une à une aux structures de vue que vous avez écrites. Un ForEach de StepView se résout en un StepView par étape, mais si le body de chaque StepView renvoie deux vues de premier niveau (un diagramme et des instructions) sans disposition englobante, le stack charge chacune d’elles séparément.1 Le nombre qui compte pour le stack est le nombre de sous-vues résolues, pas le nombre de structures.
Le piège est un nombre de sous-vues dynamique. Si un StepView renvoie une sous-vue ou zéro selon une valeur d’environnement, le stack ne peut plus se fier aux index, car le nombre de vues antérieures pourrait changer. Il maintient donc les instances StepView antérieures en vie au cas où, ce qui signifie qu’un changement d’environnement sans rapport peut déclencher des évaluations de body pour des vues défilées hors écran et que le stack ne libérera pas leur état.1 La solution consiste à déplacer le filtre hors de la vue et sur les données : si vous utilisez SwiftData, placez la condition dans un Predicate sur la Query afin que le nombre de sous-vues soit connu sans construire aucune vue.1 Dérouler un optionnel dans un body a le même effet de prolongation de vie ; le geste le plus propre consiste à afficher une ContentUnavailableView plus haut plutôt que de laisser le lazy stack retenir des lignes partiellement résolues.1
Le préchargement est le mécanisme qui donne aux estimations une sensation de rapidité. Pendant le défilement, une scroll view n’a que jusqu’à l’échéance de la frame pour mettre à jour le décalage, rendre les vues et exécuter votre travail lié au changement de décalage ; si l’affichage d’une nouvelle vue fait exploser ce budget, la frame est sautée et vous voyez un à-coup.1 Pour l’éviter, le lazy stack vérifie s’il reste du temps libre et, le cas échéant, effectue à l’avance une partie du travail d’une vue sur le point d’apparaître (en évaluant son body et sa disposition, voire en répartissant un LazyHStack imbriqué sur plusieurs frames) afin qu’au moment où la vue apparaît, l’essentiel du travail soit déjà fait.1 C’est pourquoi la session insiste sur onAppear : si vous configurez une vue dans onAppear, vous jetez le travail préchargé et forcez une reprise lorsqu’elle apparaît, en tirant parfois plus de vues que nécessaire et en dégradant le défilement. Configurez la vue dans son initialiseur afin qu’elle arrive dans un état raisonnable, et réservez onAppear au travail réellement lié à l’apparition, comme récupérer la page suivante dans un défilement infini.1 Sur un défilement inversé, un body peut même s’exécuter pendant le préchargement alors que onAppear ne se déclenche jamais.1
Le graphisme avancé n’est qu’un pipeline
La session 322 reformule le « graphisme avancé » comme de la composition. Chaque modificateur SwiftUI est un tuyau qui prend des données en entrée, les transforme et les transmet ; le résultat avancé réside dans la manière dont vous connectez les tuyaux, et non dans une quelconque API complexe isolée.3 La session construit une vue de paroles en direct façon Apple Music en enchaînant des étapes ordinaires : flouter la pochette pour qu’elle recule, exécuter un shader par-dessus, piloter ce shader avec le temps et synchroniser le défilement d’une transcription sur la même source de temps.3
L’étape du shader est l’endroit où se trouve le vrai choix. SwiftUI appelle des fonctions de shader Metal via trois points d’entrée d’effets dont la capacité augmente. colorEffect transforme la couleur de chaque pixel à partir de sa position et de sa couleur d’origine, ce qui suffit pour quelque chose comme une conversion en niveaux de gris.4 distortionEffect mappe plutôt une position vers une autre (vous indiquez à SwiftUI d’échantillonner la couleur de cette position depuis cette autre position), ce qui gère les déformations géométriques sans aucune couleur impliquée.5 layerEffect est le plus flexible : il remet au shader la couche entière de la vue, de sorte que le pixel de sortie peut échantillonner ses voisins ou toute la région, ce qui est exactement ce que le flou et les déformations plus riches exigent.63
L’arrière-plan en domain warp de la session utilise layerEffect. Un décalage float2 uniforme déplace chaque pixel de la même quantité, ce qui ne fait que glisser l’image ; un mouvement organique nécessite une variation par pixel, donc le shader échantillonne une NoiseTexture précalculée (transmise sous forme d’image, arrivant côté Metal comme une texture2d) dont les canaux rouge et vert fournissent un décalage X et Y différent à chaque coordonnée UV.3 Échantillonner le bruit une fois tord l’image ; l’échantillonner deux fois, la seconde fois à une position décalée par le premier échantillon, produit des taches fluides. Cette technique de second ordre est le domain warping, et la session renvoie à son application d’exemple téléchargeable avec un aperçu en direct des paramètres.3
Deux faits du framework font fonctionner l’animation. Les shaders sont sans état : ils ne gardent aucune mémoire de la frame précédente, et la sortie ne dépend que des paramètres que vous transmettez.3 Le mouvement ne peut donc pas venir de l’intérieur du shader ; il doit être injecté, et une TimelineView est le tuyau qui le fournit, se déclenchant à chaque frame avec un horodatage selon un calendrier d’animation.73 Transmettez cet horodatage au shader, ajoutez-le à la position d’échantillonnage du bruit, et le motif s’écoule. Le côté transcription réutilise la même source de temps dans l’autre sens : l’horodatage de lecture sélectionne la ligne courante (en gras et nette, le reste estompé), et un onChange maintient cette ligne centrée à mesure que le temps avance.123 L’horodatage flottant sur la ligne active est positionné non pas avec offset (qui nécessiterait la taille des deux vues) mais avec une redéfinition de guide d’alignement qui redéfinit sémantiquement le point d’un alignement, de sorte que le bord supérieur de la sous-vue se rattache au bord inférieur de son conteneur sans décalage manuel.133
SwiftUI s’intègre dans une application AppKit ou UIKit
La session sur l’interopérabilité s’ouvre sur un point qui recadre toute la question de l’adoption : la plupart des applications utilisent déjà SwiftUI de manière implicite. Dans la nouvelle conception, des contrôles AppKit comme NSSlider, NSSwitch et NSSegmentedControl sont rendus avec SwiftUI en coulisses, et Liquid Glass partage de larges pans de son implémentation entre les frameworks, grâce à SwiftUI lui aussi.8 Ainsi, « adopter SwiftUI » est moins une réécriture qu’une décision sur l’endroit où rendre la jointure explicite.
La première étape ne nécessite aucun SwiftUI. AppKit et UIKit observent désormais automatiquement les types @Observable : marquez une classe de modèle @Observable, lisez ses propriétés à l’intérieur d’une méthode de dessin comme drawKnob, et AppKit suit chaque accès et redessine lorsqu’une quelconque propriété accédée change, mettant à la retraite le needsDisplay = true manuel que vous écriviez chaque fois que la valeur d’un curseur affectait l’apparence d’un autre.814 L’observation s’étend au-delà de draw(_:) à updateConstraints(), layout(), updateLayer() et aux équivalents NSViewController, et UIKit va encore plus loin jusque dans UIButton, UICollectionViewCell et davantage.8 Elle est activée par défaut dans les versions 2026 et rétro-déployable vers macOS 15 (NSObservationTrackingEnabled) et iOS 18 (UIObservationTrackingEnabled) via Info.plist.8
Une fois le modèle @Observable, la jointure SwiftUI proprement dite est minime. La session reconstruit un sélecteur de couleur à base de curseurs sous la forme d’un contrôle SwiftUI circulaire dessiné avec Canvas (une API en mode immédiat analogue à drawRect, avec withCGContext pour réutiliser du code Core Graphics existant), en réutilisant le tout même @Observable ColorModel.158 Pour l’intégrer là où AppKit attend une vue, enveloppez-le dans NSHostingView, une sous-classe de NSView ; comme le modèle pilote déjà les mises à jour, cet enveloppement est tout ce qui est requis.168 Le code de gestes existant se transpose sans réécriture : un ForceClickGestureRecognizer atteint une vue SwiftUI via NSGestureRecognizerRepresentable (implémentez makeNSGestureRecognizer et handleNSGestureRecognizerAction), puis se rattache avec le modificateur .gesture ordinaire et coexiste avec le geste de glissement propre à SwiftUI.178 La même famille de representable inclut NSViewRepresentable pour intégrer des NSView dans l’autre sens.8
La jointure passe à l’échelle des menus et des scènes. Une View SwiftUI contenant un Button et un Picker devient un véritable menu via NSHostingMenu (une sous-classe de NSMenu), défini comme sous-menu d’un NSMenuItem ajouté au menu principal, avec keyboardShortcut offrant à l’action un chemin sans geste pour les périphériques de saisie qui ne peuvent pas faire de force-click.188 Des scènes SwiftUI entières se rattachent aussi : une MenuBarExtra atteint une application existante via NSHostingSceneRepresentation, ajoutée par addSceneRepresentation dans applicationWillFinishLaunching, avec un Toggle d’une scène Settings contrôlant si l’extra est inséré et l’action d’environnement openSettings() ouvrant les réglages depuis une @IBAction.198 Le point de clôture de la session est celui qui porte tout l’édifice : chaque API qu’elle couvre est livrée dans les versions 2026 ou antérieures, et il n’est nullement attendu qu’une application soit entièrement en SwiftUI pour en tirer parti.8 L’élan de modernisation a toutefois un tranchant plus dur ailleurs dans le cycle : iOS 27 fait du cycle de vie basé sur les scènes d’UIKit une exigence de lancement, de sorte qu’une application que vous reconstruisez avec le dernier SDK ne se lance pas si elle n’a jamais adopté les scènes.
Ce que l’équipe SwiftUI a ajouté dans les labs
Deux clarifications issues des group labs UI Frameworks de la WWDC26 affinent le modèle d’invalidation que les sessions décrivent. Les deux sont paraphrasées à partir d’un enregistrement transcrit localement ; Apple ne publie aucune légende officielle pour les labs, le même manque de légendes qui traversait le Group Lab de l’équipe Swift elle-même où les ingénieurs du langage répondaient aux questions sur la concurrence et la feuille de route.
Déplacer du code de body vers une propriété calculée n’apporte aucun bénéfice d’invalidation. SwiftUI réexécute toujours la propriété chaque fois qu’il réexécute body, donc le déplacement est un gain de lisibilité et rien de plus.20 La frontière de performance apparaît un niveau au-dessus : extrayez un type de vue distinct et SwiftUI peut l’invalider indépendamment, en ne réexécutant que cette vue lorsque ses entrées changent plutôt que tout le body englobant. Optez pour une nouvelle vue, pas pour une nouvelle propriété, quand vous voulez que SwiftUI fasse moins de travail.
Chaque changement d’environnement invalide toutes les vues qui lisent cette valeur d’environnement. Lire l’environnement est peu coûteux ; le brassage de l’environnement ne l’est pas.21 Gardez les valeurs à évolution rapide hors de l’environnement (l’exemple du panel est l’heure courante), car une valeur qui se met à jour à chaque frame entraîne chaque lecteur dans une réévaluation à chaque fois qu’elle bouge. Faites descendre les valeurs volatiles par le chemin qui en a besoin et laissez l’environnement porter les choses qui restent immobiles.
Une session de lab ultérieure ajoute trois détails de mécanisme supplémentaires, tous paraphrasés à partir d’un enregistrement transcrit localement du SwiftUI Group Lab de la WWDC 2026, pour lequel Apple ne publie aucune légende officielle.
L’évaluation partielle du graphe explique où va réellement le travail préchargé. Le panel a décrit un lazy stack évaluant les body des cellules à venir dans le temps de frame qui reste après le rendu de la frame courante, puis s’arrêtant juste avant le début de la frame suivante.22 Le hic qu’ils ont signalé : un onAppear qui reconfigure une cellule au point qu’elle doive être de nouveau disposée, parce qu’il change la taille de la cellule, jette ce travail préchargé. Leur conseil était d’effectuer le travail de dimensionnement dans l’initialiseur de la cellule, pas dans body ni dans onAppear, afin que le préchargement survive.22
Le modèle mental de l’interopérabilité venait d’un panéliste cumulant plus d’une décennie sur UIKit. UIKit dispose de haut en bas, de la fenêtre vers l’intérieur jusqu’aux feuilles, tandis que SwiftUI construit de bas en haut, du nœud le plus interne vers l’extérieur.22 Lorsque les deux s’entremêlent, le panel a qualifié l’agencement en couches alternées de sandwich ou de gâteau, ce qui explique pourquoi un entremêlement profond devient subtil : chaque framework veut piloter la passe de disposition depuis l’extrémité opposée.22
Le point sur l’environnement ci-dessus a reçu un exemple concret saisissant. Interrogé sur le pire mésusage de l’environnement qu’ils aient vu, le panel a cité le fait de mettre la position de défilement dans l’environnement, qui se met à jour à chaque frame pendant le défilement et invalide donc chaque lecteur à chaque frame.22
Une seconde session du SwiftUI Group Lab a ajouté quatre détails de mécanisme supplémentaires, tous paraphrasés à partir d’un enregistrement transcrit localement du SwiftUI Group Lab de la WWDC 2026 (session 2) ; Apple ne publie aucune légende officielle pour les labs.
Le modificateur onGeometryChange(for:of:action:) se comporte mieux qu’il n’y paraît grâce à la manière dont ses deux closures répartissent le travail. La closure de transformation s’exécute avec la géométrie en direct à chaque frame, mais seule la valeur qu’elle renvoie conditionne le déclenchement de l’action, puisque le type de résultat est Equatable et que l’action ne s’exécute que lorsque cette valeur change.23 Ainsi, renvoyer une valeur grossière (un palier de taille ou un point de rupture de disposition, et non la taille brute) convertit un signal cadencé à la fréquence des frames en un signal qui se déclenche deux fois, aux seuils, plutôt que continûment. Le panel a associé cela à un avertissement selon lequel GeometryReader est coûteux pour les sous-vues qu’il enveloppe et devrait être confiné à un arrière-plan afin de mesurer sans piloter la disposition principale.23
Une bonne dynamic property peut remplacer purement et simplement l’essentiel du travail d’onChange. La méthode update() d’une DynamicProperty s’exécute immédiatement avant le body de la vue, de sorte qu’un property wrapper personnalisé peut fournir une valeur déjà en cache (l’exemple du panel était une image) de manière synchrone à ce moment-là et éviter l’aller-retour d’onAppear ainsi que le re-rendu qu’il déclenche.24 La formulation du panel était que la plupart des usages d’onChange peuvent être remplacés par une dynamic property bien construite.24
Une vue qui dessine au-delà de ses limites de disposition déclarées à l’intérieur d’une ScrollView peut être éliminée, car le système la décide hors écran d’après les limites qu’on lui a indiquées, pas d’après l’endroit où elle peint réellement.25 Le panel a désigné cela comme le comportement défaillant concret derrière les listes déroulantes et les overlays personnalisés qui débordent de leur vue hôte puis disparaissent en plein défilement, et a noté que le même danger s’applique au contenu d’overlay qui s’étend au-delà de son ancre.25
Présenter un overlay plein écran au-dessus de tout, y compris des sheets, n’a aucune réponse propre purement SwiftUI. Le conseil du panel était de descendre vers une nouvelle UIWindow via le cycle de vie basé sur les scènes d’UIKit, avec le principe plus profond que la dernière fenêtre l’emporte et qu’il doit y avoir une source de vérité unique pour ce qui se trouve au sommet.26 Ils ont ajouté que la meilleure solution consiste souvent à restaurer la pile de navigation que l’utilisateur attend plutôt qu’à jeter une couverture perturbatrice par-dessus tout.26
Quoi adopter en premier
Une version expliquée comme un mécanisme récompense un ordonnancement par effet de levier, pas par nouveauté.
- Passez les classes de modèle à
@Observabledans votre code AppKit/UIKit. Cela supprime les appels manuels àneedsDisplay, dote chaque méthode de dessin et de disposition observante d’un redessin automatique, et constitue le prérequis qui rend trivial un futur ajout d’unNSHostingView.814 C’est le geste le plus faiblement risqué et au bénéfice immédiat le plus élevé des trois sessions. - Auditez les lazy stacks à la recherche de nombres de sous-vues dynamiques. Toute feuille
ForEachqui renvoie conditionnellement zéro ou une sous-vue, ou qui déroule un optionnel dans sonbody, maintient des vues en vie par index ; déplacez le filtre vers unPredicatesur uneQuery(ou plus haut dans la hiérarchie) et la mémoire comme la performance du défilement vers un élément s’améliorent.1 - Déplacez la configuration des vues hors d’
onAppearet vers les initialiseurs. Le préchargement n’aide que si le travail préchargé survit ; une configuration qui mute la taille ou le contenu dansonAppearjette ce travail.1 C’est un gain discret et large de fluidité du défilement. - Remplacez les lectures absolues du décalage de défilement par des API de visibilité relative. Tout ce qui est conditionné au décalage de contenu va dériver ;
.onScrollTargetVisibilityChangese conditionne aux lignes réellement visibles.21 - N’optez pour les shaders que là où un petit effet justifie sa place. Commencez par
colorEffectoudistortionEffect; n’escaladez verslayerEffectque lorsqu’un effet doit échantillonner les voisins, et pilotez tout mouvement avec un horodatageTimelineViewplutôt que d’attendre de l’état à l’intérieur du shader.4567
Le fil conducteur des trois : prédisez le framework avant de pousser dessus. Les lazy stacks estiment, les shaders oublient, et l’interopérabilité est une jointure, pas une réécriture. Construisez en gardant ces trois faits à l’esprit et le reste suit.
FAQ
Pourquoi la position de défilement de mon lazy stack SwiftUI saute-t-elle ou dérive-t-elle ?
Parce qu’un LazyVStack ne charge pas les vues hors écran, il estime leurs hauteurs et l’espace au-dessus de la région visible, de sorte que le décalage de contenu absolu est une estimation que le framework corrige à mesure qu’il apprend la disposition réelle (après un changement d’orientation, par exemple, il réconcilie l’estimation lorsque vous faites défiler de nouveau vers le haut).1 Si vous conditionnez l’interface au décalage absolu, le seuil dérive à mesure que les estimations se stabilisent. Utilisez plutôt .onScrollTargetVisibilityChange, qui se déclenche en fonction des sous-vues réellement visibles.2
Comment maintenir un défilement fluide dans un lazy stack SwiftUI sous iOS 27 ?
Laissez le préchargement faire son travail : configurez les vues dans leur initialiseur afin que le travail que le lazy stack effectue avant l’apparition d’une vue ne soit pas jeté, et évitez de muter la taille ou le contenu d’une vue dans onAppear.1 Évitez aussi un nombre dynamique de sous-vues dans les feuilles ForEach et évitez les changements de disposition (comme une hauteur pilotée par onGeometryChange) après l’apparition d’une vue, car les deux forcent le stack à refaire du travail ou à recalculer des positions en plein défilement.1
Quand devrais-je utiliser colorEffect, distortionEffect ou layerEffect ?
Utilisez colorEffect pour transformer la couleur de chaque pixel à partir de sa position et de sa couleur d’origine (un filtre en niveaux de gris, par exemple).4 Utilisez distortionEffect pour les effets géométriques, où vous mappez une position de sortie vers une position source à échantillonner.5 Utilisez layerEffect lorsque le pixel de sortie dépend de plus d’un pixel d’entrée, car il donne au shader toute la couche de la vue pour échantillonner les voisins ou la région entière, ce dont le flou et le domain warping ont besoin.6
Comment animer un shader Metal dans SwiftUI ?
Les shaders sont sans état : ils ne gardent aucune mémoire de la frame précédente et ne dépendent que de leurs paramètres, vous ne pouvez donc pas animer depuis l’intérieur du shader.3 Injectez une valeur qui change au fil du temps. Une TimelineView sur un calendrier d’animation se déclenche à chaque frame avec un horodatage ; transmettez cet horodatage au shader en tant que paramètre (la session l’ajoute à la position d’échantillonnage du bruit) et l’effet s’anime.73
Puis-je ajouter SwiftUI à une application AppKit ou UIKit existante sans la réécrire ?
Oui, et la session est explicite sur le fait qu’aucune application n’a besoin d’être entièrement en SwiftUI pour en tirer parti.8 Marquez votre modèle @Observable pour qu’AppKit et UIKit redessinent automatiquement, puis intégrez des vues SwiftUI avec NSHostingView, transposez les gesture recognizers existants avec NSGestureRecognizerRepresentable, construisez des menus avec NSHostingMenu et rattachez des scènes SwiftUI depuis votre app delegate avec NSHostingSceneRepresentation ; tout cela est livré dans les versions 2026 ou antérieures.816171819
Le cluster Apple Ecosystem complet : le substrat SwiftUI (result builders, types opaques, l’arbre de vues à valeurs) qui explique pourquoi un lazy stack résout les structures de vue en un ensemble différent de sous-vues ; la surface SwiftUI d’iOS 27 (réordonnancement, documents, toolbars, erreurs) à côté de laquelle se place cette histoire de performance et d’interopérabilité ; les rouages internes de @Observable qui pilotent désormais le redessin automatique dans AppKit et UIKit aussi ; et les patterns Liquid Glass dont la session d’interopérabilité attribue l’implémentation inter-frameworks à un SwiftUI partagé. Le hub est la série Apple Ecosystem. Pour un contexte plus large d’iOS avec des agents IA, consultez le guide de développement d’agents iOS.
Références
-
Apple, WWDC26 session 321, « Dive into lazy stacks and scrolling with SwiftUI ». developer.apple.com/videos/play/wwdc2026/321. Couvre la disposition des lazy stacks et l’estimation des hauteurs, le décalage de contenu estimé, la résolution des structures de vue en sous-vues, le piège du nombre de sous-vues dynamique, le préchargement à travers les échéances de frames, et la configuration via
onAppearversus l’initialiseur. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
Apple, WWDC26 session 321, « Dive into lazy stacks and scrolling with SwiftUI ». La session présente
onScrollTargetVisibilityChange(un modificateur dont la closure s’exécute lorsque l’ensemble des cibles de défilement visibles change) comme l’alternative stable, fondée sur la visibilité relative, aux lectures absolues du décalage de contenu. ↩↩↩↩↩ -
Apple, WWDC26 session 322, « Compose advanced graphics effects with SwiftUI ». developer.apple.com/videos/play/wwdc2026/322. Présente les effets comme un pipeline composable ; couvre le flou, les trois points d’entrée de shaders, la technique de domain warp avec
NoiseTexture, les shaders sans état pilotés par le temps, et le rattachement par guide d’alignement. ↩↩↩↩↩↩↩↩↩↩↩↩↩ -
Documentation développeur Apple :
colorEffect(_:isEnabled:). Renvoie une nouvelle vue qui applique un shader transformant la couleur de chaque pixel, à partir de sa position et de sa couleur d’origine. ↩↩↩↩ -
Documentation développeur Apple :
distortionEffect(_:maxSampleOffset:isEnabled:). Applique un shader qui mappe la position de chaque pixel vers une position source à échantillonner, pour des effets géométriques. ↩↩↩↩ -
Documentation développeur Apple :
layerEffect(_:maxSampleOffset:isEnabled:). Applique un shader comme effet de couche avec accès à la couche entière de la vue, permettant à chaque pixel de sortie d’échantillonner plusieurs pixels d’entrée. ↩↩↩↩ -
Documentation développeur Apple :
TimelineView. Une vue qui met à jour son contenu selon un calendrier ; sur un calendrier d’animation, elle fournit l’horodatage par frame que la session 322 injecte dans son shader. ↩↩↩↩ -
Apple, WWDC26 session 272, « Use SwiftUI with AppKit and UIKit ». developer.apple.com/videos/play/wwdc2026/272. Couvre le redessin automatique
@Observabledans AppKit/UIKit, le rétro-déploiement de l’observation via Info.plist,Canvas,NSHostingView,NSGestureRecognizerRepresentable,NSHostingMenuetNSHostingSceneRepresentation. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
Documentation développeur Apple :
NSGestureRecognizerRepresentable. Un protocole qui enveloppe unNSGestureRecognizerpour l’utiliser comme geste SwiftUI, implémenté avecmakeNSGestureRecognizerethandleNSGestureRecognizerAction. ↩ -
Documentation développeur Apple :
MenuBarExtra. Une scène qui rend un élément de barre de menus ; la session 272 la rattache à une application AppKit existante viaNSHostingSceneRepresentation. ↩ -
Documentation développeur Apple :
scrollTransition(_:axis:transition:). Applique une transition à mesure qu’une vue défile dans une scroll view ; la session 321 avertit qu’une transformation poussant une vue dans le rectangle visible peut désynchroniser un lazy stack. ↩ -
Documentation développeur Apple :
onChange(of:initial:_:). Exécute une action lorsqu’une valeur change ; la session 322 l’utilise pour recentrer la ligne de transcription courante. ↩ -
Documentation développeur Apple :
alignmentGuide(_:computeValue:). Définit le guide d’alignement d’une vue afin que le système de disposition la positionne sémantiquement ; la session 322 redéfinit un guide inférieur pour rattacher le bord supérieur d’une sous-vue au bord inférieur de son conteneur. ↩ -
Documentation développeur Apple :
Observable. La macro qui fait participer les propriétés mutables d’une classe au système d’observation, que AppKit et UIKit suivent pour un redessin automatique dans les versions 2026. ↩↩ -
Documentation développeur Apple :
Canvas. Une vue de dessin en mode immédiat dont la closure reçoit unGraphicsContext; la session 272 l’utilise pour redessiner le sélecteur de couleur circulaire et signalewithCGContextpour réutiliser du code Core Graphics. ↩ -
Documentation développeur Apple :
NSHostingView. Une sous-classe deNSViewqui héberge une hiérarchie de vues SwiftUI à l’intérieur d’un arbre de vues AppKit. ↩↩ -
Documentation développeur Apple :
NSViewRepresentable. Un wrapper qui permet à unNSViewde participer à une hiérarchie de vues SwiftUI ; la session 272 le nomme aux côtés deNSGestureRecognizerRepresentablecomme faisant partie de la famille des representable. ↩↩ -
Documentation développeur Apple :
NSHostingMenu. Une sous-classe deNSMenuqui rend une vue SwiftUI comme contenu de menu, ajoutée au menu principal comme sous-menu d’unNSMenuItem. ↩↩ -
Documentation développeur Apple :
keyboardShortcut(_:modifiers:). Assigne un raccourci clavier à l’action d’un contrôle ; la session 272 en ajoute un au bouton de menu afin que les périphériques de saisie qui ne peuvent pas faire de force-click atteignent quand même la fonctionnalité. ↩↩ -
Apple, group lab UI Frameworks de la WWDC 2026, session 8002. Paraphrasé à partir d’un enregistrement transcrit localement ; aucune transcription officielle n’est publiée. L’équipe a clarifié que déplacer du code de
bodyvers une propriété calculée n’est qu’un changement de lisibilité, et que la frontière d’invalidation indépendante apparaît lorsque vous extrayez un type de vue distinct. ↩ -
Apple, group lab UI Frameworks de la WWDC 2026, session 8003. Paraphrasé à partir d’un enregistrement transcrit localement ; aucune transcription officielle n’est publiée. L’équipe a noté que chaque changement d’environnement invalide toutes les vues lisant cette valeur, et a conseillé de garder les valeurs à évolution rapide (l’exemple du panel était l’heure courante) hors de l’environnement. ↩
-
Apple, WWDC26 session 8006, « SwiftUI Group Lab ». developer.apple.com/videos/play/wwdc2026/8006. Paraphrasé à partir d’un enregistrement transcrit localement du SwiftUI Group Lab de la WWDC 2026 ; Apple ne publie aucune légende officielle pour les labs. Source de l’évaluation partielle du graphe dans le temps de frame restant (et de l’avertissement sur le redimensionnement par
onAppear, avec le dimensionnement effectué dans l’init), de l’agencement d’interopérabilité « sandwich ou gâteau » UIKit-de-haut-en-bas-versus-SwiftUI-de-bas-en-haut, et de l’exemple de la position de défilement dans l’environnement qui invalide chaque lecteur à chaque frame. ↩↩↩↩↩ -
Apple, SwiftUI Group Lab de la WWDC 2026 (session 2). Paraphrasé à partir d’un enregistrement transcrit localement du SwiftUI Group Lab de la WWDC 2026 (session 2) ; Apple ne publie aucune légende officielle pour les labs. Source du mécanisme
onGeometryChangetransformation-conditionne-action (renvoyer une valeur grossière pour se déclencher aux seuils) et du conseil de confiner unGeometryReadercoûteux à un arrière-plan. La forme à deux closures est documentée suronGeometryChange(for:of:action:): la closure de transformationof:dérive une valeurEquatabledu geometry proxy et la closureaction:ne s’exécute que lorsque cette valeur change. ↩↩ -
Apple, SwiftUI Group Lab de la WWDC 2026 (session 2). Paraphrasé à partir d’un enregistrement transcrit localement du SwiftUI Group Lab de la WWDC 2026 (session 2) ; Apple ne publie aucune légende officielle pour les labs. Source du remplacement de l’essentiel du travail d’
onChangepar une dynamic property qui fournit une valeur en cache de manière synchrone. Le protocoleDynamicPropertyd’Apple définit une méthodeupdate()que SwiftUI appelle immédiatement avant le rendu dubodyd’une vue afin que la propriété détienne sa valeur la plus récente. ↩↩ -
Apple, SwiftUI Group Lab de la WWDC 2026 (session 2). Paraphrasé à partir d’un enregistrement transcrit localement du SwiftUI Group Lab de la WWDC 2026 (session 2) ; Apple ne publie aucune légende officielle pour les labs. Source de l’élimination des vues qui dessinent au-delà de leurs limites de disposition déclarées à l’intérieur d’une
ScrollView(la défaillance derrière les listes déroulantes et overlays personnalisés qui débordent), et de la note selon laquelle le même danger s’applique au contenu d’overlayqui s’étend au-delà de son ancre. ↩↩ -
Apple, SwiftUI Group Lab de la WWDC 2026 (session 2). Paraphrasé à partir d’un enregistrement transcrit localement du SwiftUI Group Lab de la WWDC 2026 (session 2) ; Apple ne publie aucune légende officielle pour les labs. Source de l’absence d’un overlay plein écran propre purement SwiftUI au-dessus des sheets, de la descente vers une nouvelle
UIWindowvia le cycle de vie basé sur les scènes d’UIKit, du principe « la dernière fenêtre l’emporte / source de vérité unique », et de la préférence pour la restauration de la pile de navigation plutôt qu’une couverture perturbatrice. ↩↩