← Todos los articulos

HealthKit + SwiftUI en iOS 26: autorización, tipos de muestra y patrones multiplataforma de dos apps en producción

HealthKit es uno de los frameworks de Apple más complicados de implementar correctamente en una app SwiftUI. El flujo de autorización tiene una trampa de fallo transitorio que puede dejar a los usuarios bloqueados de forma permanente. Los tipos de muestra se dividen de manera incómoda entre datos cuantitativos (HKQuantitySample para consumo de agua, pasos, calorías) y datos categóricos (HKCategorySample para sesiones de mindfulness, sueño, flujo menstrual). La superficie async API requiere envolver las llamadas de HealthKit basadas en callbacks dentro de withCheckedThrowingContinuation. Y en watchOS los patrones cambian de nuevo.

He llevado HealthKit a producción en dos apps: Water (seguimiento de consumo de agua, HealthKitService de ~192 líneas)1 y Return (registro de sesiones de mindfulness, HealthKitManager de ~171 líneas más un HealthKitPermissionSheet de 155 líneas).2 Juntas cubren ambas formas principales de muestras de HealthKit, ambas direcciones (lectura + escritura vs. solo escritura) y despliegue tanto en una sola plataforma como multiplataforma.

Este ensayo recorre los patrones que sobrevivieron en producción: UX de pre-permiso, el modificador SwiftUI .healthDataAccessRequest vs. el API heredado requestAuthorization, el wrapper async para las consultas de muestras y los deltas específicos de watchOS.

TL;DR

  • El reporte del estado de autorización es asimétrico. La API de Apple te dice de manera fiable “share authorized” pero no te dice “read denied” por razones de privacidad. El artículo cubre cómo detectar “se le preguntó al usuario pero no concedió acceso” sin inferir el estado de lectura.
  • Los datos cuantitativos usan HKQuantitySample con HKQuantityType y HKUnit (Water usa .literUnit(with: .milli) para el consumo de agua). Los datos categóricos usan HKCategorySample con HKCategoryType (Return usa .mindfulSession).
  • La hoja de pre-permiso es el patrón que más se omite. El diálogo de permiso del sistema de Apple es estéril; mostrar primero una View personalizada que explique el valor mejora drásticamente las tasas de concesión.
  • HealthKit en watchOS necesita una instancia separada de HKHealthStore, tiene reglas más estrictas de re-solicitud de autorización y no puede mostrar un sheet de SwiftUI para la UX de pre-permiso.
  • El patrón recordAuthorizationAttempt() en Return evita que los fallos transitorios de presentación se traten como una denegación permanente.

Las dos formas de muestra

HealthKit divide su modelo de muestras a lo largo de un eje que afecta cada línea de código que escribes:3

Forma de muestra Uso típico API
HKQuantitySample agua, pasos, calorías, masa corporal, frecuencia cardíaca HKQuantityType + HKUnit + HKQuantity
HKCategorySample sesiones de mindfulness, sueño, flujo menstrual, actividad sexual HKCategoryType + HKCategoryValue
HKWorkout ejercicio estructurado (correr, nadar) HKWorkoutBuilder, HKWorkoutSession

El agua es una cantidad. Código real de producción 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
    }
}

Tres detalles de producción:

  1. .literUnit(with: .milli) es la unidad canónica para el agua en mililitros. HealthKit aceptará cualquier unidad (onzas líquidas estadounidenses, litros), pero el constructor importa porque Apple normaliza todo internamente a litros. Elegir .milli te permite almacenar valores enteros (240, 500) en lugar de litros fraccionarios (0,240, 0,500).
  2. HKMetadataKeyWasUserEntered: true marca la muestra como ingresada manualmente en lugar de medida. La app Health muestra un pequeño indicador de “ingresado manualmente” en estas muestras; el usuario confía en los datos ingresados manualmente de manera distinta a los datos medidos por una báscula.
  3. start y end son la misma Date para muestras instantáneas. Las cantidades se acumulan en una ventana [start, end], pero para “acabo de beber 240 ml” la ventana se reduce a un punto.

Una sesión de mindfulness es categórica. Código real de producción de 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) ...
    }
}

Dos detalles:

  1. HKCategoryValue.notApplicable.rawValue es el centinela que sostiene todo el peso. Las sesiones de mindfulness no tienen un “valor” significativo (son marcadores de duración), por lo que HealthKit requiere un valor de categoría centinela (Int(0)) para que el campo satisfaga el sistema de tipos. Otras muestras de categoría tienen valores más ricos: el sueño usa HKCategoryValueSleepAnalysis.asleep, etc.
  2. La ventana start/end es real para las muestras de categoría. Una sesión de mindfulness de 10 minutos tiene start y end separados por 10 minutos, y el conteo diario de minutos de mindfulness de HealthKit suma estas duraciones.

