← Tous les articles

HealthKit + SwiftUI sur iOS 26 : autorisation, types d'échantillons et patterns multi-plateformes tirés de la mise en production de deux apps

HealthKit est l’un des frameworks Apple les plus délicats à livrer correctement dans une app SwiftUI. Le flux d’autorisation comporte un piège d’échec transitoire qui peut bloquer définitivement les utilisateurs. Les types d’échantillons se répartissent maladroitement entre données quantitatives (HKQuantitySample pour la consommation d’eau, les pas, les calories) et données catégorielles (HKCategorySample pour les sessions de pleine conscience, le sommeil, le flux menstruel). La surface API async exige d’envelopper les appels HealthKit basés sur callback dans withCheckedThrowingContinuation. Et sur watchOS, les patterns changent encore.

J’ai livré HealthKit dans deux apps en production : Water (suivi de la consommation d’eau, HealthKitService d’environ 192 lignes)1 et Return (journalisation des sessions de pleine conscience, HealthKitManager d’environ 171 lignes plus un HealthKitPermissionSheet de 155 lignes).2 Ensemble, elles couvrent les deux principales formes d’échantillons HealthKit, les deux directions (lecture + écriture vs. écriture seule) ainsi que le déploiement mono-plateforme et multi-plateforme.

Cet article parcourt les patterns qui ont survécu à la production : l’UX de pré-permission, le modificateur SwiftUI .healthDataAccessRequest vs. l’API historique requestAuthorization, le wrapper async pour les requêtes d’échantillons, et les spécificités watchOS.

TL;DR

  • Le rapport de statut d’autorisation est asymétrique. L’API d’Apple vous indique « partage autorisé » de manière fiable mais ne vous indique pas « lecture refusée » pour des raisons de confidentialité. L’article explique comment détecter « l’utilisateur a été interrogé mais n’a pas accordé » sans inférer l’état de lecture.
  • Les données quantitatives utilisent HKQuantitySample avec HKQuantityType et HKUnit (Water utilise .literUnit(with: .milli) pour la consommation d’eau). Les données catégorielles utilisent HKCategorySample avec HKCategoryType (Return utilise .mindfulSession).
  • La feuille de pré-permission est le pattern le plus souvent négligé. La boîte de dialogue de permission système d’Apple est austère ; une View personnalisée affichée d’abord explique la valeur et améliore considérablement les taux d’acceptation.
  • HealthKit sur watchOS nécessite une instance HKHealthStore distincte, applique des règles plus strictes pour la nouvelle invite d’autorisation, et ne peut pas afficher de sheet SwiftUI pour l’UX de pré-permission.
  • Le pattern recordAuthorizationAttempt() dans Return empêche que les échecs de présentation transitoires soient traités comme un refus permanent.

Les deux formes d’échantillons

HealthKit divise son modèle d’échantillons selon un axe qui affecte chaque ligne de code que vous écrivez :3

Forme d’échantillon Usage typique API
HKQuantitySample eau, pas, calories, masse corporelle, fréquence cardiaque HKQuantityType + HKUnit + HKQuantity
HKCategorySample sessions de pleine conscience, sommeil, flux menstruel, activité sexuelle HKCategoryType + HKCategoryValue
HKWorkout exercice structuré (course, natation) HKWorkoutBuilder, HKWorkoutSession

L’eau est une quantité. Code de production réel issu de HealthKitService.swift :1

import HealthKit

@Observable
final class HealthKitService {
    static let shared = HealthKitService()

    private let healthStore = HKHealthStore()
    private let waterType = HKQuantityType(.dietaryWater)

    func logWater(amount: Double, date: Date = .now) async throws -> UUID {
        guard isAuthorized else { throw HealthKitError.notAuthorized }

        let quantity = HKQuantity(unit: .literUnit(with: .milli), doubleValue: amount)
        let sample = HKQuantitySample(
            type: waterType,
            quantity: quantity,
            start: date,
            end: date,
            metadata: [HKMetadataKeyWasUserEntered: true]
        )

        try await healthStore.save(sample)
        return sample.uuid
    }
}

