Adding Sexual Activity to Apple Health via HealthKit
Tags: swift, healthkit, sexual, activity
Published: 2022-08-26 12:40:51

---
title: Adding Sexual Activity to Apple Health via HealthKit
published: true
description: Most tutorials only cover common HealthKit use cases like steps or heartrate. This post writes about sexual activity.
tags: swift, HealthKit, sexual, activity
# cover_image: https://direct_url_to_image.jpg
# Use a ratio of 100:42 for best results.
---

There are many examples on how to add heart rate or step counts to Apple Health via HealthKit. I made an [app](https://apps.apple.com/us/app/aphrodite/id1449414335) that lets you track your sexual activity. And now I'd like to add HealthKit support.

## Prerequisites

After creating the app in Xcode, add the HealthKit capability and add the string to your Info.plist. 

For clarity and better practice we'll create a class that'll handle the connection to HealthKit. You could add this to a view too. It would be ugly.

## Sample vs. Object

A sample is what is written to `HKHealthStore` and an object is what is read from `HKHealthStore`.

## `HealthConnector`

The custom `HealthConnector` class facilitates interaction with `HKHealthStore`. It currently has two three functions:

- `requestAuthorization`
- `saveSample`
- `deleteObject`

### Request authorization

Before any interaction with `HKHealthStore` we must ask for permission. It's not enough to ask once. The user could revoke permission when we're not looking.

You have to specifically ask for the Sample Types you'd like to read from and write to. Since my app only writes samples but won't retrieve anything, `requestAuthorization`'s `read`-parameter is `nil`.

```swift
/// This function requests permission to read and/or write to a specific
/// `HKObjectType`.
/// - Parameter completion: Completion handler must be able to handle boolean return.
func requestAuthorization(completion: @escaping (Bool) -> Void) {
    // Check if `HKHealthStore` is available to the user of the app.
    guard HKHealthStore.isHealthDataAvailable() else {
        completion(false)
        return
    }
    
    // Check if category type `.sexualActivity` is available to the user.
    guard let sexualActivityType = HKObjectType.categoryType(forIdentifier: .sexualActivity) else {
        completion(false)
        return
    }
    
    // Create a set containing the category types we'd like to access.
    let writeTypes: Set = [
        sexualActivityType
    ]
    
    // Finally request authorization.
    HKHealthStore().requestAuthorization(toShare: writeTypes, read: nil) {
        (success, _) in
        completion(success)
    }
}
```

### Save a sample

```swift
/// This function saves a new sample to `HKHealthStore`. It adds all neccessary meta date.
/// - Parameters:
///   - protection: Was protection used?
///   - date: When was the date of sexual acitivty?
///   - identifier: A UUID that allows to delete the sample later.
///   - completion: Completion handler must be able to handle boolean return.
func saveSample(with protection: Bool, date: Date, identifier: UUID, completion: @escaping (Bool) -> Void) {
    // Check if category type `.sexualActivity` is available to the user.
    guard let sexualActivityType = HKObjectType.categoryType(forIdentifier: .sexualActivity) else {
        completion(false)
        return
    }
    
    /**
     Create the sample.
     `value` must be 0. Otherwise the app will crash.
     `metadata` must contain all three keys. `HKMetadataKeySyncIdentifier` requires `HKMetadataKeySyncVersion`. Version number is 
     */
    let sample = HKCategorySample(
        type: sexualActivityType,
        value: 0,
        start: date,
        end: date,
        metadata: [
            HKMetadataKeySexualActivityProtectionUsed: protectionUsed,
            HKMetadataKeySyncIdentifier: identifier.uuidString,
            HKMetadataKeySyncVersion: 1
        ]
    )
    
    HKHealthStore().save(sample) { (success, error) in
        completion(success)
    }
}
```

### Delete an object

```swift
/// Deletes the sample with the specified identifier.
/// - Parameters:
///   - identifier: The UUID used to save the sample.
///   - completion: Completion handler must be able to handle boolean return.
func deleteObject(identifier: UUID, completion: @escaping (Bool) -> Void) {
    // Narrow down the possible results.
    let predicate = HKQuery.predicateForObjects(
        withMetadataKey: HKMetadataKeySyncIdentifier,
        allowedValues: [identifier.uuidString]
    )
    
    // Delete all the objects that match the predicate, which should only be one.
    HKHealthStore().deleteObjects(of: HKCategoryType(.sexualActivity), predicate: predicate) { success, _, error in
        completion(success)
    }
}
```

### Modify a sample

You'll have to delete the old object and save a new sample.