← Wszystkie wpisy

HealthKit + SwiftUI w iOS 26: autoryzacja, typy próbek i wzorce wieloplatformowe na podstawie dwóch wdrożonych aplikacji

HealthKit należy do trudniejszych frameworków Apple do poprawnego wdrożenia w aplikacji SwiftUI. Procedura autoryzacji zawiera pułapkę przejściowych awarii, która może na stałe zablokować dostęp użytkownikom. Typy próbek dzielą się niezręcznie pomiędzy danymi ilościowymi (HKQuantitySample dla spożycia wody, kroków, kalorii) a danymi kategorycznymi (HKCategorySample dla sesji uważności, snu, cyklu menstruacyjnego). Powierzchnia async API wymaga opakowywania wywołań HealthKit opartych na callbackach w withCheckedThrowingContinuation. A na watchOS wzorce zmieniają się ponownie.

Wdrożyłem HealthKit w dwóch produkcyjnych aplikacjach: Water (śledzenie spożycia wody, ~192-liniowy HealthKitService)1 oraz Return (rejestrowanie sesji uważności, ~171-liniowy HealthKitManager plus 155-liniowy HealthKitPermissionSheet).2 Razem obejmują obie główne formy próbek HealthKit, oba kierunki (odczyt + zapis vs. tylko zapis) oraz wdrożenie zarówno jednoplatformowe, jak i wieloplatformowe.

Niniejszy esej omawia wzorce, które przetrwały produkcję: UX wstępnej zgody, modyfikator SwiftUI .healthDataAccessRequest vs. starszy API requestAuthorization, opakowanie async dla zapytań o próbki oraz różnice specyficzne dla watchOS.

TL;DR

  • Raportowanie statusu autoryzacji jest asymetryczne. API Apple niezawodnie informuje o „autoryzacji udostępniania”, lecz nie informuje o „odmowie odczytu” ze względów prywatności. Artykuł omawia, jak wykryć stan „użytkownik został zapytany, ale nie udzielił zgody” bez wnioskowania o stanie odczytu.
  • Dane ilościowe wykorzystują HKQuantitySample z HKQuantityType i HKUnit (Water używa .literUnit(with: .milli) dla spożycia wody). Dane kategoryczne wykorzystują HKCategorySample z HKCategoryType (Return używa .mindfulSession).
  • Arkusz wstępnej zgody to najczęściej pomijany wzorzec. Systemowe okno dialogowe uprawnień Apple jest sterylne; niestandardowy View pokazany wcześniej wyjaśnia wartość i drastycznie zwiększa wskaźnik udzielania zgód.
  • HealthKit na watchOS potrzebuje osobnej instancji HKHealthStore, ma surowsze reguły ponownego pytania o autoryzację i nie może wyświetlić arkusza SwiftUI dla UX wstępnej zgody.
  • Wzorzec recordAuthorizationAttempt() w aplikacji Return zapobiega traktowaniu przejściowych awarii prezentacji jako trwałej odmowy.

Dwie formy próbek

HealthKit dzieli swój model próbek wzdłuż osi, która wpływa na każdą linię pisanego kodu:3

Forma próbki Typowe zastosowanie API
HKQuantitySample woda, kroki, kalorie, masa ciała, tętno HKQuantityType + HKUnit + HKQuantity
HKCategorySample sesje uważności, sen, cykl menstruacyjny, aktywność seksualna HKCategoryType + HKCategoryValue
HKWorkout ustrukturyzowane ćwiczenia (bieg, pływanie) HKWorkoutBuilder, HKWorkoutSession

Woda jest wielkością ilościową. Rzeczywisty kod produkcyjny z 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
    }
}

