En migrant un projet vers Xcode 27, je suis tombé sur une erreur SwiftUI que je n’avais jamais vue : l’erreur TupleContent watchOS 26. J’ai creusé la cause, et je la partage ici avec les sources qui appuient chaque affirmation.

Conformance of ‘TupleContent<repeat each Content>’ to ‘View’ is only available in watchOS 26.0 or newer

1. Le contexte

L’erreur apparaît sur du code partagé iOS/watchOS, quand un builder reçoit plusieurs vues. La lib cible watchOS 9, et ce code compilait avec Xcode 26 :

if #available(iOS 16.0, *) {
    FlowLayout(spacing: 4) {
        ForEach(items, id: \.self) { Text($0) }
        secondaryView
        trailingView
    }
} else {
    fallbackView
}

2. Ce qu’est TupleContent

TupleContent est un type documenté de SwiftUI, décrit comme « du contenu créé à partir d’un tuple de contenus à traiter comme des vues sœurs », et sa page officielle indique sa disponibilité : iOS 26.0+, watchOS 26.0+ (1). Il fait partie du nouveau ContentBuilder, qu’Apple présente comme le remplaçant unifié des builders spécifiques comme ViewBuilder. La sûreté de typage y passe par des conformances conditionnelles sur les types produits (2).

3. Ce qui change avec Xcode 27

En comparant les interfaces SwiftUI des SDK livrés avec chaque Xcode (c’est un fichier texte, la procédure est en source (3)), on voit la différence. Le watchOS 26 SDK d’Xcode 26 n’a qu’un seul overload « pack » dans le ViewBuilder, qui renvoie l’ancien TupleView. Le watchOS 27 SDK d’Xcode 27 en ajoute un second, qui renvoie TupleContent :

// watchOS 26 SDK (Xcode 26)
@_disfavoredOverload @_alwaysEmitIntoClient
static func buildBlock<each Content>(_ content: repeat each Content)
    -> TupleView<(repeat each Content)>

// watchOS 27 SDK (Xcode 27) : nouvel overload, non défavorisé
@_alwaysEmitIntoClient @inline(always)
static func buildBlock<each Content>(_ content: repeat each Content)
    -> TupleContent<repeat each Content>

@available(iOS 26.0, macOS 26.0, tvOS 26.0, watchOS 26.0, visionOS 26.0, *)
@frozen public struct TupleContent<each Content>

À noter : la syntaxe repeat each (les parameter packs) n’est pas une nouveauté d’Xcode 27. C’est une capacité du langage implémentée dans Swift 5.9 (4). Ce qui est nouveau, c’est que SwiftUI l’adopte dans son builder.

4. Pourquoi l’erreur TupleContent watchOS 26 apparaît

Le compilateur d’Xcode 27 peut donc typer le contenu multi-vues en TupleContent, un type disponible à partir de watchOS 26 (1). Si la cible déploie plus bas, comme watchOS 9 ici, la vérification de disponibilité échoue : c’est le message d’erreur, mot pour mot.

Et la garde #available(iOS 16.0, *) n’y change rien. La spécification de Swift précise le rôle de l’astérisque : sur toute autre plateforme, le bloc s’exécute à la version minimale de déploiement de la cible (5). Sur watchOS, cette condition est donc déjà vraie en watchOS 9. Elle n’établit pas watchOS 26.

5. Les deux corrections possibles

Soit nommer watchOS explicitement dans la garde, en conservant un fallback pour les versions antérieures :

if #available(iOS 16.0, watchOS 26.0, *) {
    FlowLayout(spacing: 4) { … }
} else {
    fallbackView   // watchOS < 26 (et iOS < 16)
}

Soit monter la deployment target watchOS de la lib à 26 (platforms: [.watchOS(.v26)] dans le Package.swift), en sachant que cela retire le support des watchOS antérieurs.

En résumé

  • TupleContent est un type SwiftUI disponible à partir de watchOS 26 (1), que le ViewBuilder d’Xcode 27 peut désormais produire (3).
  • Sur une cible watchOS plus ancienne, la vérification de disponibilité échoue, et l’astérisque de #available ne couvre pas ce cas (5).
  • Correction : nommer watchOS dans la garde, ou monter la deployment target.

Les sources ci-dessous reprennent les extraits exacts sur lesquels je m’appuie, pour ceux qui veulent creuser. Et si cette migration vous amène à optimiser vos compilations, voyez aussi comment accélérer les builds Xcode grâce aux modes de compilation.


Sources

(1) Apple, TupleContent (SwiftUI)
→ « Content created from a tuple of content to be treated as siblings. » Disponibilité affichée : iOS 26.0+, iPadOS 26.0+, macOS 26.0+, tvOS 26.0+, visionOS 26.0+, watchOS 26.0+.

(2) Apple, ContentBuilder (SwiftUI)
→ « …which serves as the unified replacement for type-specific builders like ViewBuilder and ToolbarContentBuilder. In its build functions, ContentBuilder doesn’t enforce protocol conformance. Instead, it maintains type safety through conditional conformances on the content types it produces. »

(3) Interfaces des SDK, à inspecter sur votre Mac avec cette commande (à lancer dans chaque Xcode) :

SDK=$(xcrun --sdk watchos --show-sdk-path)
grep -n "buildBlock<each" "$SDK/System/Library/Frameworks/\
SwiftUICore.framework/Modules/SwiftUICore.swiftmodule/\
arm64e-apple-watchos.swiftinterface"

Les déclarations citées en section 3 en sont extraites telles quelles.

(4) Swift Evolution, SE-0393 « Value and Type Parameter Packs »
→ « Status: Implemented (Swift 5.9) ».

(5) The Swift Programming Language, Statements (Availability Condition)
→ « The * argument is required and specifies that, on any other platform, the body of the code block guarded by the availability condition executes on the minimum deployment target specified by your target. »

Categorized in:

SwiftUI,