Trois détails issus de la production :

  1. .literUnit(with: .milli) est l’unité canonique pour l’eau en millilitres. HealthKit accepte n’importe quelle unité (onces liquides US, litres), mais le constructeur compte parce qu’Apple normalise tout en litres en interne. Choisir .milli vous permet de stocker des valeurs entières (240, 500) plutôt que des litres fractionnaires (0,240, 0,500).
  2. HKMetadataKeyWasUserEntered: true signale l’échantillon comme saisi manuellement plutôt que mesuré. L’app Santé affiche un petit indicateur « saisi manuellement » sur ces échantillons ; l’utilisateur fait confiance aux données saisies manuellement différemment des données mesurées par balance.
  3. start et end sont la même Date pour les échantillons instantanés. Les quantités s’accumulent sur une fenêtre [start, end], mais pour « j’ai bu 240 ml à l’instant », la fenêtre se réduit à un point.

Une session de pleine conscience est catégorielle. Code de production réel issu du HealthKitManager.swift de Return :2

import HealthKit

@MainActor
class HealthKitManager {
    static let shared = HealthKitManager()
    let healthStore = HKHealthStore()
    let mindfulType = HKCategoryType(.mindfulSession)

    func saveMindfulSession(start: Date, end: Date) async -> Bool {
        guard isAvailable else { return false }
        guard end > start else { return false }

        let sample = HKCategorySample(
            type: mindfulType,
            value: HKCategoryValue.notApplicable.rawValue,
            start: start,
            end: end
        )
        // ... save via healthStore.save(sample) ...
    }
}

Deux détails :

  1. HKCategoryValue.notApplicable.rawValue est la sentinelle porteuse. Les sessions de pleine conscience n’ont pas de « valeur » significative (ce sont des marqueurs de durée), donc HealthKit exige une valeur catégorielle sentinelle (Int(0)) pour que le champ satisfasse au système de types. D’autres échantillons catégoriels ont des valeurs plus riches : le sommeil utilise HKCategoryValueSleepAnalysis.asleep, etc.
  2. La fenêtre start/end est réelle pour les échantillons catégoriels. Une session de pleine conscience de 10 minutes a start et end séparés de 10 minutes, et le total quotidien des minutes de pleine conscience de HealthKit additionne ces durées.

Le flux d’autorisation (et son piège)

L’autorisation HealthKit est asymétrique à dessein. authorizationStatus(for:) rapporte fidèlement le statut de partage/écriture (.sharingAuthorized, .sharingDenied, .notDetermined), mais l’octroi ou le refus de lecture n’est pas directement observable via cette API. Apple cache intentionnellement l’état de lecture pour empêcher les apps d’inférer quelles données existent dans le profil Santé d’un utilisateur.4 Vous apprenez la décision de lecture indirectement : les requêtes renvoient des données si l’utilisateur a accordé la lecture, et les requêtes renvoient des résultats vides s’il l’a refusée. Il n’existe aucun signal « lecture refusée » sur lequel votre code peut se brancher.

Les deux apps contournent cela différemment.

Water lit ses propres échantillons. Comme Water écrit et lit les entrées d’eau (pour alimenter « l’historique du jour »), son flux d’autorisation doit gérer le cas où le refus de lecture est invisible :1

private var typesToShare: Set<HKSampleType> { [waterType] }
private var typesToRead: Set<HKSampleType> { [waterType] }

func requestAuthorization() async throws {
    guard isHealthDataAvailable else { throw HealthKitError.notAvailable }

    try await healthStore.requestAuthorization(toShare: typesToShare, read: typesToRead)

    await MainActor.run { checkAuthorizationStatus() }
}

func checkAuthorizationStatus() {
    authorizationStatus = healthStore.authorizationStatus(for: waterType)
    isAuthorized = authorizationStatus == .sharingAuthorized
}

Notez ce que checkAuthorizationStatus ne fait PAS : interroger le statut d’autorisation de lecture. Water vérifie seulement le statut de partage. Si l’utilisateur accorde le partage mais refuse la lecture, l’interface de Water affichera « aucune entrée » parce que la lecture renvoie un résultat vide (et non à cause d’une erreur explicite). Water fait confiance à la décision de partage et laisse l’absence de données parler d’elle-même. L’utilisateur peut corriger cela depuis Réglages s’il y tient.

Return n’écrit qu’en sortie. Return enregistre les sessions de pleine conscience mais ne les relit jamais ; la liste de sessions qu’il affiche provient de son propre NSUbiquitousKeyValueStore, pas de HealthKit. Donc la requête d’autorisation de Return est en écriture seule :2

