Cinq plateformes Apple, trois fichiers partagés : comment Return livre vraiment du SwiftUI multiplateforme
Return, mon minuteur de méditation, tourne sur cinq plateformes Apple : iPhone, iPad, Mac, Apple Watch et Apple TV.1 La base de code compte 40 fichiers Swift (hors tests). Trois d’entre eux sont partagés entre les cinq plateformes. Le reste est réparti dans des cibles Xcode distinctes qui dupliquent des concepts comme TimerManager, AudioManager et ContentView plutôt que de les partager via la compilation conditionnelle #if os(...).
Le taux de partage est d’environ 7,5 %, et c’est intentionnel.
Cet essai porte sur ce à quoi ressemble vraiment la livraison d’une application SwiftUI multiplateforme en 2026, sur les raisons pour lesquelles le partage de code agressif est surévalué, et sur ce que les trois fichiers qui ont bien été partagés ont en commun.
Les cinq plateformes que cible Return, telles qu’Apple les présente sur developer.apple.com. Chacune est une cible de plateforme distincte dans Xcode, pas une bifurcation à l’exécution.
TL;DR
- Return : 18 fichiers Swift dans la cible principale (iOS + iPadOS + macOS), 10 fichiers dans la cible tvOS, 7 fichiers dans la cible watchOS, 2 fichiers de widget (Live Activities) et 3 fichiers réellement multiplateformes dans
Return/Shared/. Total : 40. - Les trois fichiers partagés sont ceux qui touchent à la persistance :
MeditationSession,SessionStore,SessionHistoryView. De l’état qui voyage via iCloud, pas une UI qui s’adapte à la plateforme. - tvOS et watchOS sont des cibles Xcode séparées, pas des branches
#if os(tvOS)dans la cible principale. Les modèles de contrôle sont trop différents pour tenir dans un seul ContentView. - Même au sein de la cible principale iOS/iPadOS/macOS, les blocs
#if osprolifèrent : 10 dansContentView.swift, 8 dansLiveActivityManager.swift, 8 dansVideoBackgroundView.swift, 6 dansAudioManager.swift. - Le constat honnête : le partage agressif sur cinq plateformes Apple est une dette de maintenance. Un petit noyau partagé (la couche de persistance) plus des UI séparées, propres à chaque plateforme, livre plus vite et casse moins qu’un seul gros fichier truffé de
#if.
Pour les compagnons spécifiques aux plateformes, lisez la matrice des plateformes Apple, le contrat d’exécution watchOS et les patterns SwiftUI Liquid Glass.
Les chiffres
La forme de la base de code, en nombre de fichiers Swift, après élagage des tests et des UI tests :
Return/ 18 files (iPhone + iPad + Mac, single target)
├── Shared/ 3 files ← cross-platform truth
│ ├── MeditationSession.swift
│ ├── SessionStore.swift
│ └── SessionHistoryView.swift
├── ContentView.swift (10 #if os branches)
├── TimerManager.swift (2 #if os branches)
├── AudioManager.swift (6 #if os branches)
├── HealthKitManager.swift
├── LiveActivityManager.swift (8 #if os branches, iOS-only)
├── ThemeManager.swift
├── VideoBackgroundView.swift (8 #if os branches)
├── GlassTextShape.swift (Liquid Glass, see prior post)
├── GlassTimerText.swift
└── … (settings, theme, audio assets, etc.)
ReturnTV/ 10 files (tvOS, separate target)
├── TVContentView.swift
├── TVTimerManager.swift ← duplicates main TimerManager
├── TVAudioManager.swift ← duplicates main AudioManager
├── TVDurationPicker.swift
├── TVFocusModifier.swift ← tvOS button styles for focus
├── TVSettingsView.swift
└── …
ReturnWatch Watch App/ 7 files (watchOS, separate target)
├── WatchContentView.swift
├── WatchTimerManager.swift ← duplicates main TimerManager
├── WatchAudioManager.swift ← duplicates main AudioManager
├── WatchHealthKitManager.swift ← duplicates main HealthKitManager (mostly)
├── WatchSettingsView.swift
└── …
ReturnWidgets/ 2 files (Live Activity + bundle)
├── ReturnLiveActivity.swift
└── ReturnWidgetsBundle.swift
Cinq plateformes, trois fichiers partagés, deux cibles séparées par plateforme plus une cible widget, plus une compilation conditionnelle copieuse à l’intérieur de la cible principale. Le ratio de partage est d’environ 7,5 %. La plupart des tutoriels de « SwiftUI multiplateforme » suggèrent l’inverse : écrire un seul ContentView qui s’adapte à chaque plateforme via @Environment(\.horizontalSizeClass) et #if os(...).2 Cela fonctionne pour deux plateformes (iPhone + iPad). Cela casse à cinq.
Ce que les trois fichiers partagés ont en commun
Return/Shared/MeditationSession.swift définit le type valeur adjacent à SwiftData :3
struct MeditationSession: Codable, Identifiable, Equatable {
let id: UUID
let startDate: Date
let endDate: Date
let durationSeconds: Int
let sourceDevice: DeviceType
var syncedToHealthKit: Bool
enum DeviceType: String, Codable, CaseIterable {
case iPhone, iPad, mac, appleTV, appleWatch
}
}
Le commentaire d’en-tête du fichier porte sa charge : // Add this file to: Return, ReturnTV, ReturnWatch Watch App targets. Le même fichier source est référencé par les trois cibles Xcode, sans lien symbolique, sans Swift package. Le système de build d’Apple compile volontiers un seul fichier dans trois binaires.
SessionStore.swift est la couche de persistance : un fin enrobage autour de NSUbiquitousKeyValueStore (le Key-Value Store iCloud d’Apple) qui lit et écrit des tableaux de MeditationSession. Le choix compte : la synchronisation par KV-store offre à Return un historique de séances multi-appareils sans avoir à provisionner de conteneur CloudKit, avec le compromis que le store entier est plafonné à 1 Mo au total.12 Pour une liste de séances de méditation pesant chacune quelques centaines d’octets, ce plafond suffit largement. SessionHistoryView.swift est une List SwiftUI qui affiche les séances. Les deux sont utilisés à l’identique par les cibles iPhone, iPad, Mac, Watch et TV.
Ce que ces trois fichiers ont en commun : ils décrivent de l’état, pas de l’interaction. Une MeditationSession est le même concept sur tous les appareils. La liste des séances passées se lit de la même manière sur tous les appareils. Aucun n’implique une surface de contrôle, un gestionnaire de fenêtres, une décision de routage audio, un moteur de focus ou une couronne digitale. Dès qu’un fichier doit savoir sur quelle plateforme il s’exécute, il cesse d’être partageable.
Pourquoi le reste n’a pas été partagé
Prenez TimerManager. La version iOS/iPadOS/macOS utilise Timer.publish(every: 1, ...) et fait passer les notifications par UserNotifications. La version tvOS (TVTimerManager) gère le cas où l’utilisateur a mis en pause via la Siri Remote et où l’économiseur d’écran s’enclenche. La version watchOS (WatchTimerManager) délègue à une WKExtendedRuntimeSession (via WatchSessionManager) pour que le système conserve l’application réactive pendant que l’écran s’éteint, et route l’entrée par la couronne digitale plutôt que par le toucher. Trois plateformes, trois comportements de minuteur profondément différents.
Vous pourriez les unifier en class TimerManager { #if os(watchOS) ... #elif os(tvOS) ... }. Le résultat serait une classe à trois modes, chacun avec quarante lignes de code sous #if, où toucher au chemin iOS risquerait de casser le chemin watchOS. Voilà un cauchemar de maintenance.
Trois classes séparées avec trois noms de fichiers, c’est plus de code sur le disque et moins de code dans la tête. La duplication que vous pouvez lire vaut mieux que l’abstraction que vous ne pouvez pas lire.
La même logique s’applique à :
ContentViewvsTVContentViewvsWatchContentView: les modèles de navigation diffèrent (par empilement sur iPhone, par focus sur TV, par liste sur Watch).AudioManagervsTVAudioManagervsWatchAudioManager: les catégories de session audio diffèrent, watchOS impose des règles plus strictes pour l’audio en arrière-plan, tvOS route différemment vers AirPlay.VideoBackgroundViewcomporte 8 branches#if os(iOS)dans la cible principale (avec un compagnon#elseif os(macOS)), couvrant des assets vidéo différents (fire_phone.mp4vsfire_mac.mp4), des types de couches différents et des ratios d’image différents.4
À noter : la cible principale Return/ regroupe bien iOS, iPadOS et macOS. Ces trois plateformes partagent plus de code qu’elles n’en partagent pas. Le NavigationStack de SwiftUI fonctionne sur les trois. .glassEffect() fonctionne sur les trois. Les différences de gestion de fenêtres existent mais restent traitables au sein d’une seule cible. tvOS et watchOS sont l’endroit où j’ai tracé la frontière de la cible séparée.
Le cas tvOS : pourquoi le moteur de focus a imposé une cible séparée
La navigation sur Apple TV est construite autour du moteur de focus.5 Chaque élément d’UI avec lequel l’utilisateur peut interagir se déclare focusable ; les flèches du système sur la Siri Remote déplacent le focus entre les éléments ; appuyer sur Sélectionner active l’élément focalisé. SwiftUI sur tvOS expose cela via .focusable(), .focusEffect, et des types ButtonStyle personnalisés qui réagissent à @Environment(\.isFocused) pour l’effet de parallaxe que les applications maison d’Apple utilisent. Du vrai code de production dans TVFocusModifier.swift :6
struct TVCapsuleButtonStyle: ButtonStyle {
var accentColor: Color = .white
@Environment(\.isFocused) private var isFocused
func makeBody(configuration: Configuration) -> some View {
configuration.label
.colorMultiply(isFocused ? focusedTextColor : accentColor)
.background(
Capsule().fill(isFocused
? AnyShapeStyle(accentColor)
: AnyShapeStyle(.ultraThinMaterial))
)
.clipShape(Capsule())
.scaleEffect(isFocused ? 1.1 : 1.0)
.scaleEffect(configuration.isPressed ? 0.95 : 1.0)
.shadow(color: .black.opacity(isFocused ? 0.3 : 0.1),
radius: isFocused ? 20 : 5, y: isFocused ? 10 : 2)
.animation(.easeInOut(duration: 0.2), value: isFocused)
}
}
Le même fichier définit aussi TVCircleButtonStyle pour les contrôles carrés/circulaires. Les deux styles inversent couleur et translucidité au focus : les boutons non focalisés reposent sur .ultraThinMaterial, les boutons focalisés se remplissent de la couleur d’accent et accentuent l’échelle et l’ombre. Le pattern est structurellement spécifique à tvOS pour cette application. @Environment(\.isFocused) est disponible sur iOS, iPadOS, macOS, watchOS et tvOS,13 mais la navigation pilotée par le focus n’est le modèle d’interaction principal que sur tvOS, où la Siri Remote ne produit ni événement de pointeur ni événement tactile. Sur iPhone ou iPad, le contrôle équivalent est résolu par appui ; sur Mac il l’est par survol ou clic. Les styles de bouton dans TVFocusModifier.swift partent du principe que le focus est l’affordance principale de l’utilisateur et conçoivent toute la réponse visuelle autour. Il n’existe pas de bonne façon d’écrire un ContentView qui gère le toucher sur iOS, le survol sur Mac et la navigation par focus sur tvOS dans un seul endroit. La structure des vues est véritablement différente : un ContentView tvOS est un graphe de rangées focusables, un ContentView iOS est une pile « tap-to-act ».
Il en va de même pour le sélecteur de durée. Sur iPhone, il glisse depuis le bas et accepte les appuis. Sur Apple TV, c’est une rangée horizontale de cellules focusables que l’utilisateur parcourt avec la télécommande. TVDurationPicker.swift est un fichier à part parce que le design par cellules focusables n’a pas d’analogue sur l’iPhone. Les forcer dans un seul fichier reviendrait à coller deux UI sans rapport avec un #if os(tvOS).
Le cas watchOS : sessions d’exécution étendue, HealthKit et une surface plus petite
watchOS ajoute deux contraintes structurelles que les autres plateformes n’ont pas :
WKExtendedRuntimeSessionpour conserver l’application réactive pendant que l’écran de la montre est en veille.8 Sans elle, watchOS suspend agressivement l’application entre chaque tic de seconde et le minuteur dérive. Return déclareWKBackgroundModes: mindfulnessdans leInfo.plistde la cible watchOS pour que le système reconnaisse le cas d’usage et accorde le budget d’exécution ; la session d’exécution elle-même est créée avec l’initialiseur par défautWKExtendedRuntimeSession().- Synchronisation iCloud via
NSUbiquitousKeyValueStore, et non WatchConnectivity. La synchronisation de l’historique de séances de Return repose sur le même Key-Value Store que les cibles iPhone, iPad et Mac, de sorte qu’une méditation enregistrée sur la montre apparaît dans la vue d’historique de l’iPhone sans message direct watch-to-phone. WatchConnectivity pourrait être une option future pour la synchronisation d’état en direct, mais Return a choisi le modèle plus simple : chaque appareil écrit dans le même KV-store iCloud, et la lecture suivante sur n’importe quel appareil voit l’union.
WatchTimerManager.swift est le minuteur côté montre ; il délègue le travail d’exécution étendue à WatchSessionManager, défini dans ReturnWatchApp.swift sous final class WatchSessionManager: NSObject, WKExtendedRuntimeSessionDelegate. Le TimerManager iOS n’a pas d’équivalent parce que les applications iOS restent réactives au premier plan sans session d’exécution explicite. Mettre la logique de la montre dans le TimerManager iOS via #if os(watchOS) impliquerait que le chemin de code iOS importe des symboles WatchKit qu’il n’utilise jamais, en plus du fait que le chemin watchOS exigerait des chemins d’initialisation que le chemin iOS n’exige pas.
WatchHealthKitManager.swift est une variante plus petite du HealthKitManager principal. Elle journalise les minutes de pleine conscience de la même façon, mais l’UX de la demande d’autorisation diffère (la montre ne peut pas afficher de HealthKitPermissionSheet). La classe Watch fait à peu près la moitié de la taille de la classe principale.
Ce qui se passe à l’intérieur de la cible principale iOS/iPadOS/macOS
Même au sein de la cible principale, le partage n’a rien d’automatique. ContentView.swift comporte dix blocs #if os(macOS) ou #if !os(macOS) ; LiveActivityManager.swift en compte huit ; VideoBackgroundView.swift huit ; AudioManager.swift six. Les Live Activities sont une fonctionnalité réservée à l’iPhone, donc l’intégralité de LiveActivityManager est enveloppée dans un #if os(iOS). Le sélecteur de durée sur iPhone utilise une mise en page différente du sélecteur sur iPad et Mac, donc ContentView comporte des branches de mise en page parallèles.
Le pattern qui a fonctionné : #if os(...) pour les petits écarts de plateforme (comportement clavier différent, padding différent, API manquante), cible séparée pour les écarts structurels importants (focus vs. toucher, session d’entraînement vs. minuteur). Le seuil que j’ai fini par adopter est « plus de ~10 lignes de branchement ». En dessous, la compilation conditionnelle convient. Au-dessus, le fichier fait deux métiers à la fois et le second métier appartient à une autre cible.
Quand ne pas livrer sur les cinq plateformes
L’évaluation honnête.
Évitez Apple Watch si votre application est dense en information. L’écran de 46 mm n’a pas la place pour une liste de 30 éléments, un sélecteur de durée et une page de réglages. Return survit sur watchOS parce que l’interaction principale tient dans un seul bouton (démarrer/arrêter un minuteur). Une application de productivité, une application financière ou une application riche en médias ne tiendra pas.
Évitez Apple TV si votre application est interactive. La TV est faite pour des expériences ambiantes (un minuteur qui tourne sur un écran à l’autre bout de la pièce, la lecture musicale). Tout ce qui demande une saisie fréquente de l’utilisateur lutte contre la plateforme. Return est sur tvOS parce que « régler un minuteur de 20 minutes et regarder du feu à l’écran » est exactement le bon cas ambiant. Une application de prise de notes y serait pénible.
Évitez Mac si votre application est une interface conçue d’abord pour le téléphone. SwiftUI sur Mac fonctionne, mais le modèle d’empilement de NavigationStack se lit comme un jouet par rapport à une vraie barre latérale Mac. Si l’application paraît bâclée sur Mac, livrez en Catalyst (qui convertit l’application iPad) ou laissez tomber Mac jusqu’à pouvoir construire une UI native Mac.
Évitez l’iPad si vous n’avez pas fait l’adaptation par classes de taille. Une application iPhone étirée pour remplir un iPad paraît bas de gamme. L’iPad demande au minimum un NavigationSplitView avec une barre latérale ; idéalement une vraie disposition à deux volets. Return utilise des split views sur iPad et des piles sur iPhone. Le code est dans la même cible mais l’UI est véritablement différente.
La règle que j’ai tracée : livrez sur une plateforme quand l’interaction principale de l’application correspond au modèle d’entrée de cette plateforme. Livrez un minuteur de méditation sur Apple Watch (un appui pour démarrer). Livrez un minuteur de méditation sur Apple TV (régler et oublier). Ne livrez pas un kanban sur l’une ou l’autre.
Ce qui voyage sans effort
Les trois choses qui ont bien été partagées sur les cinq plateformes dans Return :
- Le modèle de données (
MeditationSession). La struct est identique sur chaque plateforme, est synchronisée viaNSUbiquitousKeyValueStore, et n’importe quelle plateforme peut lire ce qu’une autre a écrit. - La vue d’historique des séances (
SessionHistoryView). UneListdes séances passées s’affiche à l’identique sur iPhone, iPad, Mac, Apple Watch et Apple TV. LaListSwiftUI est l’une des rares primitives qui s’adapte proprement aux cinq facteurs de forme. - L’enrobage de persistance (
SessionStore). Les lectures et écritures sont indépendantes de la plateforme ; le stockage sous-jacent (NSUbiquitousKeyValueStore) est la même API partout.
Trois concepts. État, rendu de liste, persistance. Tout ce qui est avec état et présentationnel sans toucher à un modèle d’entrée matériel spécifique est partageable. Tout ce qui touche à l’entrée, au focus, au routage audio, à la taille d’écran ou à l’exécution en arrière-plan ne l’est pas.
Ce pattern apparaît dans le guide iOS Agent Development, où je défendais la même chose avec d’autres mots : les parties d’une application iOS qu’un agent peut écrire partagent l’essentiel de leur code avec les parties qu’un humain écrit ; les parties qui demandent du jugement humain (signature, finition visuelle, performance) sont précisément celles qui ne se partagent pas non plus bien entre plateformes.9 Les deux frontières s’alignent. Toutes deux concernent l’endroit où la connaissance du domaine commence à compter.
Ce que coûte le multiplateforme
Le ROI est asymétrique. Ajouter l’iPad à une application iPhone coûte peut-être 20 % de code en plus (branches par classe de taille, split view par endroits). Ajouter le Mac à cette même cible ajoute encore 15-20 % (branches #if os(macOS), barre de menus, gestion des fenêtres). Chaque cible majeure ajoute environ 10 fichiers pour une petite application.
Apple Watch et Apple TV sont les coûteuses. Ajouter watchOS à Return a demandé 11 nouveaux fichiers dans une cible séparée, dont des gestionnaires audio, minuteur et HealthKit dédiés. Ajouter tvOS a demandé 10 nouveaux fichiers dans une autre cible séparée, dont la gestion du focus et un sélecteur de durée personnalisé. Ensemble, elles ont presque doublé la surface Swift pour ce qui est, au niveau des fonctionnalités utilisateur, la même application.
Le choix de livrer sur les cinq n’était pas « on veut être multiplateforme pour le sport ». C’était une série de décisions distinctes : Apple Watch parce que les minuteurs de méditation appartiennent vraiment au poignet, Apple TV parce que le format d’écran ambiant convient aux longues séances dans une pièce, Mac parce que certains utilisateurs méditent à leur bureau entre deux réunions. Chaque plateforme a gagné sa cible en ayant un vrai cas d’usage.
Si une fonctionnalité ne mérite pas sa cible, le mouvement le moins coûteux est de laisser tomber la plateforme et de redoubler d’efforts sur celles où l’application est excellente.
Ce que cela signifie pour votre application
Trois enseignements.
- Par défaut, une cible par grand groupe de plateformes. iOS + iPadOS + macOS dans une seule cible fonctionne parce que l’interaction principale (toucher + curseur) est similaire. tvOS dans une cible séparée. watchOS dans une cible séparée. Chaque cible séparée coûte ~10 fichiers mais vous épargne une God-class avec des branches
#ifqui croissent sans limite. - Partagez l’état agressivement, pas l’interaction. Les structs
Codabledu modèle, les enrobages de persistance et les rendus deListvoyagent presque gratuitement. Les gestionnaires de minuteur, les gestionnaires audio, les content views, non. - Méritez chaque plateforme. Ne livrez pas sur watchOS parce que vous le pouvez. Livrez quand l’interaction principale de votre application correspond au modèle d’entrée de la plateforme. Évitez le reste.
Ce pattern fonctionne aux côtés des trois autres surfaces dont j’ai parlé pour la même famille d’applications : des App Intents typés pour Apple Intelligence, des serveurs MCP pour les agents LLM, Liquid Glass pour l’humain devant l’appareil. La couche la plus extérieure de la même pile, c’est la plateforme : les écrans sur lesquels l’application tourne tout court. Choisissez-les aussi délibérément que vous choisissez la surface IA.
FAQ
Pourquoi ne pas utiliser un Swift package pour le code partagé ?
Je l’ai envisagé. Pour trois fichiers, un Swift package ajoute plus de cérémonie qu’il n’en économise. Le système de build d’Xcode 26 d’Apple compile volontiers un seul fichier source dans plusieurs cibles quand vous cochez les cases Target Membership. Un package ajoute un Package.swift séparé, une cible de tests séparée, et une étape d’indirection que chaque refactor doit traverser. Pour un petit noyau partagé, la réponse la plus simple gagne.10
SwiftData fonctionne-t-il sur watchOS et tvOS ?
SwiftData est disponible sur iOS 17+, macOS 14+, watchOS 10+ et tvOS 17+, ce qui couvre toutes les plateformes que cible Return.11 La struct MeditationSession est un simple Codable, pas un @Model, parce que Return utilise NSUbiquitousKeyValueStore pour la synchronisation de l’historique de séances plutôt qu’un conteneur SwiftData. Le pattern fonctionne de la même manière pour les types @Model : le fichier de modèle est partagé, le conteneur de persistance diffère par plateforme s’il le faut.
Faut-il utiliser Mac Catalyst ou une cible Mac native ?
Catalyst est le bon outil quand l’application iPad est suffisamment bonne pour qu’une version Mac reconstruite par Catalyst se lise comme native. La cible principale de Return est une vraie cible multiplateforme (pas Catalyst), construite avec SwiftUI pour iOS, iPadOS et macOS dans un seul binaire. L’UI Mac utilise #if os(macOS) pour s’afficher différemment de l’iPad : barre latérale au lieu de feuille, équivalents clavier sur les boutons, etc. Catalyst aurait été plus simple, mais l’UI Mac aurait ressemblé à une application iPad sur un Mac, ce qui est précisément le mode d’échec pour lequel Catalyst est le plus connu.
Apple TV vaut-il la peine d’être livré pour une petite application ?
Probablement pas. Les applications Apple TV ont des cas d’usage très spécifiques (ambiant, médias, jeu casual). Si votre application n’entre pas dans l’un d’eux, l’audience de la plateforme est trop petite pour justifier les 10 fichiers Swift par application. Return cible tvOS précisément parce que les longues séances de méditation sur un écran à l’autre bout de la pièce sont l’un des rares cas d’usage proches de la productivité qui collent à la plateforme.
Combien de temps faut-il pour livrer sur les cinq plateformes ?
Difficile à chiffrer précisément ; cela dépend de l’application. Return a livré multiplateforme dès le premier jour plutôt que d’ajouter les plateformes par incréments, ce qui va plus vite que le rétrofit. Comme règle empirique : une cible iPhone uniquement plus le support iPad plus le support Mac représente environ 1,5x le temps iPhone seul. Ajouter Apple Watch en ajoute 0,5x. Ajouter Apple TV en ajoute 0,5x encore. Donc une première livraison cinq plateformes représente environ 2,5x l’effort iPhone seul, avec le bémol qu’il s’agissait d’un build assisté par agent où la majeure partie du code dupliqué a été éditée en masse par Claude Code plutôt que tapée à la main.
Références
-
Return de l’auteur, une application de minuteur de méditation publiée sur l’App Store le 21 avril 2026. Cibles natives : iOS 26+, iPadOS 26+, macOS 26+, watchOS 26+, tvOS 26+. SwiftUI partout.
NSUbiquitousKeyValueStorepour l’historique de séances multi-appareils. ↩ -
Apple Developer, « Configuring a Multi-Platform App » et la session « SwiftUI essentials » à la WWDC 2024. La recommandation Apple par défaut penche vers une cible unique avec adaptation pilotée par l’environnement ; la voie multi-cibles que prend cet article est un écart délibéré. ↩
-
Code de production dans
Return/Return/Shared/MeditationSession.swift,SessionStore.swift,SessionHistoryView.swift. Le commentaire d’en-tête deMeditationSession.swiftse lit : « Add this file to: Return, ReturnTV, ReturnWatch Watch App targets. » ↩ -
Code de production dans
Return/Return/VideoBackgroundView.swift(8 branches#if os(iOS)plus une branche#elseif os(macOS)),Return/Return/ContentView.swift(10 branches#if os),Return/Return/AudioManager.swift(6 branches#if os),Return/Return/LiveActivityManager.swift(8 branches#if os, fichier réservé à iOS). Comptes de branches obtenus en exécutantgrep -Ec '^\s*#if os\\(' <file>. ↩ -
Apple Developer, Human Interface Guidelines « Focus interactions ». Le moteur de focus de tvOS est un modèle de navigation fondamentalement différent du toucher sur iOS ou du pointeur sur Mac. ↩
-
Code de production dans
Return/ReturnTV/TVFocusModifier.swift. Définit deux typesButtonStyle(TVCapsuleButtonStyleetTVCircleButtonStyle) qui enveloppent@Environment(\.isFocused)pour inverser couleur et translucidité au focus et appliquer échelle + ombre. ↩ -
Apple Developer, « WatchConnectivity ». Le framework pour la communication appariée iPhone-vers-Watch ; Return ne l’utilise pas pour la synchronisation des séances, s’appuyant plutôt sur le Key-Value Store iCloud. ↩
-
Apple Developer, « WKExtendedRuntimeSession » et la clé Info.plist « WKBackgroundModes ». La valeur
mindfulnessest documentée ainsi : « Enables extended runtime sessions for silent meditation » — la bonne adéquation pour un minuteur de méditation. Return crée uneWKExtendedRuntimeSession()par défaut et déclareWKBackgroundModes: mindfulnessdans leInfo.plistde la cible watchOS. Code de production :Return/ReturnWatch Watch App/ReturnWatchApp.swiftdéfinitWatchSessionManager: NSObject, WKExtendedRuntimeSessionDelegate;WatchTimerManager.swiftlui délègue le travail d’exécution étendue. ↩ -
Analyse de l’auteur dans Building iOS Apps with AI Agents, le guide du praticien sur le développement iOS assisté par agent à travers 8 applications de production. ↩
-
Apple Developer, « Configuring a Multi-Platform App ». La Target Membership permet à un fichier source unique de compiler dans plusieurs cibles sans Swift package. Le bon outil pour les petits noyaux partagés. ↩
-
Apple Developer, disponibilité par plateforme de « SwiftData ». Disponible sur iOS 17+, iPadOS 17+, macOS 14+, watchOS 10+, tvOS 17+, visionOS 1+, couvrant les cinq familles de plateformes Apple. ↩
-
Apple Developer, « NSUbiquitousKeyValueStore ». Le Key-Value Store iCloud d’Apple pour synchroniser de petites quantités d’état entre les appareils d’un utilisateur. Taille totale du store plafonnée à 1 Mo sur l’ensemble des clés selon les limites publiées par Apple. Code de production :
Return/Return/Shared/SessionStore.swift. ↩ -
Apple Developer,
EnvironmentValues.isFocused. Disponible sur iOS 14+, iPadOS 14+, macOS 11+, tvOS 14+, watchOS 7+. L’API est multiplateforme ; ce qui diffère, c’est de savoir si le focus est l’affordance de navigation principale de l’utilisateur. ↩