Trzy szczegóły z produkcji:

  1. .literUnit(with: .milli) to kanoniczna jednostka dla wody w mililitrach. HealthKit zaakceptuje dowolną jednostkę (uncje płynu USA, litry), ale konstruktor ma znaczenie, ponieważ Apple normalizuje wszystko do litrów wewnętrznie. Wybór .milli umożliwia przechowywanie wartości całkowitych (240, 500) zamiast ułamkowych litrów (0,240, 0,500).
  2. HKMetadataKeyWasUserEntered: true oznacza próbkę jako wprowadzoną ręcznie, a nie zmierzoną. Aplikacja Health pokazuje na takich próbkach mały wskaźnik „wprowadzone ręcznie”; użytkownik ufa danym wprowadzonym ręcznie inaczej niż danym zmierzonym wagą.
  3. start i end to ten sam Date dla próbek chwilowych. Ilości akumulują się w oknie [start, end], lecz dla „właśnie wypiłem 240 ml” okno zwija się do punktu.

Sesja uważności jest kategoryczna. Rzeczywisty kod produkcyjny z HealthKitManager.swift aplikacji 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) ...
    }
}

Dwa szczegóły:

  1. HKCategoryValue.notApplicable.rawValue to wartość-wartownik niosąca obciążenie. Sesje uważności nie mają znaczącej „wartości” (są znacznikami czasu trwania), więc HealthKit wymaga wartości-wartownika kategorii (Int(0)), aby pole spełniło wymogi systemu typów. Inne próbki kategorii mają bogatsze wartości: sen używa HKCategoryValueSleepAnalysis.asleep itd.
  2. Okno start/end jest realne dla próbek kategorii. 10-minutowa sesja uważności ma start i end oddalone o 10 minut, a dzienne podsumowanie minut uważności w HealthKit sumuje te czasy trwania.

Procedura autoryzacji (i jej pułapka)

Autoryzacja HealthKit jest asymetryczna celowo. authorizationStatus(for:) raportuje status udostępniania/zapisu zgodnie z prawdą (.sharingAuthorized, .sharingDenied, .notDetermined), lecz udzielenie lub odmowa odczytu nie są bezpośrednio obserwowalne poprzez ten API. Apple celowo ukrywa stan odczytu, aby uniemożliwić aplikacjom wnioskowanie o tym, jakie dane istnieją w profilu Health użytkownika.4 Decyzję o odczycie poznaje się pośrednio: zapytania zwracają dane, jeśli użytkownik udzielił prawa odczytu, a zapytania zwracają puste wyniki, jeśli odmówił. Nie istnieje sygnał „odczyt został odrzucony”, według którego kod mógłby się rozgałęziać.

Obie aplikacje radzą sobie z tym inaczej.

Water odczytuje swoje własne próbki. Ponieważ Water zarówno zapisuje, jak i odczytuje wpisy o wodzie (aby wypełnić „dzisiejszą historię”), jego procedura autoryzacji musi obsłużyć przypadek niewidocznej odmowy odczytu: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
}

Należy zwrócić uwagę, czego checkAuthorizationStatus NIE robi: nie pyta o status autoryzacji odczytu. Water sprawdza tylko status udostępniania. Jeżeli użytkownik udzieli zgody na udostępnianie, ale odmówi odczytu, interfejs Water wyświetli „brak wpisów”, ponieważ odczyt zwraca pustkę (a nie z powodu jawnego błędu). Water ufa decyzji o udostępnianiu i pozwala, by brak danych mówił sam za siebie. Użytkownik może to naprawić w ustawieniach, jeśli mu na tym zależy.

Return tylko zapisuje. Return rejestruje sesje uważności, ale nigdy ich nie odczytuje; lista sesji, którą wyświetla, pochodzi z własnego NSUbiquitousKeyValueStore, a nie z HealthKit. Zatem żądanie autoryzacji w Return jest tylko do zapisu: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
    }
}

Pusty zbiór read: [] jest celowy. Proszenie o prawa odczytu, których nie potrzebujemy, rozszerza zakres uprawnień, który trzeba uzasadnić w App Review, oraz dezorientuje użytkowników, którzy widzą „Return chce odczytywać Twoje sesje uważności”, podczas gdy aplikacja oczywiście tego nie potrzebuje.