El flujo de autorización (y su trampa)

La autorización de HealthKit es asimétrica a propósito. authorizationStatus(for:) reporta el estado de compartir/escritura con honestidad (.sharingAuthorized, .sharingDenied, .notDetermined), pero la concesión o denegación de lectura no es directamente observable a través de esa API. Apple oculta intencionalmente el estado de lectura para evitar que las apps infieran qué datos existen en el perfil de Health de un usuario.4 Aprendes la decisión de lectura indirectamente: las consultas devuelven datos si el usuario concedió la lectura, y las consultas devuelven resultados vacíos si el usuario denegó la lectura. No hay una señal de “la lectura fue denegada” sobre la cual tu código pueda ramificarse.

Ambas apps abordan esto de manera diferente.

Water lee sus propias muestras. Como Water tanto escribe como lee entradas de agua (para poblar el “historial de hoy”), su flujo de autorización debe manejar el caso de la denegación de lectura 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
}

Observa lo que checkAuthorizationStatus NO hace: preguntar por el estado de autorización de lectura. Water solo verifica el estado de compartir. Si el usuario concede compartir pero deniega la lectura, la UI de Water mostrará “sin entradas” porque la lectura devuelve vacío (no por un error explícito). Water confía en la decisión de compartir y deja que la ausencia de datos hable por sí misma. El usuario puede arreglarlo desde Configuración si le importa.

Return solo escribe. Return registra sesiones de mindfulness pero nunca las vuelve a leer; la lista de sesiones que muestra proviene de su propio NSUbiquitousKeyValueStore, no de HealthKit. Por lo tanto, la solicitud de autorización de Return es solo de escritura: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
    }
}

El conjunto vacío read: [] es intencional. Pedir acceso de lectura que no necesitas amplía el alcance del permiso que tienes que justificar en App Review y confunde a los usuarios que ven “Return quiere leer tus sesiones de mindfulness” cuando la app obviamente no lo necesita.

La trampa. Ambas apps convergieron en el mismo patrón defensivo: marcar un intento de autorización como “completado” solo si el estado realmente avanzó más allá de .notDetermined. El código ingenuo es:

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

El patrón correcto de Return:2

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

Por qué importa: el modificador SwiftUI .healthDataAccessRequest y la API subyacente requestAuthorization pueden fallar al presentar el diálogo del sistema bajo ciertas condiciones (conflictos de sheets, interrupciones del ciclo de vida de la vista, estado transitorio del SO). Si marcas el intento como “completado” antes de verificar el estado real, atrapas a los usuarios en un estado donde tu app cree que rechazaron pero nunca apareció ningún diálogo del sistema. No tienen forma de regresar a menos que vayan a Configuración → Privacidad → Salud y concedan manualmente, lo cual no se les ocurrirá hacer porque nunca vieron tu solicitud. El recordAuthorizationAttempt() de Return existe específicamente para este caso.

La hoja de pre-permiso

El diálogo de permiso de HealthKit de Apple es correcto pero no convence. Muestra una lista de tipos que tu app quiere compartir o leer, con interruptores. No hay contexto sobre por qué la app quiere los datos, ni explicación de beneficios, ni marca de la app. Los usuarios pulsan No permitir porque la solicitud está descontextualizada.

Return incluye una HealthKitPermissionSheet que aparece antes del diálogo del sistema. La hoja muestra el ícono de la app Return junto al ícono de Apple Health (correcto según la HIG: el ícono de Apple Health no debe recortarse ni sombrearse),5 indica el beneficio (“Track Your Practice” / “Save your meditation sessions as Mindful Minutes in Apple Health”), lista tres filas de beneficios (“See your practice in Apple Health”, “Syncs across all your devices”, “Works with other wellness apps”), y termina en un único botón hacia adelante Continue que activa la solicitud del sistema. Estructura real 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)
    }
}

El interactiveDismissDisabled(true) junto con la falta de cualquier botón de cancelar es una decisión deliberada de diseño. La directriz 5.1.1 de App Review de Apple exige que las apps respeten la configuración de privacidad del usuario y prohíbe manipular, engañar o forzar el consentimiento.10 El comentario del código de producción sobre el botón Continue dice:

Single forward action: the system HealthKit dialog owns the real yes/no choice. Apple Guideline 5.1.1(iv): the priming screen may not include any exit/dismiss path that bypasses the system permission request.