func requestAuthorization() async -> Bool {
    guard isAvailable else { return false }

    do {
        try await healthStore.requestAuthorization(
            toShare: [mindfulType],
            read: []  // We only need write access
        )
        hasRequestedHealthKit = authorizationStatus != .notDetermined
        return isAuthorizedToWrite()
    } catch {
        hasRequestedHealthKit = authorizationStatus != .notDetermined
        return false
    }
}

L’ensemble vide read: [] est intentionnel. Demander un accès en lecture dont vous n’avez pas besoin élargit la portée des permissions que vous devez justifier en App Review et déroute les utilisateurs qui voient « Return veut lire vos sessions de pleine conscience » alors que l’app n’en a manifestement pas besoin.

Le piège. Les deux apps ont convergé vers le même pattern défensif : ne marquer une tentative d’autorisation comme « complétée » que si le statut a effectivement dépassé .notDetermined. Le code naïf est :

hasRequestedHealthKit = true  // ❌ wrong: treats transient failures as denial

Le pattern correct issu de Return :2

hasRequestedHealthKit = authorizationStatus != .notDetermined  // ✓ checks real state

Pourquoi cela compte : le modificateur SwiftUI .healthDataAccessRequest et l’API sous-jacente requestAuthorization peuvent échouer à présenter la boîte de dialogue système dans certaines conditions (conflits de sheets, interruptions du cycle de vie de la vue, état transitoire de l’OS). Si vous marquez la tentative comme « complétée » avant de vérifier le statut réel, vous piégez les utilisateurs dans un état où votre app pense qu’ils ont décliné alors qu’aucune boîte de dialogue système n’est jamais apparue. Ils n’ont aucun moyen de revenir en arrière à moins qu’ils n’aillent dans Réglages → Confidentialité → Santé et accordent manuellement, ce à quoi ils ne penseront pas parce qu’ils n’ont jamais vu votre invite. Le recordAuthorizationAttempt() de Return existe spécifiquement pour ce cas.

La feuille de pré-permission

La boîte de dialogue de permission HealthKit d’Apple est correcte mais sans argumentaire. Elle affiche une liste des types que votre app souhaite partager ou lire, avec des bascules. Il n’y a aucun contexte sur le pourquoi l’app veut ces données, aucune explication des bénéfices, aucune marque de l’app. Les utilisateurs touchent Ne pas autoriser parce que l’invite est décontextualisée.

Return livre un HealthKitPermissionSheet qui apparaît avant la boîte de dialogue système. La feuille affiche l’icône de l’app Return à côté de l’icône d’Apple Santé (conforme aux HIG : l’icône Apple Santé ne doit pas être recadrée ni ombrée),5 énonce le bénéfice (« Suivez votre pratique » / « Enregistrez vos sessions de méditation comme Minutes de pleine conscience dans Apple Santé »), liste trois lignes de bénéfices (« Voyez votre pratique dans Apple Santé », « Synchronise sur tous vos appareils », « Fonctionne avec d’autres apps de bien-être »), et se termine par un seul bouton Continuer qui déclenche la requête système. Structure réelle issue de HealthKitPermissionSheet.swift :6

struct HealthKitPermissionSheet: View {
    var onEnableRequested: () -> Void
    var theme: Theme

    var body: some View {
        VStack(spacing: 0) {
            HStack(spacing: 16) {
                Image("ReturnAppIcon")
                    .resizable().scaledToFit().frame(width: 80, height: 80)
                    .clipShape(RoundedRectangle(cornerRadius: 18))

                Text("+").font(.title)

                Image("AppleHealthIcon")
                    .resizable().scaledToFit().frame(width: 80, height: 80)
                    // No clipShape; Apple HIG forbids altering the Health icon
            }

            // Title + subtitle + benefits list

            // (See discussion below for the production comment.)
            Button { onEnableRequested() } label: {
                Text("Continue")
            }
        }
        .interactiveDismissDisabled(true)
    }
}

Le interactiveDismissDisabled(true) plus l’absence de tout bouton d’annulation est un choix de conception délibéré. La directive 5.1.1 d’App Review d’Apple exige des apps qu’elles respectent les paramètres de confidentialité de l’utilisateur et interdit la manipulation, la tromperie ou la contrainte du consentement.10 Le commentaire au-dessus du bouton Continuer dans le code de production se lit :