Pułapka. Obie aplikacje zbiegły się w tym samym wzorcu obronnym: oznaczać próbę autoryzacji jako „zakończoną” tylko wtedy, gdy status faktycznie wyszedł poza .notDetermined. Naiwny kod brzmi:

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

Poprawny wzorzec z aplikacji Return:2

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

Dlaczego ma to znaczenie: modyfikator SwiftUI .healthDataAccessRequest oraz leżący pod spodem API requestAuthorization mogą nie zaprezentować systemowego okna dialogowego w określonych warunkach (konflikty arkuszy, przerwania cyklu życia widoku, przejściowy stan systemu operacyjnego). Jeżeli oznaczy się próbę jako „zakończoną” przed sprawdzeniem rzeczywistego statusu, użytkownik zostaje uwięziony w stanie, w którym aplikacja sądzi, że odmówił, lecz żadne systemowe okno dialogowe nigdy się nie pojawiło. Nie ma drogi powrotu, chyba że użytkownik wejdzie do Ustawień → Prywatność → Zdrowie i udzieli zgody ręcznie, co nie przyjdzie mu do głowy, ponieważ nigdy nie zobaczył monitu. recordAuthorizationAttempt() w aplikacji Return istnieje właśnie dla tego przypadku.

Arkusz wstępnej zgody

Okno dialogowe uprawnień HealthKit od Apple jest poprawne, lecz „niesprzedane”. Pokazuje listę typów, które aplikacja chce udostępniać lub odczytywać, z przełącznikami. Brak kontekstu dlaczego aplikacja chce tych danych, brak wyjaśnienia korzyści, brak marki aplikacji. Użytkownicy stukają „Nie zezwalaj”, ponieważ monit jest pozbawiony kontekstu.

Return dostarcza HealthKitPermissionSheet, który pojawia się przed systemowym oknem dialogowym. Arkusz pokazuje ikonę aplikacji Return obok ikony Apple Health (zgodnie z HIG: ikona Apple Health nie może być przycięta ani ocieniona),5 formułuje korzyść („Track Your Practice” / „Save your meditation sessions as Mindful Minutes in Apple Health”), wymienia trzy wiersze korzyści („See your practice in Apple Health”, „Syncs across all your devices”, „Works with other wellness apps”) i kończy się jednym przyciskiem Continue skierowanym do przodu, który uruchamia systemowe żądanie. Rzeczywista struktura z 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)
    }
}

interactiveDismissDisabled(true) w połączeniu z brakiem jakiegokolwiek przycisku anulowania to celowy wybór projektowy. Wytyczna App Review 5.1.1 firmy Apple wymaga, by aplikacje respektowały ustawienia prywatności użytkownika i zabrania manipulowania, oszukiwania lub wymuszania zgody.10 Komentarz w kodzie produkcyjnym powyżej przycisku Continue brzmi:

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.

To ujęcie jest surowsze niż dosłowny tekst wytycznej (który mówi o respektowaniu zgody i niewymuszaniu jej, lecz nie przepisuje UX ekranu wstępnego w tych słowach). Jest to interpretacja Return, skodyfikowana w kodzie źródłowym. Intencja: użytkownik, który odmawia, powinien to uczynić na ekranie Apple, a nie na ekranie Return. Rezultatem jest arkusz z jedną akcją skierowaną do przodu i bez „wyjścia awaryjnego”. Tekst i wiersze korzyści muszą zasłużyć na stuknięcie; nie ma rozgałęzienia „pomyślę o tym”.

Przebieg:

  1. Użytkownik stuka „Connect Health” w ustawieniach Return.
  2. Pojawia się arkusz wstępnej zgody. Użytkownik czyta wyjaśnienie.
  3. Użytkownik stuka Continue. Arkusz pozostaje zamontowany podczas wykonywania requestAuthorization i pojawia się systemowe okno dialogowe.
  4. Użytkownik akceptuje (lub odmawia) w systemowym oknie dialogowym. Return wywołuje recordAuthorizationAttempt(), aby przechwycić wynik, a arkusz znika.