Ese encuadre es más estricto que el texto literal de la directriz (que habla de respetar el consentimiento y no forzarlo, pero no prescribe la UX de la pantalla previa con esas palabras). Es la interpretación de Return, codificada en el código fuente. La intención: un usuario que rechaza debe hacerlo en la pantalla de Apple, no en la de Return. El resultado es una hoja con una única acción hacia adelante y sin escape. La copia y las filas de beneficios tienen que ganarse el toque; no hay una rama de “lo pensaré más tarde”.

El flujo:

  1. El usuario pulsa “Connect Health” en la configuración de Return.
  2. Aparece la hoja de pre-permiso. El usuario lee la explicación.
  3. El usuario pulsa Continue. La hoja permanece montada mientras se ejecuta requestAuthorization y aparece el diálogo del sistema.
  4. El usuario acepta (o deniega) en el diálogo del sistema. Return llama a recordAuthorizationAttempt() para capturar el resultado y la hoja se cierra.

Por qué vale la pena. Apple no ha publicado cifras oficiales sobre el aumento en la tasa de concesión derivado de las hojas de pre-permiso, pero todos los desarrolladores de iOS con los que he hablado que han hecho una prueba A/B y han tenido la paciencia de ejecutarla han reportado la misma dirección: las hojas de pre-permiso aumentan drásticamente la tasa de concesión de autorización de compartir. El patrón ahora es lo suficientemente común como para que las propias plantillas de Apple (Fotos, Cámara, Ubicación) incluyan cada vez más su propia UX de pre-permiso.

La variante de watchOS

HealthKit en watchOS comparte la misma superficie API que HealthKit en iOS (HKHealthStore, tipos de muestra, autorización), pero con tres deltas estructurales:

  1. Un nuevo HKHealthStore por cada app de watch. La app de watch y la app de iPhone emparejada tienen cada una sus propias instancias de HKHealthStore. Ambas pueden escribir en la base de datos de HealthKit del usuario, ambas pueden leer sus propias muestras. El store no se comparte entre el par.
  2. No hay sheets de SwiftUI para pre-permiso. Las jerarquías de vista de watchOS no soportan sheets de la forma en que lo hace iOS. La UX de pre-permiso tiene que ser una pantalla completa.
  3. Reglas más estrictas de re-solicitud. La llamada requestAuthorization de watchOS es más conservadora a la hora de volver a presentar el diálogo del sistema si el usuario lo denegó previamente; puede que necesites dirigir a los usuarios a la app Watch en su iPhone para cambiar la configuración, ya que el watch en sí no tiene una UI de Configuración → Privacidad → Salud.

Código real de producción 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 división es deliberada: requestAuthorization reporta si la llamada al sistema tuvo éxito, y isAuthorizedToWrite reporta el resultado que el usuario eligió. Dividir esto en el watch hace que el código sea más fácil de razonar; en iOS la división equivalente aparece como el par recordAuthorizationAttempt() más isAuthorizedToWrite() en HealthKitManager.

La clase de Watch es aproximadamente la mitad del tamaño de la de iOS porque no necesita:

  • La bandera hasRequestedHealthKit de UserDefaults (la UX del watch tiene menos rutas de segundo intento que soportar).
  • La fontanería de HealthKitPermissionSheet (no hay UI de sheet en watchOS).
  • El método recordAuthorizationAttempt() (el flujo más estrecho del watch tiene menos casos límite de fallos transitorios).

La compensación es que la app de watchOS a veces llega a un estado de denegación-sin-recurso para los usuarios que rechazaron el diálogo del sistema y no se dan cuenta de que pueden arreglarlo desde la app Watch en el iPhone. Return muestra una pequeña instrucción in-app en este caso (“Open Watch app on iPhone → My Watch → Privacy → Health”) en lugar de intentar volver a solicitar.

Lo que construiría de manera diferente

Tres lecciones de llevar HealthKit a producción en dos apps.

Ambas APIs funcionan; la elección depende del contexto de presentación. El modificador .healthDataAccessRequest de SwiftUI envuelve la API heredada en una forma más declarativa y maneja correctamente el contexto de presentación en iPadOS. Return usa el modificador, exponiendo sus tipos para compartir a través de una propiedad pública mindfulShareTypes en HealthKitManager para que una vista padre de SwiftUI pueda conectarlo. Water usa el healthStore.requestAuthorization heredado directamente porque el flujo de autorización de Water se ejecuta desde un contexto que no es View (un servicio @Observable). La división es un patrón útil: prefiere el modificador cuando la solicitud se origina desde un evento del ciclo de vida de SwiftUI (toque de botón dentro de una hoja), recurre a la API heredada cuando la solicitud se origina desde un servicio.