Action unique vers l’avant : la boîte de dialogue HealthKit système possède le vrai choix oui/non. Directive Apple 5.1.1(iv) : l’écran d’amorçage ne peut inclure aucun chemin de sortie/abandon qui contournerait la requête de permission système.

Cette formulation est plus stricte que le texte littéral de la directive (qui parle de respecter le consentement et de ne pas le forcer, mais ne prescrit pas l’UX d’écran d’amorçage en ces termes). C’est l’interprétation de Return, codifiée dans le code source. L’intention : un utilisateur qui décline doit le faire sur l’écran d’Apple, pas sur celui de Return. Le résultat est une feuille avec une seule action vers l’avant et aucune issue de secours. Le texte et les lignes de bénéfices doivent gagner le tap ; il n’y a pas de branche « j’y réfléchirai ».

Le flux :

  1. L’utilisateur touche « Connecter Santé » dans les réglages de Return.
  2. La feuille de pré-permission apparaît. L’utilisateur lit l’explication.
  3. L’utilisateur touche Continuer. La feuille reste affichée pendant que requestAuthorization s’exécute et que la boîte de dialogue système apparaît.
  4. L’utilisateur accepte (ou refuse) dans la boîte de dialogue système. Return appelle recordAuthorizationAttempt() pour capturer le résultat et la feuille se ferme.

Pourquoi s’embêter. Apple n’a pas publié de chiffres officiels sur l’augmentation du taux d’octroi grâce aux feuilles de pré-permission, mais chaque développeur iOS avec qui j’ai parlé qui a mené à la fois un test A/B et eu la patience de le poursuivre a rapporté la même direction : les feuilles de pré-permission augmentent considérablement le taux d’octroi d’autorisation de partage. Le pattern est désormais suffisamment courant pour que les propres modèles d’Apple (Photos, Caméra, Localisation) incluent de plus en plus leur propre UX de pré-permission.

La variante watchOS

HealthKit sur watchOS partage la même surface API que HealthKit sur iOS (HKHealthStore, types d’échantillons, autorisation), mais avec trois différences structurelles :

  1. Un nouveau HKHealthStore par app de montre. L’app de la montre et l’app iPhone jumelée ont chacune leur propre instance HKHealthStore. Les deux peuvent écrire dans la base de données HealthKit de l’utilisateur, les deux peuvent lire leurs propres échantillons. Le store n’est pas partagé à travers la paire.
  2. Pas de sheets SwiftUI pour la pré-permission. Les hiérarchies de vues watchOS ne prennent pas en charge les sheets comme iOS le fait. L’UX de pré-permission doit être un plein écran.
  3. Règles de réinvitation plus strictes. L’appel requestAuthorization sur watchOS est plus conservateur quant à la réaffichage de la boîte de dialogue système si l’utilisateur a précédemment refusé ; vous devrez peut-être diriger les utilisateurs vers l’app Watch sur leur iPhone pour modifier le réglage, puisque la montre elle-même n’a pas d’interface Réglages → Confidentialité → Santé.

Code de production réel issu de WatchHealthKitManager.swift :7

@MainActor
class WatchHealthKitManager {
    static let shared = WatchHealthKitManager()
    let healthStore = HKHealthStore()
    let mindfulType = HKCategoryType(.mindfulSession)

    private init() {}

    var isAvailable: Bool { HKHealthStore.isHealthDataAvailable() }

    /// Returns true if the system request completed (success or already-authorized).
    /// Caller checks isAuthorizedToWrite() separately for the actual share state.
    func requestAuthorization() async -> Bool {
        guard isAvailable else { return false }
        do {
            try await healthStore.requestAuthorization(toShare: [mindfulType], read: [])
            return true
        } catch {
            return false
        }
    }

    func isAuthorizedToWrite() -> Bool {
        guard isAvailable else { return false }
        return healthStore.authorizationStatus(for: mindfulType) == .sharingAuthorized
    }
    // ... saveMindfulSession identical to iOS variant ...
}