Po co zawracać sobie głowę. Apple nie opublikowało oficjalnych liczb dotyczących wzrostu wskaźnika zgód dzięki arkuszom wstępnej zgody, lecz każdy programista iOS, z którym rozmawiałem, mający zarówno test A/B, jak i cierpliwość, by go przeprowadzić, raportował ten sam kierunek: arkusze wstępnej zgody drastycznie zwiększają wskaźnik udzielania autoryzacji udostępniania. Wzorzec ten jest obecnie na tyle powszechny, że własne szablony Apple (Photos, Camera, Location) coraz częściej zawierają własny UX wstępnej zgody.

Wariant watchOS

HealthKit na watchOS dzieli tę samą powierzchnię API co HealthKit na iOS (HKHealthStore, typy próbek, autoryzacja), lecz z trzema strukturalnymi różnicami:

  1. Nowy HKHealthStore na każdą aplikację Watch. Aplikacja na zegarek i sparowana aplikacja na iPhone’a mają każda własną instancję HKHealthStore. Obie mogą zapisywać do bazy HealthKit użytkownika, obie mogą odczytywać własne próbki. Magazyn nie jest współdzielony pomiędzy parą.
  2. Brak arkuszy SwiftUI dla wstępnej zgody. Hierarchie widoków watchOS nie obsługują arkuszy w taki sposób jak iOS. UX wstępnej zgody musi być pełnoekranowy.
  3. Surowsze reguły ponownego pytania. Wywołanie requestAuthorization na watchOS jest bardziej zachowawcze co do ponownego prezentowania systemowego okna dialogowego, jeżeli użytkownik wcześniej odmówił; może być konieczne skierowanie użytkownika do aplikacji Watch na iPhonie, aby zmienić ustawienie, ponieważ sam zegarek nie ma interfejsu Ustawienia → Prywatność → Zdrowie.

Rzeczywisty kod produkcyjny z 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 ...
}

Podział jest celowy: requestAuthorization raportuje, czy wywołanie systemowe powiodło się, a isAuthorizedToWrite raportuje wynik, który użytkownik wybrał. Rozdzielenie tego na zegarku ułatwia zrozumienie kodu; na iOS ekwiwalentny podział pojawia się jako para recordAuthorizationAttempt() plus isAuthorizedToWrite() w HealthKitManager.

Klasa Watch jest mniej więcej o połowę mniejsza od tej dla iOS, ponieważ nie potrzebuje:

  • Flagi UserDefaults hasRequestedHealthKit (UX zegarka ma mniej ścieżek drugiej próby do obsłużenia).
  • Instalacji HealthKitPermissionSheet (brak interfejsu arkuszy na watchOS).
  • Metody recordAuthorizationAttempt() (węższy przepływ zegarka ma mniej skrajnych przypadków przejściowych awarii).

Kompromisem jest to, że aplikacja watchOS czasami trafia w stan odmowy bez możliwości naprawy dla użytkowników, którzy odmówili w systemowym oknie dialogowym i nie zdają sobie sprawy, że mogą to naprawić w aplikacji Watch na iPhonie. Return wyświetla w tym przypadku małą instrukcję w aplikacji („Otwórz aplikację Watch na iPhonie → Mój zegarek → Prywatność → Zdrowie”) zamiast próbować ponownego monitu.

Co zbudowałbym inaczej

Trzy lekcje z wdrożenia HealthKit w dwóch produkcyjnych aplikacjach.