Las hojas de pre-permiso valen el costo de ingeniería en iOS, no en watchOS. La hoja de pre-permiso añade quizás cuatro horas de trabajo (vista del sheet, copia, theming, integración). El aumento en la tasa de concesión en iOS es lo suficientemente grande como para que esas cuatro horas sean una inversión obvia. En watchOS, el equivalente de pre-permiso a pantalla completa es más intrusivo (toma toda la pantalla del watch en lugar de ser una hoja), es menos probable que el usuario lea copia larga en una pantalla pequeña, y el flujo de UX del watch tiene menos puntos de entrada donde el usuario está pidiendo una función que requiera HealthKit. Lancé Return sin una en watchOS y no lo he lamentado.

Rastrea hasRequestedHealthKit por separado del estado de autorización en vivo. La API de HealthKit te dice el estado de autorización actual, pero no te dice si alguna vez has preguntado. La distinción importa porque el comportamiento correcto del segundo toque depende de ello: el primer toque debe llamar a requestAuthorization; el segundo toque, si el usuario denegó previamente, debe mostrar una alerta de “Configuración → Privacidad → Salud” en lugar de volver a llamar a la API (que silenciosamente no hace nada en un estado denegado). La bandera hasRequestedHealthKit de UserDefaults es lo que hace útil al segundo toque.

Cuándo no usar HealthKit

El rechazo es parte del diseño.

No escribas en HealthKit solo porque puedes. Una app de temporizador de enfoque no necesita escribir un workout. Una app de toma de notas no necesita registrar mindfulness. Añadir HealthKit porque es una “integración gratis” expande tu huella de privacidad, tu fricción de autorización y el alcance de tu cuestionario de App Review sin darle al usuario un valor material.

No leas HealthKit si puedes evitarlo. El acceso de lectura es más difícil de justificar en App Review y más difícil de explicar a los usuarios. Muchas apps que leen HealthKit podrían solo escribir y dejar que los usuarios vean sus datos en Apple Health; el flujo de lectura duplica el área de superficie por una ganancia de UX insignificante.

No uses HealthKit en Mac para datos exclusivos entre dispositivos. macOS soporta HealthKit desde macOS 13, pero la mayoría de los datos de Health se originan en iPhone o Apple Watch. Si tu app de Mac necesita los mismos datos, escribe en HealthKit en el iPhone y deja que la sincronización entre dispositivos de Apple los muestre en Mac. Las escrituras directas de HealthKit desde Mac son válidas pero raras en la práctica.

No lances sin probar la ruta de denegado-luego-revocado. Los usuarios conceden permiso, luego lo revocan desde Configuración → Privacidad → Salud. Tu app necesita manejar la revocación con elegancia, generalmente mostrando la instrucción de deep-link a Configuración la próxima vez que intenten usar la función. Tanto Water como Return incluyen esta ruta; ninguna la implementó bien al primer intento.

Lo que esto significa para apps SwiftUI que envían HealthKit en iOS 26+

Tres conclusiones.

  1. Decide solo-escritura vs. lectura+escritura antes de diseñar la UI. Solo-escritura es una superficie más pequeña y un App Review más rápido. Lectura+escritura es más flexible pero añade la asimetría de denegación-de-lectura-invisible.
  2. Lanza una hoja de pre-permiso en iOS. El aumento en la tasa de concesión es real. Usa el ícono de Apple correctamente (sin recortar, sin sombra), indica el beneficio, luego llama a la API del sistema.
  3. Trata HealthKit en watchOS como una variante más pequeña y simple del patrón de iOS. Menos ceremonia, sin sheets, autorización de propósito único. Dirige a los usuarios a la app Watch en el iPhone para volver a conceder.

Combina este post con mis escritos anteriores para la misma familia de apps: App Intents tipados para Apple Intelligence; servidores MCP para agentes multi-LLM; patrones de Liquid Glass para la capa visual; envío multiplataforma para el alcance entre dispositivos. HealthKit es la capa de fuente de datos, situada bajo la capa visual y las superficies de integración.8

FAQ

¿Puedo compartir la autorización entre una app de iOS y su extensión de watchOS?

No. El HKHealthStore de iOS y el HKHealthStore de watchOS son independientes. El usuario concede la autorización por separado en cada plataforma a través de diálogos del sistema separados. Tu código en cada lado verifica su propio estado de autorización; no puedes leer el estado de iOS desde el watch ni viceversa.

¿Qué pasa con mis muestras si el usuario revoca el acceso de escritura?

Las muestras existentes permanecen en HealthKit. El usuario puede eliminarlas manualmente desde la app Health si así lo decide, pero revocar el acceso de tu app solo detiene las nuevas escrituras. Tu app ya no puede guardar ni modificar muestras, pero los datos históricos del usuario se conservan.