La séparation est délibérée : requestAuthorization rapporte si l’appel système a réussi, et isAuthorizedToWrite rapporte le résultat que l’utilisateur a choisi. Séparer les deux sur la montre rend le code plus facile à raisonner ; sur iOS, la séparation équivalente apparaît sous la forme du couple recordAuthorizationAttempt() plus isAuthorizedToWrite() sur HealthKitManager.

La classe Watch fait à peu près la moitié de la taille de celle d’iOS parce qu’elle n’a pas besoin :

  • Du flag UserDefaults hasRequestedHealthKit (l’UX de la montre a moins de chemins de seconde tentative à supporter).
  • De la plomberie HealthKitPermissionSheet (pas d’UI de sheet sur watchOS).
  • De la méthode recordAuthorizationAttempt() (le flux plus étroit de la montre a moins de cas limites de défaillance transitoire).

Le compromis est que l’app watchOS tombe parfois dans un état refusé-sans-recours pour les utilisateurs qui ont décliné la boîte de dialogue système et ne réalisent pas qu’ils peuvent corriger cela depuis l’app Watch sur l’iPhone. Return affiche une petite instruction in-app dans ce cas (« Ouvrir l’app Watch sur l’iPhone → Ma Watch → Confidentialité → Santé ») plutôt que d’essayer de réinviter.

Ce que je construirais différemment

Trois leçons tirées de la livraison de HealthKit dans deux apps de production.

Les deux APIs fonctionnent ; le choix dépend du contexte de présentation. Le modificateur .healthDataAccessRequest de SwiftUI enveloppe l’API historique dans une forme plus déclarative et gère correctement le contexte de présentation sur iPadOS. Return utilise le modificateur, exposant ses types de partage via une propriété publique mindfulShareTypes sur HealthKitManager afin qu’une vue parent SwiftUI puisse le câbler. Water utilise directement healthStore.requestAuthorization historique parce que le flux d’autorisation de Water s’exécute depuis un contexte non-View (un service @Observable). Cette séparation est un pattern utile : préférez le modificateur lorsque la requête provient d’un événement du cycle de vie SwiftUI (tap sur un bouton à l’intérieur d’une sheet), repliez-vous sur l’API historique lorsque la requête provient d’un service.

Les feuilles de pré-permission valent le coût d’ingénierie sur iOS, pas sur watchOS. La feuille de pré-permission ajoute peut-être quatre heures de travail (vue de la sheet, rédaction, thématisation, intégration). L’augmentation du taux d’octroi sur iOS est suffisamment importante pour que ces quatre heures soient un investissement évident. Sur watchOS, la pré-permission plein écran équivalente est plus intrusive (elle prend tout l’écran de la montre au lieu d’être une feuille), l’utilisateur est moins susceptible de lire un long texte sur un petit écran, et le flux UX de la montre a moins de points d’entrée où l’utilisateur demande une fonctionnalité qui requiert HealthKit. J’ai livré Return sans en avoir une sur watchOS et je ne l’ai pas regretté.

Suivez hasRequestedHealthKit séparément du statut d’autorisation en direct. L’API HealthKit vous indique le statut d’autorisation actuel, mais elle ne vous indique pas si vous avez déjà demandé. La distinction compte parce que le bon comportement au second tap en dépend : le premier tap doit appeler requestAuthorization ; le second tap, si l’utilisateur a précédemment refusé, doit afficher une alerte « Réglages → Confidentialité → Santé » plutôt que de rappeler l’API (qui ne fait silencieusement rien dans un état refusé). Le flag UserDefaults hasRequestedHealthKit est ce qui rend le second tap utile.

Quand ne pas utiliser HealthKit

Le refus fait partie du design.

N’écrivez pas dans HealthKit juste parce que vous le pouvez. Une app de minuteur de focus n’a pas besoin d’écrire un workout. Une app de prise de notes n’a pas besoin de journaliser la pleine conscience. Ajouter HealthKit parce que c’est une « intégration gratuite » élargit votre empreinte de confidentialité, votre friction d’autorisation et la portée de votre questionnaire d’App Review sans donner à l’utilisateur de valeur matérielle.

Ne lisez pas HealthKit si vous pouvez l’éviter. L’accès en lecture est plus difficile à justifier en App Review et plus difficile à expliquer aux utilisateurs. De nombreuses apps qui lisent HealthKit pourraient se contenter d’écrire et laisser les utilisateurs voir leurs données dans Apple Santé ; le flux de lecture double la surface pour un gain UX négligeable.