Oba APIy działają; wybór zależy od kontekstu prezentacji. Modyfikator SwiftUI .healthDataAccessRequest opakowuje starszy API w bardziej deklaratywną formę i poprawnie obsługuje kontekst prezentacji na iPadOS. Return używa modyfikatora, udostępniając swoje typy udostępniania poprzez publiczną właściwość mindfulShareTypes w HealthKitManager, dzięki czemu nadrzędny widok SwiftUI może to podpiąć. Water używa starszego healthStore.requestAuthorization bezpośrednio, ponieważ procedura autoryzacji w Water uruchamiana jest z kontekstu poza widokiem (usługa @Observable). Ten podział to użyteczny wzorzec: preferować modyfikator, gdy żądanie pochodzi ze zdarzenia cyklu życia SwiftUI (stuknięcie przycisku wewnątrz arkusza), powracać do starszego API, gdy żądanie pochodzi z usługi.

Arkusze wstępnej zgody warte są kosztu inżynieryjnego na iOS, ale nie na watchOS. Arkusz wstępnej zgody dodaje może cztery godziny pracy (widok arkusza, tekst, motywy, integracja). Wzrost wskaźnika udzielania zgód na iOS jest na tyle duży, że te cztery godziny są oczywistą inwestycją. Na watchOS równoważna pełnoekranowa wstępna zgoda jest bardziej inwazyjna (przejmuje cały ekran zegarka zamiast być arkuszem), użytkownik mniej chętnie czyta długi tekst na małym ekranie, a UX zegarka ma mniej punktów wejścia, w których użytkownik prosi o funkcję wymagającą HealthKit. Wdrożyłem Return bez tego elementu na watchOS i nie żałuję.

Należy śledzić hasRequestedHealthKit osobno od bieżącego statusu autoryzacji. API HealthKit informuje o aktualnym statusie autoryzacji, lecz nie informuje, czy kiedykolwiek pytano. To rozróżnienie ma znaczenie, ponieważ właściwe zachowanie przy drugim stuknięciu od tego zależy: pierwsze stuknięcie powinno wywołać requestAuthorization; drugie stuknięcie, jeśli użytkownik wcześniej odmówił, powinno pokazać alert „Ustawienia → Prywatność → Zdrowie” zamiast ponownie wywoływać API (który po cichu nic nie robi w stanie odmowy). Flaga UserDefaults hasRequestedHealthKit jest tym, co czyni drugie stuknięcie użytecznym.

Kiedy nie używać HealthKit

Odmowa jest częścią projektu.

Nie zapisuj do HealthKit tylko dlatego, że można. Aplikacja-licznik czasu skupienia nie musi zapisywać treningu. Aplikacja do robienia notatek nie musi rejestrować uważności. Dodawanie HealthKit, ponieważ jest to „darmowa integracja”, rozszerza ślad prywatności, tarcia autoryzacyjne i zakres kwestionariusza App Review bez dawania użytkownikowi materialnej wartości.

Nie odczytuj z HealthKit, jeśli można tego uniknąć. Dostęp do odczytu jest trudniejszy do uzasadnienia w App Review i trudniejszy do wyjaśnienia użytkownikom. Wiele aplikacji, które odczytują HealthKit, mogłoby tylko zapisywać i pozwolić użytkownikom widzieć swoje dane w Apple Health; ścieżka odczytu podwaja powierzchnię ataku przy znikomym zysku UX.

Nie używaj HealthKit na Macu dla danych tylko międzyurządzeniowych. macOS obsługuje HealthKit od macOS 13, lecz większość danych Health pochodzi z iPhone’a lub Apple Watch. Jeżeli aplikacja Mac potrzebuje tych samych danych, należy zapisywać do HealthKit na iPhonie i pozwolić synchronizacji międzyurządzeniowej Apple wyświetlić je na Macu. Bezpośrednie zapisy HealthKit z Maca są dopuszczalne, lecz w praktyce rzadkie.

Nie wdrażaj bez przetestowania ścieżki odmowa-następnie-cofnięcie. Użytkownicy udzielają zgody, a następnie cofają ją w Ustawieniach → Prywatność → Zdrowie. Aplikacja musi obsłużyć cofnięcie z gracją, zazwyczaj przez wyświetlenie instrukcji deep-link do Ustawień przy następnej próbie skorzystania z funkcji. Zarówno Water, jak i Return dostarczają tę ścieżkę; żadna nie zrobiła tego dobrze za pierwszym razem.