¿Es seguro usar el modificador SwiftUI .healthDataAccessRequest en producción?

Funciona en iOS 17.4+ y se ha refinado en versiones posteriores. Return usa el modificador (expone mindfulShareTypes en HealthKitManager para que SwiftUI lo consuma). Water usa el healthStore.requestAuthorization heredado directamente porque la solicitud de Water se origina desde un servicio @Observable que no es View. Elige según dónde se inicia la solicitud: evento del ciclo de vida de SwiftUI → modificador; servicio o contexto que no es View → API heredada.

¿Por qué el estado de autorización de HealthKit dice .sharingAuthorized incluso cuando nunca pedí acceso de compartir?

No lo hace. El estado es por HKObjectType. Si verificas authorizationStatus(for: heartRateType) y nunca solicitaste la frecuencia cardíaca, obtendrás .notDetermined. El estado solo pasa a .sharingAuthorized después de una autorización exitosa para ese tipo específico.

¿Necesito un manifiesto de privacidad para HealthKit?

Sí. Las apps que tocan HealthKit deben declarar el uso en PrivacyInfo.xcprivacy (el manifiesto de privacidad) y en el cuestionario de privacidad de App Store Connect. Las entradas relevantes son NSHealthShareUsageDescription y NSHealthUpdateUsageDescription en Info.plist, además de las declaraciones correspondientes en el manifiesto de privacidad.9

Referencias


  1. Código de producción en Water/Water/Services/HealthKitService.swift (192 líneas). Water del autor, una app SwiftUI de seguimiento de hidratación disponible en iOS, iPadOS, macOS, watchOS y visionOS. Usa HKQuantitySample con HKQuantityType(.dietaryWater) y la bandera HKMetadataKeyWasUserEntered

  2. Código de producción en Return/Return/HealthKitManager.swift (171 líneas). Return del autor, una app SwiftUI de temporizador de meditación disponible en iOS, iPadOS, macOS, watchOS y tvOS. Usa HKCategorySample con HKCategoryType(.mindfulSession) y HKCategoryValue.notApplicable.rawValue

  3. Apple Developer, “HealthKit framework”. Los dos tipos principales de muestras del framework son HKQuantitySample (para cantidades medibles como agua, pasos, calorías) y HKCategorySample (para eventos no cuantitativos como sesiones de mindfulness, sueño, flujo menstrual). HKWorkout cubre el ejercicio estructurado. 

  4. Apple Developer, “Authorizing access to health data”. Apple oculta intencionalmente el estado de denegación de lectura para evitar que las apps infieran qué datos existen en el perfil de Health de un usuario. El método authorizationStatus(for:) solo devuelve resultados honestos para el acceso de compartir. 

  5. Apple Developer, “Apple Health icon usage” Human Interface Guidelines. El ícono de Health de Apple no debe modificarse, recortarse, sombrearse ni recolorearse; reprodúcelo con fidelidad 1:1 en interfaces promocionales y de pre-permiso. 

  6. Código de producción en Return/Return/HealthKitPermissionSheet.swift (155 líneas). View de pre-permiso mostrada antes de activar el diálogo del sistema de HealthKit. Empareja el ícono de la app Return con el ícono de Apple Health, explica el beneficio y activa la autorización a través de un closure de callback al toque del usuario. 

  7. Código de producción en Return/ReturnWatch Watch App/WatchHealthKitManager.swift (86 líneas). La variante de watchOS de HealthKitManager. HKHealthStore independiente, sin bandera hasRequestedHealthKit, sin fontanería de hoja de permiso. 

  8. Análisis del autor: HealthKit es la capa de fuente de datos de una app de iOS, App Intents es la superficie de IA del sistema, MCP es la superficie de agentes multi-LLM, Liquid Glass es la superficie visual. Las cuatro capas se componen en un único producto enviado a través de las cinco plataformas de Apple. 

  9. Apple Developer, “Privacy manifest files”. El uso de HealthKit debe declararse en PrivacyInfo.xcprivacy más las claves NSHealthShareUsageDescription y NSHealthUpdateUsageDescription en Info.plist

  10. Apple Developer, “App Review Guideline 5.1.1”. Las apps deben respetar la configuración de privacidad del usuario, solicitar consentimiento antes de recopilar datos personales y no pueden manipular, engañar ni forzar el consentimiento. La redacción exacta sobre las rutas de salida de la pantalla previa en este artículo refleja la interpretación de Return en lugar del texto literal de la directriz. 

Artículos relacionados

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 lectura

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 lectura

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 lectura