N’utilisez pas HealthKit sur Mac pour des données purement multi-appareils. macOS prend en charge HealthKit depuis macOS 13, mais la plupart des données Santé proviennent de l’iPhone ou de l’Apple Watch. Si votre app Mac a besoin des mêmes données, écrivez dans HealthKit sur l’iPhone et laissez la synchronisation multi-appareils d’Apple les faire remonter sur le Mac. Les écritures HealthKit directes depuis le Mac sont valides mais rares en pratique.

Ne livrez pas sans tester le chemin accordé-puis-révoqué. Les utilisateurs accordent la permission, puis la révoquent depuis Réglages → Confidentialité → Santé. Votre app doit gérer la révocation avec élégance, généralement en affichant l’instruction de lien profond Réglages la prochaine fois qu’ils essaieront d’utiliser la fonctionnalité. Water et Return livrent tous deux ce chemin ; aucun ne l’a réussi du premier coup.

Ce que cela signifie pour les apps SwiftUI livrant HealthKit sur iOS 26+

Trois enseignements.

  1. Décidez écriture seule vs. lecture+écriture avant de concevoir l’UI. L’écriture seule est une surface plus petite et une App Review plus rapide. Lecture+écriture est plus flexible mais ajoute l’asymétrie du refus de lecture invisible.
  2. Livrez une feuille de pré-permission sur iOS. L’augmentation du taux d’octroi est réelle. Utilisez l’icône d’Apple correctement (pas de recadrage, pas d’ombre), énoncez le bénéfice, puis appelez l’API système.
  3. Traitez HealthKit sur watchOS comme une variante plus petite et plus simple du pattern iOS. Moins de cérémonie, pas de sheets, autorisation à objectif unique. Dirigez les utilisateurs vers l’app Watch sur l’iPhone pour réaccorder.

Associez cet article à mes écrits précédents pour la même famille d’apps : App Intents typés pour Apple Intelligence ; serveurs MCP pour les agents LLM croisés ; patterns Liquid Glass pour la couche visuelle ; livraison multi-plateforme pour la portée multi-appareils. HealthKit est la couche source de données, située sous la couche visuelle et les surfaces d’intégration.8

FAQ

Puis-je partager l’autorisation entre une app iOS et son extension watchOS ?

Non. Le HKHealthStore iOS et le HKHealthStore watchOS sont indépendants. L’utilisateur accorde l’autorisation séparément sur chaque plateforme via des boîtes de dialogue système distinctes. Votre code de chaque côté vérifie son propre statut d’autorisation ; vous ne pouvez pas lire le statut d’iOS depuis la montre ni inversement.

Qu’arrive-t-il à mes échantillons si l’utilisateur révoque l’accès en écriture ?

Les échantillons existants restent dans HealthKit. L’utilisateur peut les supprimer manuellement depuis l’app Santé s’il le souhaite, mais révoquer l’accès de votre app n’arrête que les nouvelles écritures. Votre app ne peut plus enregistrer ni modifier d’échantillons, mais les données historiques de l’utilisateur sont préservées.

Le modificateur SwiftUI .healthDataAccessRequest est-il sûr à utiliser en production ?

Il fonctionne sur iOS 17.4+ et a été affiné dans les versions ultérieures. Return utilise le modificateur (il expose mindfulShareTypes sur HealthKitManager pour que SwiftUI le consomme). Water utilise directement healthStore.requestAuthorization historique parce que la requête de Water provient d’un service @Observable non-View. Choisissez en fonction de l’endroit où la requête est initiée : événement du cycle de vie SwiftUI → modificateur ; service ou contexte non-View → API historique.

Pourquoi le statut d’autorisation HealthKit indique-t-il .sharingAuthorized même quand je n’ai jamais demandé d’accès en partage ?

Ce n’est pas le cas. Le statut est par HKObjectType. Si vous vérifiez authorizationStatus(for: heartRateType) et que vous n’avez jamais demandé la fréquence cardiaque, vous obtiendrez .notDetermined. Le statut ne passe à .sharingAuthorized qu’après une autorisation réussie pour ce type spécifique.

Ai-je besoin d’un manifeste de confidentialité pour HealthKit ?