Co to oznacza dla aplikacji SwiftUI wdrażających HealthKit na iOS 26+

Trzy wnioski.

  1. Należy zdecydować o tylko-zapisie vs. odczyt+zapis przed projektowaniem interfejsu. Tylko-zapis to mniejsza powierzchnia i szybszy App Review. Odczyt+zapis jest bardziej elastyczny, lecz dodaje asymetrię niewidocznej odmowy odczytu.
  2. Należy dostarczyć arkusz wstępnej zgody na iOS. Wzrost wskaźnika udzielania zgód jest realny. Ikony Apple należy używać poprawnie (bez przycinania, bez cienia), formułować korzyść, a następnie wywoływać systemowy API.
  3. HealthKit na watchOS należy traktować jako mniejszy, prostszy wariant wzorca iOS. Mniej ceremonii, brak arkuszy, autoryzacja jednoznaczna. Użytkowników należy kierować do aplikacji Watch na iPhonie w celu ponownego udzielenia zgody.

Niniejszy artykuł warto połączyć z moimi wcześniejszymi tekstami dla tej samej rodziny aplikacji: typowanymi App Intents dla Apple Intelligence; serwerami MCP dla agentów cross-LLM; wzorcami Liquid Glass dla warstwy wizualnej; wieloplatformowym wdrażaniem dla zasięgu międzyurządzeniowego. HealthKit jest warstwą źródła danych, leżącą pod warstwą wizualną i powierzchniami integracji.8

FAQ

Czy mogę współdzielić autoryzację pomiędzy aplikacją iOS a jej rozszerzeniem watchOS?

Nie. HKHealthStore na iOS oraz HKHealthStore na watchOS są niezależne. Użytkownik udziela autoryzacji osobno na każdej platformie poprzez osobne systemowe okna dialogowe. Kod po każdej stronie sprawdza własny status autoryzacji; nie można odczytać statusu iOS z zegarka ani odwrotnie.

Co dzieje się z moimi próbkami, jeśli użytkownik cofnie dostęp do zapisu?

Istniejące próbki pozostają w HealthKit. Użytkownik może je usunąć ręcznie z aplikacji Health, jeśli zechce, lecz cofnięcie dostępu aplikacji zatrzymuje tylko nowe zapisy. Aplikacja nie może już zapisywać ani modyfikować próbek, lecz historyczne dane użytkownika są zachowane.

Czy modyfikator SwiftUI .healthDataAccessRequest jest bezpieczny do użycia w produkcji?

Działa w iOS 17.4+ i był doskonalony w kolejnych wydaniach. Return używa modyfikatora (udostępnia mindfulShareTypes w HealthKitManager do wykorzystania przez SwiftUI). Water używa starszego healthStore.requestAuthorization bezpośrednio, ponieważ żądanie Water pochodzi z usługi @Observable poza kontekstem widoku. Wybierać należy w zależności od miejsca, z którego inicjowane jest żądanie: zdarzenie cyklu życia SwiftUI → modyfikator; usługa lub kontekst poza widokiem → starszy API.

Dlaczego status autoryzacji HealthKit pokazuje .sharingAuthorized, mimo że nigdy nie pytałem o dostęp do udostępniania?

Nie pokazuje. Status jest per-HKObjectType. Jeżeli sprawdzi się authorizationStatus(for: heartRateType), a nigdy nie zażądano tętna, otrzyma się .notDetermined. Status przechodzi do .sharingAuthorized dopiero po pomyślnej autoryzacji dla tego konkretnego typu.

Czy potrzebuję manifestu prywatności dla HealthKit?

