--- name: sensorkit description: "Access research-grade sensor data using SensorKit for approved studies. Use when an app needs SensorKit entitlement setup, Research Sensor & Usage Data authorization, ambient light, recorded motion, device usage, keyboard metrics, visits, speech, face, wrist temperature, ECG, PPG, acoustic settings, or sleep-session data. Route ordinary motion to CoreMotion and health records/workouts to HealthKit." --- # SensorKit Choose the exact `SRSensor` and verify its individual availability. Use CoreMotion for ordinary motion/activity features and HealthKit for health records and workouts. ## Contents - [Overview and Requirements](#overview-and-requirements) - [Entitlements](#entitlements) - [Info.plist Configuration](#infoplist-configuration) - [Authorization](#authorization) - [Available Sensors](#available-sensors) - [SRSensorReader](#srsensorreader) - [Recording and Fetching Data](#recording-and-fetching-data) - [SRDevice](#srdevice) - [Common Mistakes](#common-mistakes) - [Review Checklist](#review-checklist) - [References](#references) ## Overview and Requirements SensorKit enables research apps to record and fetch sensor data across iPhone and Apple Watch. The framework requires: 1. **Apple-approved research study** -- submit a proposal at [researchandcare.org](https://www.researchandcare.org/resources/accessing-sensorkit-data/). 2. **SensorKit entitlement** -- Apple grants `com.apple.developer.sensorkit.reader.allow` only for approved studies. 3. **Manual provisioning profile** -- Xcode requires an explicit App ID with the SensorKit capability enabled. 4. **User authorization** -- the system presents a Research Sensor & Usage Data sheet that users approve per-sensor. 5. **Delayed retrieval** -- design fetch timing around the canonical [Data Holding Period](#data-holding-period). An app can access up to 7 days of prior recorded data for an active sensor. ## Entitlements Add the SensorKit reader entitlement to a `.entitlements` file. List only the sensors Apple approved for the study. Common entitlement values include: ```xml com.apple.developer.sensorkit.reader.allow ambient-light-sensor motion-accelerometer device-usage keyboard-metrics ``` Load the [Entitlement and Usage-Detail Catalog](references/sensorkit-patterns.md#entitlement-and-usage-detail-catalog) when selecting the exact entitlement string and `NSSensorKitUsageDetail` key for each approved sensor. Recheck specialized sensors against their individual `SRSensor` pages. For manual signing, set Code Signing Entitlements to the entitlements file, Code Signing Identity to `Apple Developer`, Code Signing Style to `Manual`, and Provisioning Profile to the explicit profile with SensorKit capability. ## Info.plist Configuration Three keys are required: ```xml NSSensorKitUsageDescription This study monitors activity patterns for sleep research. NSSensorKitPrivacyPolicyURL https://example.com/privacy-policy NSSensorKitUsageDetail SRSensorUsageMotion Description Measures physical activity levels during the study. Required SRSensorUsageAmbientLightSensor Description Records ambient light to assess sleep environment. ``` If `Required` is `true` and the user denies that sensor, the system warns them that the study needs it and offers a chance to reconsider. Use the exact usage-detail dictionary for each requested sensor. Load the [Entitlement and Usage-Detail Catalog](references/sensorkit-patterns.md#entitlement-and-usage-detail-catalog) when mapping sensors beyond the motion and ambient-light examples above. ## Authorization Request authorization for the sensors your study needs. The system shows the Research Sensor & Usage Data sheet on first request. ```swift import SensorKit let reader = SRSensorReader(sensor: .ambientLightSensor) // Request authorization for multiple sensors at once SRSensorReader.requestAuthorization( sensors: [.ambientLightSensor, .accelerometer, .keyboardMetrics] ) { error in if let error { print("Authorization request failed: \(error)") } } ``` Use one status handler both for the initial check and delegate changes: ```swift private func applyAuthorizationStatus( _ status: SRAuthorizationStatus, to reader: SRSensorReader ) { switch status { case .authorized: reader.startRecording() case .denied: reader.stopRecording() // Direct the user to Settings > Privacy > Research Sensor & Usage Data. case .notDetermined: break // Request authorization first. @unknown default: break } } applyAuthorizationStatus(reader.authorizationStatus, to: reader) func sensorReader(_ reader: SRSensorReader, didChange authorizationStatus: SRAuthorizationStatus) { applyAuthorizationStatus(authorizationStatus, to: reader) } ``` ## Available Sensors Load the [Sensor Catalog](references/sensorkit-patterns.md#sensor-catalog) to map each `SRSensor` to its sample type. Request only sensors approved for the study and recheck the selected sensor's availability and usage-detail key. ## SRSensorReader `SRSensorReader` is the central class for accessing sensor data. Each instance reads from a single sensor. ```swift import SensorKit // Create a reader for one sensor let lightReader = SRSensorReader(sensor: .ambientLightSensor) let keyboardReader = SRSensorReader(sensor: .keyboardMetrics) // Assign delegate to receive callbacks lightReader.delegate = self keyboardReader.delegate = self ``` The reader communicates through `SRSensorReaderDelegate`. Load the [Delegate Method Catalog](references/sensorkit-patterns.md#delegate-method-catalog) when wiring the complete authorization, recording, device-fetch, and sample-fetch lifecycle. ## Recording and Fetching Data ### Start and Stop Recording ```swift // Begin recording -- sensor stays active as long as any app has a stake reader.startRecording() // Stop recording -- framework deactivates the sensor when // no app or system process is using it reader.stopRecording() ``` ### Fetch Data Build an `SRFetchRequest` with a time range and target device, then pass it to the reader: ```swift let request = SRFetchRequest() request.device = SRDevice.current request.from = SRAbsoluteTime(CFAbsoluteTimeGetCurrent() - 86400 * 2) // 2 days ago request.to = SRAbsoluteTime.current() reader.fetch(request) ``` Receive results through the delegate: ```swift func sensorReader( _ reader: SRSensorReader, fetching request: SRFetchRequest, didFetchResult result: SRFetchResult ) -> Bool { let timestamp = result.timestamp switch reader.sensor { case .ambientLightSensor: if let sample = result.sample as? SRAmbientLightSample { let lux = sample.lux let chromaticity = sample.chromaticity let placement = sample.placement processSample(lux: lux, chromaticity: chromaticity, at: timestamp) } case .keyboardMetrics: if let sample = result.sample as? SRKeyboardMetrics { let words = sample.totalWords let speed = sample.typingSpeed processKeyboard(words: words, speed: speed, at: timestamp) } case .deviceUsageReport: if let sample = result.sample as? SRDeviceUsageReport { let wakes = sample.totalScreenWakes let unlocks = sample.totalUnlocks processUsage(wakes: wakes, unlocks: unlocks, at: timestamp) } default: break } return true // Return true to continue receiving results } func sensorReader(_ reader: SRSensorReader, didCompleteFetch request: SRFetchRequest) { print("Fetch complete for \(reader.sensor)") } func sensorReader( _ reader: SRSensorReader, fetching request: SRFetchRequest, failedWithError error: any Error ) { print("Fetch failed: \(error)") } ``` Cast `result.sample` to the sample shape for the reader's sensor. Some streams return one object per result, while recorded motion, ECG, PPG, and ambient pressure streams can return arrays of recorded samples. ### Data Holding Period SensorKit imposes a **24-hour holding period** on newly recorded data. Fetch requests whose time range overlaps this period return no results. Design data collection workflows around this delay. ## SRDevice `SRDevice` identifies the hardware source for sensor samples. Use it to distinguish data from iPhone versus Apple Watch. ```swift // Get the current device let currentDevice = SRDevice.current print("Model: \(currentDevice.model)") print("System: \(currentDevice.systemName) \(currentDevice.systemVersion)") // Fetch all available devices for a sensor reader.fetchDevices() ``` Handle fetched devices through the delegate: ```swift func sensorReader(_ reader: SRSensorReader, didFetch devices: [SRDevice]) { for device in devices { let request = SRFetchRequest() request.device = device request.from = SRAbsoluteTime(CFAbsoluteTimeGetCurrent() - 86400) request.to = SRAbsoluteTime.current() reader.fetch(request) } } func sensorReader(_ reader: SRSensorReader, fetchDevicesDidFailWithError error: any Error) { print("Failed to fetch devices: \(error)") } ``` ### SRDevice Properties | Property | Type | Description | |---|---|---| | `model` | `String` | User-defined device name | | `name` | `String` | Framework-defined device name | | `systemName` | `String` | OS name (iOS, watchOS) | | `systemVersion` | `String` | OS version | | `productType` | `String` | Hardware identifier | | `current` | `SRDevice` | Class property for the running device | ## Common Mistakes ### DON'T: Attempt to use SensorKit without the entitlement Obtain Apple's study approval, the sensor-specific entitlement values, and a matching manual provisioning profile before constructing production readers. ### DON'T: Expect immediate data access Apply the [Data Holding Period](#data-holding-period); a fetch overlapping the hold is not proof that recording failed. ### DON'T: Forget to set the delegate before fetching Assign the delegate before `startRecording()` or `fetch(_:)`; results and failures arrive through delegate callbacks. ### DON'T: Skip per-sensor Info.plist usage detail Add the exact [Info.plist Configuration](#infoplist-configuration) usage-detail entry for every requested sensor. ### DON'T: Ignore SRError codes Distinguish at least `.invalidEntitlement`, `.noAuthorization`, `.dataInaccessible`, `.fetchRequestInvalid`, `.promptDeclined`, and unknown future codes. Load the [Full Delegate Implementation](references/sensorkit-patterns.md#full-delegate-implementation) for the complete switch and callback wiring. ## Review Checklist - [ ] Apple-approved research study in place before development - [ ] `com.apple.developer.sensorkit.reader.allow` entitlement lists only needed sensors - [ ] Manual provisioning profile with explicit App ID and SensorKit capability - [ ] `NSSensorKitUsageDescription` in Info.plist with clear study purpose - [ ] `NSSensorKitPrivacyPolicyURL` in Info.plist with valid privacy policy URL - [ ] `NSSensorKitUsageDetail` entries for every requested sensor - [ ] `Required` key set appropriately for essential vs. optional sensors - [ ] Authorization requested before recording, status checked before fetching - [ ] Delegate assigned before calling `startRecording()` or `fetch(_:)` - [ ] Fetch request time ranges account for 24-hour data holding period - [ ] `SRError` codes handled in all failure delegate methods - [ ] `fetchDevices()` used to discover available devices before fetching - [ ] `stopRecording()` called when data collection is complete - [ ] `sensorReader(_:fetching:didFetchResult:)` returns `true` to continue or `false` to stop ## References - Extended patterns (delegate wiring, multi-sensor manager, sample type details): [references/sensorkit-patterns.md](references/sensorkit-patterns.md) - [SensorKit framework](https://sosumi.ai/documentation/sensorkit) - [SRSensorReader](https://sosumi.ai/documentation/sensorkit/srsensorreader) - [SRSensor](https://sosumi.ai/documentation/sensorkit/srsensor) - [SRDevice](https://sosumi.ai/documentation/sensorkit/srdevice) - [SRFetchRequest](https://sosumi.ai/documentation/sensorkit/srfetchrequest) - [Configuring your project for sensor reading](https://sosumi.ai/documentation/sensorkit/configuring-your-project-for-sensor-reading) - [com.apple.developer.sensorkit.reader.allow](https://sosumi.ai/documentation/bundleresources/entitlements/com.apple.developer.sensorkit.reader.allow)