Les nouveautés de SwiftUI pour iOS 27
Chaque version de SwiftUI vous indique où se trouvaient les points de tension du framework par ce qu’Apple a décidé de reconstruire. La réponse d’iOS 27 est exceptionnellement large : les listes obtiennent une réorganisation de première classe, les documents reçoivent une nouvelle famille de protocoles lecteur/rédacteur, les barres d’outils gagnent un modèle de débordement avec priorité explicite, et la présentation des erreurs obtient enfin une liaison à laquelle vous pouvez confier directement une Error. La version fait évoluer quatre surfaces à la fois, et la plupart des applications en touchent au moins deux.1
La tentation, avec une version aussi vaste, est de tout adopter. Le meilleur choix consiste à reconnaître quels ajouts changent votre façon de construire par opposition à ceux qui ne sont que des surcharges de commodité sur des modèles que vous utilisez déjà. La réorganisation par glisser et les protocoles de document relèvent de la première catégorie : ils remplacent du code que vous écriviez à la main. Les alertes basées sur un élément et AsyncImage(request:) relèvent de la seconde : ils suppriment un contournement. Cet article trie la surface SwiftUI d’iOS 27 selon ces fils conducteurs, avec les véritables déclarations et le raisonnement expliquant quand chacun gagne sa place dans votre code.
Dans la session 269, Apple présente la version comme quatre grands fils conducteurs qui avancent ensemble : un look et un ressenti affinés, une nouvelle API de document puissante, de nouvelles façons d’interagir et un travail sur les performances, plutôt qu’une seule fonctionnalité phare.35
En bref / Points clés
- Les listes et les conteneurs personnalisés obtiennent une réorganisation déclarative :
reorderContainer(for:isEnabled:move:)marque un conteneur,reorderable()surDynamicViewContenty inscrit les lignes, et vous recevez uneReorderDifferenceau lieu d’écrire le calcul d’index à la main.23 - Les conteneurs de glissement paresseux arrivent via
dragContainer(for:itemID:in:_:)accompagné dedraggable(containerItemID:containerNamespace:), qui ne transporte qu’un identifiant afin que le framework récupère les charges utiles de manière paresseuse au démarrage d’un glissement.45 - Un nouveau modèle de document débarque sous la forme de
ReadableDocumentetWritableDocument(avecDocumentReader/DocumentWriterqui effectuent le travail sur disque etFileWrapperDocumentReader/FileWrapperDocumentWriterpour le cas simple), adossé àURLDocumentConfiguration.6789101112 - Les barres d’outils gagnent
ToolbarOverflowMenu, le placementtopBarPinnedTrailingetvisibilityPriority(_:), de sorte que vous décidez quels contrôles survivent lorsque la barre manque de place.131415 - La présentation des erreurs obtient
alert(error:actions:message:)ainsi quealert(_:item:actions:)/confirmationDialogbasés sur un élément, en plus deAsyncImage(request:)pour un contrôle complet deURLRequestet deasyncImageURLSession(_:)pour partager une session.1617181920 - Pour compléter la surface :
swipeActions(...onPresentationChanged:),swipeActionsContainer(),NavigationTransition.crossFade,TabRole.prominent,UIHostingSceneDelegateetGestureInputKinds.212223242526
La réorganisation par glisser devient déclarative
Réorganiser une liste dans SwiftUI signifiait autrefois onMove, un ensemble d’index et un décalage de destination que vous traduisiez en votre propre mutation de modèle. iOS 27 remplace cela par une déclaration en deux parties : vous marquez un conteneur comme réorganisable, et vous marquez son contenu comme participant. Le conteneur vous remet alors un diff structuré. Le modèle que ce diff fait muter est lui-même mieux surveillé dans iOS 27 : SwiftData a obtenu des API d’observation et d’historique persistant de première classe lors de la même version, de sorte qu’un magasin que vous réorganisez dans une vue reste synchronisé partout où il est lu.
Le cas de la collection unique est le plus courant. Vous déclarez reorderContainer(for:isEnabled:move:) sur le conteneur et reorderable() sur le DynamicViewContent qu’il contient :23
struct LandmarkList: View {
@State private var landmarks: [Landmark]
var body: some View {
List {
ForEach(landmarks) { landmark in
LandmarkRow(landmark: landmark)
}
.reorderable()
}
.reorderContainer(for: Landmark.self) { difference in
// Apply the reorder to your model.
landmarks.apply(difference)
}
}
}
La signature vous indique le contrat. Le modificateur de conteneur est générique sur Item : Identifiable et vous donne une ReorderDifference<Item.ID, ReorderableSingleCollectionIdentifier> :2
nonisolated func reorderContainer<Item>(
for item: Item.Type,
isEnabled: Bool = true,
move: @escaping (ReorderDifference<Item.ID, ReorderableSingleCollectionIdentifier>) -> ()
) -> some View where Item : Identifiable, Item.ID : Sendable
Deux décisions de conception comptent ici. Premièrement, le framework pilote l’interaction : comme le décrit la documentation d’Apple, un élément réorganisable peut être soulevé par un geste de glissement, une vue d’espace réservé prend sa position pour montrer où l’élément va atterrir, et l’espace réservé suit le glissement à travers le conteneur.2 Vous ne construisez plus cet affordance ; vous décrivez la collection et réagissez au résultat. Deuxièmement, isEnabled est un paramètre plutôt qu’un modificateur distinct, de sorte qu’une bascule de mode édition devient un seul booléen au lieu d’une construction de vue conditionnelle.
Lorsqu’un conteneur contient plus d’une collection, vous recourez à la surcharge qui prend un type d’identifiant de collection, reorderContainer(for:in:isEnabled:move:) :27
nonisolated func reorderContainer<Item, CollectionID>(
for item: Item.Type,
in collectionID: CollectionID.Type,
isEnabled: Bool = true,
move: @escaping (ReorderDifference<Item.ID, CollectionID>) -> ()
) -> some View where Item : Identifiable, CollectionID : Hashable, CollectionID : Sendable, Item.ID : Sendable
La ReorderDifference est désormais indexée à la fois par l’ID de l’élément et un ID de collection, de sorte qu’un déplacement qui passe d’une section à une autre devient exprimable. La recommandation d’Apple est directe : utilisez la surcharge multi-collections lorsque votre conteneur comporte plusieurs collections, et la commodité de collection unique lorsqu’il n’en a qu’une.27 Le modificateur reorderable() accepte l’identifiant de collection correspondant lorsque vous devez lever l’ambiguïté.3
reorderable() à un ForEach et un reorderContainer à son parent, en traitant la différence dans la closure.
Dans la session 271, Apple montre le même code de réorganisation transposé sans modification d’une List vers une LazyVGrid, car les modificateurs décrivent la collection plutôt que le conteneur, de sorte que la réorganisation fonctionne dans tout conteneur qui prend en charge le glisser-déposer.36
Conteneurs de glissement paresseux
L’autre moitié de l’histoire du glissement dans iOS 27 concerne le coût. Le draggable classique vous oblige à produire la charge utile en amont, ce qui signifie que le framework peut avoir besoin de matérialiser un élément (et parfois de le rendre) avant même qu’un glissement ne commence. Pour une liste paresseuse de milliers de lignes, c’est un travail gaspillé.
dragContainer(for:itemID:in:_:) définit un conteneur de vues déplaçables et ne demande la charge utile qu’une seule fois, sous la forme d’une closure sur les identifiants glissés :4
nonisolated func dragContainer<ItemID, Item, Data>(
for itemType: Item.Type = Item.self,
itemID: KeyPath<Item, ItemID>,
in namespace: Namespace.ID? = nil,
_ payload: @escaping (Array<ItemID>) -> Data
) -> some View where ItemID : Hashable, ItemID : Sendable, Item : Transferable, Item == Data.Element, Data : Collection
À l’intérieur de ce conteneur, chaque enfant déplaçable utilise draggable(containerItemID:containerNamespace:), qui ne transporte que l’identifiant de l’élément :5
nonisolated func draggable<ItemID>(
containerItemID: ItemID,
containerNamespace: Namespace.ID? = nil
) -> some View where ItemID : Hashable, ItemID : Sendable
La raison pour laquelle c’est le meilleur réglage par défaut pour les grandes collections figure dans la description même d’Apple : parce que le modificateur ne fournit qu’un identifiant et non la charge utile, il fonctionne de manière paresseuse, de sorte que le framework ne demande les éléments réellement glissés qu’au démarrage du glissement et n’a pas à rendre une vue pour accéder à sa charge utile.5 Une valeur Fruit qui s’identifie par son nom (et ne se conforme jamais à Identifiable) peut quand même être la source d’un glissement multi-éléments, car le conteneur s’indexe sur le KeyPath que vous fournissez plutôt que sur une conformité à Identifiable.4
À signaler pour le code multiplateforme : dragContainer et draggable(containerItemID:containerNamespace:) sont disponibles sur macOS 26.0, là où la majeure partie du reste de cette version est en macOS 27.0 ; l’API de glissement paresseux est donc une que vous auriez déjà pu adopter sur le Mac.45
Un nouveau modèle de document
DocumentGroup et FileDocument ont porté les applications de documents de SwiftUI pendant des années, mais les côtés lecture et écriture étaient entremêlés dans une seule conformité. iOS 27 les sépare. La lecture et l’écriture sont désormais des protocoles distincts, la logique sur disque constitue sa propre couche, et un type lecture-écriture compose les deux.
Les deux protocoles de premier niveau sont ReadableDocument et WritableDocument :67
protocol ReadableDocument : AnyObject
protocol WritableDocument : AnyObject
Un type en lecture seule se conforme à ReadableDocument seul. Un type lecture-écriture se conforme aux deux, et Apple fournit un typealias Document qui regroupe les deux afin que vous puissiez adopter la paire sans nommer chacun.67 Les deux sont liés à une classe (: AnyObject), ce qui est le signal visible que les documents sont des types par référence dans ce modèle.
Les E/S disque réelles passent dans une abstraction de lecteur et de rédacteur, DocumentReader et DocumentWriter, chacune paramétrée par le type d’instantané que votre document sérialise :89
protocol DocumentReader<Snapshot>
protocol DocumentWriter<Snapshot>
La plupart des applications ne les implémentent jamais à la main. Pour les documents de petite et moyenne taille qui n’ont besoin d’aucune logique personnalisée, SwiftUI livre FileWrapperDocumentReader et FileWrapperDocumentWriter, chacun adossé à un file wrapper et décrit par Apple comme le choix efficace pour le cas simple :1011
struct FileWrapperDocumentReader<Snapshot>
struct FileWrapperDocumentWriter<Snapshot>
Ce qui relie le document ouvert est URLDocumentConfiguration, une classe main-actor qui contient les paramètres et les propriétés d’un document ouvert :12
@MainActor final class URLDocumentConfiguration
L’exportation passe par un fileExporter mis à jour qui prend un WritableDocument dont le rédacteur cible une URL :28
nonisolated func fileExporter<D>(
isPresented: Binding<Bool>,
document: D?,
contentType: UTType? = nil,
defaultFilename: String? = nil,
onCompletion: @escaping (Result<URL, any Error>) -> Void,
onCancellation: (() -> Void)? = nil
) -> some View where D : WritableDocument, D.Writer.Destination == URL
La contrainte D.Writer.Destination == URL est l’élément porteur : l’exportateur n’accepte qu’un document inscriptible dont le rédacteur écrit vers une URL, ce qui correspond exactement au cas fichier-sur-disque que la boîte de dialogue système gère. Apple documente le cycle de vie avec précision : la boîte de dialogue n’apparaît que lorsque document est non nil, isPresented est défini sur false avant l’exécution de onCompletion, et une annulation par l’utilisateur définit isPresented sur false et appelle onCancellation.28 La séparation entre lisible et inscriptible est ce qui permet à une fonctionnalité réservée à la consultation d’importer un document sans jamais se conformer au côté écriture.
Des barres d’outils qui décident de ce qui survit
Les barres d’outils manquent de place. Un iPhone en largeur compacte, une fenêtre Mac redimensionnée ou un champ de recherche actif peuvent tous laisser moins d’emplacements que vous n’avez de contrôles. Avant iOS 27, le framework prenait les décisions d’éviction à votre place. Désormais, c’est vous qui les prenez.
ToolbarOverflowMenu est la surface de débordement explicite. Apple le décrit comme des actions qui sont toujours placées dans le menu de débordement de la barre d’outils quel que soit le mode de barre d’outils, la plateforme ou la possibilité de personnalisation, et sur iOS et visionOS ce contenu atterrit dans le menu de débordement de la barre de navigation :13
nonisolated struct ToolbarOverflowMenu<Content> where Content : View
Pour les contrôles qui devraient résister au débordement, le nouveau placement topBarPinnedTrailing épingle un élément au bord de fuite de la barre d’outils :14
static let topBarPinnedTrailing: ToolbarItemPlacement
La nuance que documente Apple est que les éléments épinglés ne se déplacent vers le menu de débordement que lorsque la recherche est active et qu’il n’y a pas assez de place, et que sur iOS et visionOS la barre supérieure est la barre de navigation.14 Ainsi, topBarPinnedTrailing est destiné aux un ou deux contrôles que vous ne voulez jamais voir enfouis à moins que la recherche ne force la chose.
Lorsque le choix est relatif plutôt qu’absolu, visibilityPriority(_:) sur ToolbarContent classe les éléments afin que le framework connaisse l’ordre d’éviction :15
@MainActor @preconcurrency
func visibilityPriority(_ priority: ToolbarItemVisibilityPriority) -> some ToolbarContent
La règle d’Apple : lorsque l’espace de la barre d’outils est limité, les éléments de priorité inférieure se déplacent dans le menu de débordement avant les éléments de priorité supérieure.15 Un contrôle important peut se trouver au bord de fuite tout en restant affiché à mesure que la fenêtre rétrécit. Associé à topBarPinnedTrailing et ToolbarOverflowMenu, vous disposez désormais d’un vocabulaire complet pour une dégradation gracieuse : épinglez l’essentiel, hiérarchisez le reste et acheminez le toujours-secondaire vers le débordement.
Un modificateur connexe relie la barre d’outils au comportement de défilement et au chrome Liquid Glass introduit par iOS 26. toolbarMinimizeBehavior(_:for:) active la minimisation de la barre d’outils en réponse au défilement, et Apple note que lorsque la barre de navigation se minimise, une barre d’onglets supérieure intégrée se minimise avec elle :29
nonisolated func toolbarMinimizeBehavior(
_ behavior: ToolbarMinimizeBehavior,
for bars: ToolbarPlacement...
) -> some View
Le placement pris en charge est la barre de navigation, et par défaut la zone de sécurité s’ajuste à mesure que la barre se minimise.29 Si vous avez adopté des barres d’outils Liquid Glass et vouliez qu’elles s’effacent à mesure que l’utilisateur lit, c’est le modificateur qui le fait.
Alertes basées sur un élément et présentation des erreurs
L’API d’alerte de SwiftUI a longtemps eu une forme booléenne (alert(_:isPresented:)) qui vous oblige à ranger les données de l’alerte dans un @State distinct aux côtés du drapeau de présentation. iOS 27 ajoute les formes basées sur un élément que les API de sheet et de popover possédaient déjà, de sorte que les données sont le déclencheur.
L’alerte basée sur un élément se présente dès que la liaison est non nil et transmet la valeur déballée à votre constructeur d’actions :17
nonisolated func alert<A, T>(
_ title: Text,
item data: Binding<T?>,
@ContentBuilder actions: (T) -> A
) -> some View where A : View
Il existe une surcharge correspondante qui ajoute un constructeur de message, alert(_:item:actions:message:), et une paire équivalente pour confirmationDialog, de sorte que le même modèle piloté par l’élément se reporte sur les alertes et les dialogues.183031 Le contrat d’Apple est le même dans chaque cas : les données doivent être non nil pour que la présentation apparaisse, et les modifications que vous apportez aux données après la présentation sont ignorées.18
Les surcharges de présentation des erreurs sont la capacité véritablement nouvelle. Au lieu de mapper une erreur vers une structure personnalisée, vous liez directement une Error :16
nonisolated func alert<E, A, M>(
error: Binding<E?>,
@ContentBuilder actions: (E) -> A,
@ContentBuilder message: (E) -> M
) -> some View where E : Error, A : View, M : View
Le comportement est ce qui rend son adoption intéressante. Lorsque la valeur d’erreur n’est pas nil, le système présente l’alerte, et le titre est déduit de l’errorDescription de l’erreur si l’erreur est une LocalizedError ; sinon le titre se rabat sur la description localisée.16 Une LocalizedError que vous avez déjà définie pilote désormais son propre titre d’alerte sans câblage supplémentaire. Une surcharge plus simple, alert(error:actions:), supprime le constructeur de message lorsqu’une action OK suffit :19
nonisolated func alert<E, A>(
error: Binding<E?>,
@ContentBuilder actions: () -> A
) -> some View where E : Error, A : View
struct EditorView: View {
@State private var saveError: SaveError?
var body: some View {
Form { /* ... */ }
.alert(error: $saveError) { error in
Button("Retry") { retry() }
Button("Cancel", role: .cancel) { }
} message: { error in
Text(error.recoverySuggestion ?? "")
}
}
}
Le modèle qui disparaît : une structure AlertError confectionnée à la main, un wrapper identifiable et le code de mappage entre votre véritable type d’erreur et la source de données de l’alerte. Vous liez l’Error? que votre code produit déjà.
AsyncImage gagne en maturité avec URLRequest
AsyncImage était livré avec un initialiseur URL et aucun moyen de définir des en-têtes, une politique de cache ou un délai d’expiration. Les ajouts d’iOS 27 prennent une URLRequest, qui est l’objet qui transporte les trois.
La forme la plus simple charge et affiche une image à partir d’une requête :20
nonisolated init(request: URLRequest, scale: CGFloat = 1) where Content == Image
La forme par phases vous donne l’AsyncImagePhase pour piloter une closure de contenu, et Apple note que vous pouvez spécifier la politique de cache et l’intervalle de délai d’expiration via la requête :32
nonisolated init(
request: URLRequest?,
scale: CGFloat = 1,
transaction: Transaction = Transaction(),
@ContentBuilder content: @escaping (AsyncImagePhase) -> Content
)
Il existe aussi une forme content/placeholder pour la répartition courante « affiche ceci jusqu’au chargement, affiche cela en cas de succès ».33 Le comportement des trois est le contrat documenté d’AsyncImage : SwiftUI affiche un espace réservé jusqu’à ce que le chargement se termine, échange l’image en cas de succès et conserve l’espace réservé en cas d’échec.20
Le modificateur compagnon est asyncImageURLSession(_:), qui remet aux instances d’AsyncImage à l’intérieur d’une vue une URLSession avec laquelle effectuer les récupérations :34
nonisolated func asyncImageURLSession(_ urlSession: URLSession) -> some View
var body: some View {
List(avatars) { avatar in
AsyncImage(request: URLRequest(url: avatar.url))
.frame(width: 44, height: 44)
}
.asyncImageURLSession(authenticatedSession)
}
La combinaison est la réponse au chargement d’images authentifiées. Une requête vous permet d’attacher un en-tête Authorization ou une politique de cache personnalisée ; le modificateur de session permet à tout un sous-arbre de partager une seule URLSession configurée (en-têtes personnalisés, un cache disque, un proxy) au lieu que chaque AsyncImage se rabatte sur la session partagée. Pour une application qui charge des avatars derrière un jeton, c’est la différence entre fonctionner et ne pas fonctionner.
Aussi au programme
Plusieurs ajouts plus modestes méritent chacun une ligne, car chacun supprime une friction précise.
swipeActions gagne une surcharge avec une closure onPresentationChanged: qui se déclenche avec true lorsque les actions de balayage d’une ligne deviennent visibles et false lorsqu’elles se ferment, de sorte que vous pouvez assombrir une ligne ou mettre à jour le chrome environnant pendant que les actions s’affichent.21 Pour les mises en page de ligne personnalisées construites sur ScrollView ou LazyVStack plutôt que sur List, swipeActionsContainer() coordonne la fermeture et l’exclusion mutuelle entre les lignes comme List le fait déjà automatiquement (l’appliquer à une List est sans effet).22
NavigationTransition.crossFade est une transition qui réalise un fondu enchaîné entre les vues apparaissante et disparaissante ; spécifiée sur une sheet, elle fait apparaître la sheet en fondu par-dessus le contenu au lieu de la déplacer vers le haut pour couvrir le contenu.23 TabRole.prominent confère à un onglet un traitement visuel proéminent dans les barres d’onglets prises en charge, et Apple note qu’en l’absence d’onglet .prominent explicite, un onglet de rôle .search peut recevoir le traitement proéminent par défaut.24
UIHostingSceneDelegate étend UISceneDelegate pour faire le pont avec les scènes SwiftUI, permettant à UIKit d’activer une scène SwiftUI déclarée dans la propriété statique rootScene de la classe conforme.25 (C’est le seul élément ici qui remonte à iOS 26.0 sur la plupart des plateformes, atteignant tvOS dans la version bêta 27.0.25) Cette plomberie de scène compte plus qu’il n’y paraît, car iOS 27 fait aussi du cycle de vie UIKit basé sur les scènes une exigence stricte : une application construite avec le dernier SDK qui ne l’a pas adopté échoue purement et simplement au lancement. Et GestureInputKinds est un ensemble d’options qui spécifie quels types d’entrée un geste doit reconnaître, le fondement des gestes qui distinguent, disons, le toucher du pointeur.26
ContentBuilder unifie les result builders
Les ajouts ci-dessus relèvent de la surface d’API. Un changement du cycle 2027 relève de la plomberie, et il touche chaque vue que vous compilez plutôt qu’une seule que vous adoptez. La session 269 le présente à travers une erreur que la plupart des développeurs SwiftUI ont rencontrée : « The compiler is unable to type-check this expression in reasonable time. »37
La cause est la résolution de surcharge. Une vue qui enveloppe son contenu dans une Section, un Group et un ForEach force le compilateur à parcourir un arbre de décision. Comme l’explique Apple, « first the compiler has to select which overload of Section to use. Section can be initialized with a builder that produces either a View, or TableRowContent. To know which one to use, the compiler has to try both options. » Cette ramification s’imbrique : « for the nested ForEach the compiler will have to try each one. And then, ForEach’s builder has its own set of options that will also need to be checked. » Chaque couche multiplie les chemins, et « trying each of these paths makes type checking increasingly expensive. »37
Le correctif réduit l’arbre. « The most common set of builders now share a single initializer, leaving just one, straightforward path. This is possible because multiple different builder types have been unified under a single builder: ContentBuilder! »37 Apple le positionne comme le début d’un arc plus long : « This is a step towards enabling unified builders across all of SwiftUI’s APIs. »37
Deux propriétés rendent ContentBuilder sûr à adopter dès maintenant plutôt que plus tard. Il ne comporte aucun coût de cible de déploiement : « ContentBuilder can be used with any minimum deployment target, because under the hood, it’s an evolution of the existing ViewBuilder. »37 Et le gain se concrétise au moment de la compilation quelle que soit votre cible : « ContentBuilder provides a substantial improvement in type checking performance in SwiftUI when building using Xcode 27; whether you’re targeting the 2027 releases, or previous releases as well. »37 La documentation d’Apple confirme la rétrocompatibilité dans sa déclaration : ContentBuilder est un typealias, et sa disponibilité est répertoriée jusqu’à iOS 13.0 et macOS 10.15, décrit comme « A custom parameter attribute that constructs views and other content types from closures. »38
Holly Borla, responsable de l’ingénierie Swift, a corroboré le côté compilateur dans son entretien de clôture de la WWDC26. L’erreur « is a fallback in the compiler’s type checker », a-t-elle expliqué, et l’équipe a restreint l’endroit où elle apparaît : « This year we focused a lot on mitigating that error in nested closures and Swift UI view bodies, which is a really common place to see it. »40 Elle a ajouté que le travail se poursuit au grand jour : « there’s still some more work to be done and you can follow along with that through the Open Source Swift project. »40
L’équipe SwiftUI a donné au changement une seconde dimension lors d’un group lab. Paraphrasé à partir d’un enregistrement transcrit localement du SwiftUI Group Lab de la WWDC 2026, l’équipe a décrit les anciennes surcharges de builder par type comme ayant plafonné leur propre surface d’API : chaque endroit où ils voulaient ajouter un builder dégradait le type-checking, alors ils se sont retenus, laissant ForEach et ses semblables utilisables dans moins de positions qu’ils ne l’auraient voulu.39 L’unification lève ce plafond. L’équipe a aussi noté que le builder unifié peut désormais être utilisé en dehors des vues, de sorte que vous pouvez assembler des DSL personnalisés à la manière de SwiftUI à partir de vos propres blocs de construction plutôt que de seules vues.39 Le gain pour le compilateur est l’élément phare ; la marge de manœuvre de conception en est la conséquence plus discrète.
Priorités d’adoption
Une version aussi vaste récompense le tri. Saisissez-vous d’abord de celles-ci.
- Remplacez la réorganisation écrite à la main. Si vous maintenez du calcul d’index
onMove,reorderContainer(for:isEnabled:move:)accompagné dereorderable()est une suppression nette de code et une meilleure interaction (l’affordance d’espace réservé est celle du système, pas la vôtre).23 Pour les applications riches en listes, l’API de réorganisation pèse le plus lourd dans la version. - Adoptez les alertes à liaison d’erreur.
alert(error:actions:message:)supprime la structure de wrapper d’erreur personnalisée de chaque écran qui fait remonter des échecs, et uneLocalizedErrorque vous avez déjà titre désormais sa propre alerte.16 Peu d’effort, lisibilité immédiate. - Faites passer les grandes sources de glissement vers des conteneurs paresseux. Toute liste de plus de quelques centaines de lignes déplaçables bénéficie de
dragContainer(for:itemID:in:_:)accompagné dedraggable(containerItemID:containerNamespace:), car le framework cesse de matérialiser des charges utiles qu’il n’utilisera peut-être jamais.45 - Donnez à vos barres d’outils une logique de priorité. Si votre barre d’outils déborde un jour en largeur compacte,
visibilityPriority(_:),topBarPinnedTrailingetToolbarOverflowMenuvous laissent décider de ce qui survit au lieu d’accepter les réglages par défaut du framework.131415 - Migrez les applications de documents délibérément, pas par réflexe. La séparation
ReadableDocument/WritableDocumentest le bon modèle, mais c’est un changement plus important que les autres ; adoptez-le lorsque vous touchez déjà à la couche document, et appuyez-vous surFileWrapperDocumentReader/FileWrapperDocumentWriterpour le cas petit-et-moyen plutôt que d’implémenter les protocoles de lecteur et de rédacteur à la main.671011
Le fil conducteur : adoptez les ajouts qui suppriment du code que vous maintenez, différez ceux qui restructurent du code qui fonctionne déjà.
FAQ
Comment rendre une liste SwiftUI réorganisable dans iOS 27 ?
Déclarez reorderContainer(for:isEnabled:move:) sur le conteneur et appliquez reorderable() au DynamicViewContent (typiquement un ForEach) qu’il contient. La closure move du conteneur reçoit une ReorderDifference que vous appliquez à votre modèle ; le framework gère le geste de glissement, le soulèvement et l’espace réservé qui marque la position de dépôt.23 Utilisez la surcharge reorderContainer(for:in:isEnabled:move:) avec un type d’identifiant de collection lorsqu’un même conteneur contient plusieurs collections.27
Quelle est la différence entre draggable(containerItemID:) et l’ancien draggable ?
draggable(containerItemID:containerNamespace:) ne transporte que l’identifiant de l’élément, pas la charge utile, de sorte qu’il fonctionne de manière paresseuse à l’intérieur d’un dragContainer(for:itemID:in:_:) : le framework ne demande les éléments réellement glissés qu’au démarrage du glissement et n’a pas à rendre une vue pour lire sa charge utile.45 Cela en fait le bon choix pour les collections grandes ou chargées paresseusement où produire chaque charge utile en amont serait un travail gaspillé.
En quoi le nouveau modèle de document SwiftUI diffère-t-il de FileDocument ?
iOS 27 sépare la lecture et l’écriture en protocoles distincts, ReadableDocument et WritableDocument, avec DocumentReader/DocumentWriter qui effectuent le travail sur disque et un typealias Document pour un type à la fois lisible et inscriptible.67 Pour les documents de petite et moyenne taille qui n’ont besoin d’aucune logique personnalisée, FileWrapperDocumentReader et FileWrapperDocumentWriter fournissent l’implémentation ; URLDocumentConfiguration décrit un document ouvert.101112 La séparation permet à une fonctionnalité réservée à la consultation de se conformer uniquement au côté lecture.
Puis-je désormais afficher une alerte directement à partir d’une Error dans SwiftUI ?
Oui. alert(error:actions:message:) et alert(error:actions:) prennent une Binding<E?> où E : Error. Lorsque l’erreur liée est non nil, le système présente l’alerte, et si l’erreur se conforme à LocalizedError, le titre est déduit de son errorDescription ; sinon il utilise la description localisée.1619 Vous n’enveloppez plus l’erreur dans une structure identifiable personnalisée.
Comment contrôler quels éléments de barre d’outils disparaissent lorsque l’espace est restreint ?
Utilisez visibilityPriority(_:) sur votre ToolbarContent pour classer les éléments : les éléments de priorité inférieure se déplacent dans le menu de débordement avant ceux de priorité supérieure à mesure que l’espace rétrécit.15 Utilisez topBarPinnedTrailing pour épingler un contrôle au bord de fuite afin qu’il ne se déplace vers le débordement que lorsque la recherche est active et qu’il n’y a pas de place, et ToolbarOverflowMenu pour déclarer des actions qui vivent toujours dans le menu de débordement.1314
AsyncImage peut-il envoyer des en-têtes personnalisés ou définir une politique de cache dans iOS 27 ?
Oui. La famille AsyncImage(request:scale:) prend une URLRequest, qui transporte les en-têtes, la politique de cache et l’intervalle de délai d’expiration ; Apple note que vous pouvez spécifier la politique de cache et le délai d’expiration via la requête.2032 Pour partager une URLSession configurée (pour l’authentification ou un cache personnalisé) entre les instances d’AsyncImage d’un sous-arbre, appliquez asyncImageURLSession(_:).34
L’intégralité du cluster Apple Ecosystem : le substrat SwiftUI (result builders, types opaques, l’arbre de vues à valeur typée) ; les patterns Liquid Glass dans lesquels s’inscrit le comportement de minimisation de la barre d’outils d’iOS 27 ; les rouages internes de @Observable qui pilotent la couche d’état sous chaque vue de cet article ; et la surface parallèle des App Intents dans iOS 27 pour l’exécution en arrière-plan, la synchronisation et Spotlight. Le hub se trouve à 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
-
Documentation pour développeurs Apple : SwiftUI. La référence du framework couvrant les vues, les listes, les documents, les barres d’outils et les ajouts d’iOS 27 décrits ici. ↩
-
Documentation pour développeurs Apple :
reorderContainer(for:isEnabled:move:)(iOS 27.0 bêta). Définit un conteneur qui permet la réorganisation de ses éléments ; la commodité de collection unique qui remet uneReorderDifferenceà sa closuremove. ↩↩↩↩↩↩ -
Documentation pour développeurs Apple :
reorderable()(iOS 27.0 bêta). Permet la réorganisation des vues d’unDynamicViewContentlorsqu’il est utilisé dans la portée d’un conteneur de réorganisation. ↩↩↩↩↩ -
Documentation pour développeurs Apple :
dragContainer(for:itemID:in:_:)(iOS 27.0 bêta ; macOS 26.0). Un conteneur de vues déplaçables ; prend unKeyPathvers l’identifiant de chaque élément et une closure de charge utile sur les identifiants glissés. ↩↩↩↩↩↩ -
Documentation pour développeurs Apple :
draggable(containerItemID:containerNamespace:)(iOS 27.0 bêta ; macOS 26.0). Active une vue comme source de glissement à l’intérieur d’un conteneur de glissement, ne fournissant qu’un identifiant afin que le conteneur fonctionne de manière paresseuse. ↩↩↩↩↩↩ -
Documentation pour développeurs Apple :
ReadableDocument(iOS 27.0 bêta). « A type that you use to read documents from file. » Déclaré commeprotocol ReadableDocument : AnyObject; pour la lecture-écriture, conformez-vous aussi àWritableDocumentou utilisez le typealiasDocument. ↩↩↩↩↩ -
Documentation pour développeurs Apple :
WritableDocument(iOS 27.0 bêta). « A type that you use to write documents to file. » Déclaré commeprotocol WritableDocument : AnyObject; conformez-vous-y aux côtés deReadableDocumentpour prendre en charge l’enregistrement. ↩↩↩↩↩ -
Documentation pour développeurs Apple :
DocumentReader(iOS 27.0 bêta). « Implements logic of reading documents from disk. » Déclaré commeprotocol DocumentReader<Snapshot>. ↩↩ -
Documentation pour développeurs Apple :
DocumentWriter(iOS 27.0 bêta). « Implements logic of writing documents to disk. » Déclaré commeprotocol DocumentWriter<Snapshot>. ↩↩ -
Documentation pour développeurs Apple :
FileWrapperDocumentReader(iOS 27.0 bêta). Un lecteur de document adossé à un file wrapper ; efficace pour les documents de petite et moyenne taille qui n’ont besoin d’aucune logique de lecture personnalisée. ↩↩↩↩ -
Documentation pour développeurs Apple :
FileWrapperDocumentWriter(iOS 27.0 bêta). Un rédacteur de document adossé à un file wrapper ; efficace pour les documents de petite et moyenne taille qui n’ont besoin d’aucune logique d’écriture personnalisée. ↩↩↩↩ -
Documentation pour développeurs Apple :
URLDocumentConfiguration(iOS 27.0 bêta). « A set of settings and properties of an open document. » Déclaré comme@MainActor final class URLDocumentConfiguration. ↩↩↩ -
Documentation pour développeurs Apple :
ToolbarOverflowMenu(iOS 27.0 bêta). « The overflow menu of a toolbar. » Déclaré commenonisolated struct ToolbarOverflowMenu<Content> where Content : View; sur iOS et visionOS le contenu est placé dans le menu de débordement de la barre de navigation. ↩↩↩↩ -
Documentation pour développeurs Apple :
topBarPinnedTrailing(iOS 27.0 bêta). « A placement that pins the item to the trailing edge of the toolbar. » Les éléments épinglés ne se déplacent vers le menu de débordement que lorsque la recherche est active et qu’il n’y a pas assez de place. ↩↩↩↩↩ -
Documentation pour développeurs Apple :
visibilityPriority(_:)(iOS 27.0 bêta). « Defines the visibility priority for a toolbar item. » Lorsque l’espace de la barre d’outils est limité, les éléments de priorité inférieure se déplacent dans le menu de débordement avant les éléments de priorité supérieure. ↩↩↩↩↩ -
Documentation pour développeurs Apple :
alert(error:actions:message:)(iOS 27.0 bêta). « Presents an alert with a message when an error is present. » Le titre est déduit de l’errorDescriptionde l’erreur s’il s’agit d’uneLocalizedError; sinon de la description localisée. ↩↩↩↩↩ -
Documentation pour développeurs Apple :
alert(_:item:actions:)(iOS 27.0 bêta). « Presents an alert using the given data to produce the alert’s content and a text view as a title. » Pour que l’alerte apparaisse,datane doit pas êtrenil. ↩↩ -
Documentation pour développeurs Apple :
alert(_:item:actions:message:)(iOS 27.0 bêta). La surcharge d’alerte basée sur un élément avec un constructeur de message ; les données doivent être non nil et les modifications après la présentation sont ignorées. ↩↩↩ -
Documentation pour développeurs Apple :
alert(error:actions:)(iOS 27.0 bêta). « Presents an alert when an error is present. » La surcharge à liaison d’erreur sans constructeur de message. ↩↩↩ -
Documentation pour développeurs Apple :
init(request:scale:)(iOS 27.0 bêta). « Loads and displays an image from the specified URL load request. » Déclaré commeinit(request: URLRequest, scale: CGFloat = 1) where Content == Image; affiche un espace réservé jusqu’à ce que le chargement se termine. ↩↩↩↩ -
Documentation pour développeurs Apple :
swipeActions(edge:allowsFullSwipe:content:onPresentationChanged:)(iOS 27.0 bêta). La closure est appelée avectruelorsque les actions de balayage d’une ligne deviennent visibles etfalselorsqu’elles sont fermées. ↩↩ -
Documentation pour développeurs Apple :
swipeActionsContainer()(iOS 27.0 bêta). Coordonne la fermeture des actions de balayage et l’exclusion mutuelle entre les lignes dans uneScrollViewou un conteneur similaire ; l’appliquer à uneListest sans effet. ↩↩ -
Documentation pour développeurs Apple :
crossFade(iOS 27.0 bêta). « A navigation transition that cross-fades between the appearing view and the disappearing view. » Spécifiée sur une sheet, elle apparaît en fondu par-dessus le contenu plutôt que de se déplacer vers le haut pour le couvrir. ↩↩ -
Documentation pour développeurs Apple :
prominent(iOS 27.0 bêta). « The prominent role. » Confère un traitement visuel proéminent à un onglet dans les barres d’onglets prises en charge ; en l’absence d’onglet.prominentexplicite, un onglet de rôle.searchpeut le recevoir par défaut. ↩↩ -
Documentation pour développeurs Apple :
UIHostingSceneDelegate(iOS 26.0 ; tvOS 27.0 bêta). « ExtendsUISceneDelegateto bridge SwiftUI scenes. » Déclarez des scènes SwiftUI à activer depuis UIKit dans la propriété statiquerootScenede la classe conforme. ↩↩↩ -
Documentation pour développeurs Apple :
GestureInputKinds(iOS 27.0 bêta). « An option set that specifies which input kinds a gesture should recognize. » ↩↩ -
Documentation pour développeurs Apple :
reorderContainer(for:in:isEnabled:move:)(iOS 27.0 bêta). « Defines a container that allows its items to be reordered. » La surcharge multi-collections, indexée par un type d’identifiant de collection ; utilisez-la lorsqu’un conteneur contient plus d’une collection. ↩↩↩ -
Documentation pour développeurs Apple :
fileExporter(isPresented:document:contentType:defaultFilename:onCompletion:onCancellation:)(iOS 27.0 bêta). Présente une boîte de dialogue système pour exporter unWritableDocumentdont la destination du rédacteur estURL; la boîte de dialogue n’apparaît que lorsquedocumentest non nil. ↩↩ -
Documentation pour développeurs Apple :
toolbarMinimizeBehavior(_:for:)(iOS 27.0 bêta). « Sets the minimize behavior for the specified bars. » Active la minimisation de la barre d’outils en réponse au défilement ; le placement pris en charge est la barre de navigation, et une barre d’onglets supérieure intégrée se minimise avec elle. ↩↩ -
Documentation pour développeurs Apple :
confirmationDialog(_:item:titleVisibility:actions:message:)(iOS 27.0 bêta). Présente un dialogue de confirmation avec un message en utilisant les données pour produire le contenu du dialogue et une vue texte pour le message. ↩ -
Documentation pour développeurs Apple :
confirmationDialog(_:item:titleVisibility:actions:)(iOS 27.0 bêta). Le dialogue de confirmation basé sur un élément sans constructeur de message. ↩ -
Documentation pour développeurs Apple :
init(request:scale:transaction:content:)(iOS 27.0 bêta). « Loads and displays a modifiable image from the specified URL load request in phases. » Vous pouvez spécifier la politique de cache et l’intervalle de délai d’expiration via la requête. ↩↩ -
Documentation pour développeurs Apple :
init(request:scale:content:placeholder:)(iOS 27.0 bêta). « Loads and displays a modifiable image from the specified URL load request using a custom placeholder until the image loads. » ↩ -
Documentation pour développeurs Apple :
asyncImageURLSession(_:)(iOS 27.0 bêta). « A modifier that adds a URL session for asynchronous images contained in the view to use when fetching image data. » ↩↩ -
Apple, WWDC26 session 269, « What’s new in SwiftUI ». developer.apple.com/videos/play/wwdc2026/269. La session cadre la version autour d’un look et d’un ressenti affinés, d’une nouvelle API de document, de nouvelles façons d’interagir et d’améliorations des performances. ↩
-
Apple, WWDC26 session 271, « Code-along: Build powerful drag and drop in SwiftUI ». developer.apple.com/videos/play/wwdc2026/271. Le code de réorganisation est montré en train de passer sans modification entre une
Listet uneLazyVGrid, puisque l’API réorganisable fonctionne avec tout conteneur qui prend en charge le glisser-déposer. ↩ -
Apple, WWDC26 session 269, « What’s new in SwiftUI ». developer.apple.com/videos/play/wwdc2026/269. Source de l’erreur « unable to type-check this expression in reasonable time », de l’arbre de décision de surcharge
Section/Group/ForEach, de l’unification des builders communs sousContentBuilder, de l’étape vers des builders unifiés à travers SwiftUI, de la prise en charge de n’importe quelle cible de déploiement minimale en tant qu’évolution deViewBuilder, et de l’amélioration du type-checking sous Xcode 27 pour les versions 2027 comme pour les précédentes. ↩↩↩↩↩↩ -
Documentation pour développeurs Apple :
ContentBuilder. « A custom parameter attribute that constructs views and other content types from closures. » Déclaré comme un typealias, avec une disponibilité répertoriée jusqu’à iOS 13.0 et macOS 10.15. ↩ -
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 aucun sous-titre officiel pour les labs. Source des surcharges de builder par type ayant plafonné la propre surface d’API de l’équipe (chaque ajout dégradait le type-checking) et du builder unifié utilisable en dehors des vues pour permettre des DSL personnalisés à la manière de SwiftUI. ↩↩
-
Apple, WWDC26 session 400, « Dub Dub Daily: Day 5 », transcription officielle. Holly Borla, responsable de l’ingénierie Swift, dans l’entretien de clôture avec Jeff ; source de la caractérisation « fallback in the compiler’s type checker », de l’accent mis sur les closures imbriquées et les corps de vue SwiftUI, et du travail Open Source Swift en cours. ↩↩