Tak. Aplikacje, które dotykają HealthKit, muszą zadeklarować to użycie w PrivacyInfo.xcprivacy (manifest prywatności) oraz w kwestionariuszu prywatności App Store Connect. Odpowiednie wpisy to NSHealthShareUsageDescription i NSHealthUpdateUsageDescription w Info.plist, plus odpowiadające im deklaracje w manifeście prywatności.9

Bibliografia


  1. Kod produkcyjny w Water/Water/Services/HealthKitService.swift (192 linie). Autorska aplikacja Water, aplikacja SwiftUI do śledzenia nawodnienia dostępna na iOS, iPadOS, macOS, watchOS i visionOS. Wykorzystuje HKQuantitySample z HKQuantityType(.dietaryWater) oraz flagą HKMetadataKeyWasUserEntered

  2. Kod produkcyjny w Return/Return/HealthKitManager.swift (171 linii). Autorska aplikacja Return, aplikacja SwiftUI z licznikiem czasu medytacji dostępna na iOS, iPadOS, macOS, watchOS i tvOS. Wykorzystuje HKCategorySample z HKCategoryType(.mindfulSession) oraz HKCategoryValue.notApplicable.rawValue

  3. Apple Developer, „HealthKit framework”. Dwa główne typy próbek frameworka to HKQuantitySample (dla mierzalnych wielkości takich jak woda, kroki, kalorie) oraz HKCategorySample (dla zdarzeń nieliczbowych takich jak sesje uważności, sen, cykl menstruacyjny). HKWorkout obejmuje ustrukturyzowane ćwiczenia. 

  4. Apple Developer, „Authorizing access to health data”. Apple celowo ukrywa stan odmowy odczytu, aby uniemożliwić aplikacjom wnioskowanie o tym, jakie dane istnieją w profilu Health użytkownika. Metoda authorizationStatus(for:) zwraca uczciwe wyniki tylko dla dostępu do udostępniania. 

  5. Apple Developer, „Apple Health icon usage” Human Interface Guidelines. Ikona Health firmy Apple nie może być modyfikowana, przycinana, ocieniana ani przebarwiana; należy ją reprodukować w wierności 1:1 w promocyjnych i wstępno-zgodowych interfejsach. 

  6. Kod produkcyjny w Return/Return/HealthKitPermissionSheet.swift (155 linii). View wstępnej zgody pokazywany przed wywołaniem systemowego okna dialogowego HealthKit. Łączy ikonę aplikacji Return z ikoną Apple Health, wyjaśnia korzyść i wywołuje autoryzację poprzez domknięcie callback po stuknięciu użytkownika. 

  7. Kod produkcyjny w Return/ReturnWatch Watch App/WatchHealthKitManager.swift (86 linii). Wariant HealthKitManager dla watchOS. Niezależny HKHealthStore, brak flagi hasRequestedHealthKit, brak instalacji arkusza zgody. 

  8. Analiza autora: HealthKit jest warstwą źródła danych aplikacji iOS, App Intents są powierzchnią systemowego AI, MCP jest powierzchnią agenta cross-LLM, Liquid Glass jest powierzchnią wizualną. Te cztery warstwy składają się w jeden wdrożony produkt na wszystkich pięciu platformach Apple. 

  9. Apple Developer, „Privacy manifest files”. Użycie HealthKit musi być zadeklarowane w PrivacyInfo.xcprivacy plus klucze NSHealthShareUsageDescription i NSHealthUpdateUsageDescription w Info.plist

  10. Apple Developer, „App Review Guideline 5.1.1”. Aplikacje muszą respektować ustawienia prywatności użytkownika, prosić o zgodę przed zbieraniem danych osobowych i nie mogą manipulować, oszukiwać ani wymuszać zgody. Dokładne sformułowanie dotyczące ścieżek wyjścia z ekranu wstępnego w niniejszym artykule odzwierciedla interpretację Return, a nie dosłowny tekst wytycznej. 

Powiązane artykuły

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 czytania

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 czytania

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 czytania