Oui. Les apps qui touchent à HealthKit doivent déclarer cet usage dans PrivacyInfo.xcprivacy (le manifeste de confidentialité) et dans le questionnaire de confidentialité d’App Store Connect. Les entrées pertinentes sont NSHealthShareUsageDescription et NSHealthUpdateUsageDescription dans Info.plist, plus les déclarations correspondantes dans le manifeste de confidentialité.9

Références


  1. Code de production dans Water/Water/Services/HealthKitService.swift (192 lignes). Water de l’auteur, une app SwiftUI de suivi de la consommation d’eau disponible sur iOS, iPadOS, macOS, watchOS et visionOS. Utilise HKQuantitySample avec HKQuantityType(.dietaryWater) et le flag HKMetadataKeyWasUserEntered

  2. Code de production dans Return/Return/HealthKitManager.swift (171 lignes). Return de l’auteur, une app SwiftUI de minuteur de méditation disponible sur iOS, iPadOS, macOS, watchOS et tvOS. Utilise HKCategorySample avec HKCategoryType(.mindfulSession) et HKCategoryValue.notApplicable.rawValue

  3. Apple Developer, « HealthKit framework ». Les deux principaux types d’échantillons du framework sont HKQuantitySample (pour les quantités mesurables comme l’eau, les pas, les calories) et HKCategorySample (pour les événements non quantitatifs comme les sessions de pleine conscience, le sommeil, le flux menstruel). HKWorkout couvre l’exercice structuré. 

  4. Apple Developer, « Authorizing access to health data ». Apple cache intentionnellement l’état de refus de lecture pour empêcher les apps d’inférer quelles données existent dans le profil Santé d’un utilisateur. La méthode authorizationStatus(for:) ne renvoie de résultats honnêtes que pour l’accès en partage. 

  5. Apple Developer, « Apple Health icon usage » Human Interface Guidelines. L’icône Santé d’Apple ne doit pas être modifiée, recadrée, ombrée ou recolorée ; reproduisez-la à fidélité 1:1 dans les UI promotionnelles et de pré-permission. 

  6. Code de production dans Return/Return/HealthKitPermissionSheet.swift (155 lignes). View de pré-permission affichée avant le déclenchement de la boîte de dialogue HealthKit système. Associe l’icône de l’app Return à l’icône d’Apple Santé, explique le bénéfice et déclenche l’autorisation via une closure de callback au tap de l’utilisateur. 

  7. Code de production dans Return/ReturnWatch Watch App/WatchHealthKitManager.swift (86 lignes). La variante watchOS de HealthKitManager. HKHealthStore indépendant, pas de flag hasRequestedHealthKit, pas de plomberie de feuille de permission. 

  8. Analyse de l’auteur : HealthKit est la couche source de données d’une app iOS, App Intents est la surface IA-système, MCP est la surface d’agent LLM croisé, Liquid Glass est la surface visuelle. Les quatre couches se composent en un seul produit livré sur les cinq plateformes Apple. 

  9. Apple Developer, « Privacy manifest files ». L’utilisation de HealthKit doit être déclarée dans PrivacyInfo.xcprivacy plus les clés NSHealthShareUsageDescription et NSHealthUpdateUsageDescription dans Info.plist

  10. Apple Developer, « App Review Guideline 5.1.1 ». Les apps doivent respecter les paramètres de confidentialité de l’utilisateur, demander le consentement avant de collecter des données personnelles, et ne peuvent pas manipuler, tromper ou forcer le consentement. La formulation exacte concernant les chemins de sortie d’écran d’amorçage dans cet article reflète l’interprétation de Return plutôt que le texte littéral de la directive. 

Articles connexes

Liquid Glass in SwiftUI: Three Patterns From Shipping Return on iOS 26

Apple's Liquid Glass is a one-line SwiftUI API. Three patterns from Return go beyond .glassEffect(): glass on text via C…

17 min de lecture

Five Apple Platforms, Three Shared Files: How Return Actually Ships Cross-Platform SwiftUI

Return runs on iPhone, iPad, Mac, Apple Watch, and Apple TV. Three Swift files are shared across all five targets out of…

18 min de lecture

The Cleanup Layer Is the Real AI Agent Market

Charlie Labs pivoted from building agents to cleaning up after them. The AI agent market is moving from generation to pr…

15 min de lecture