A current app project I am working on uses HealthKit workout data as its data source. This makes life easier in that I don’t have to create my own models and manage the data, the frameworks will do this for me and all I need to do is access the users data. This is all well and good until you need to work in the simulator or Xcode previews where no HealthKit data is present.
I’ve come up with an admittedly imperfect solution. It works well enough that I thought I would share. I welcome any thoughts on improvements. This solution is for iOS 17+ projects.
My app will display your workout data for segments of time so you can compare your last 7 days against the prior 7 days, or months, or years. I display some graphs and text output of this data. I don’t record any data in the app and rely on any data the user has recorded via HealthKit. A rough screenshot looks like this:
In the past I would just cobble together some code to use HKWorkout.init(…) but with iOS 17 that method is deprecated in favor of HKWorkoutBuilder. I found the documentation on how to actually use HKWorkoutBuilder a bit lacking so after some trial and error I came up with the following solution. Your mileage may vary but for my use case this is enough to allow me to get back to work.
Before you can access the users Health data you need to request permissions for either read/write or both. The final app I am creating will not write any user data so in order to write sample data I need to request additional permission to share data. I created a helper on HKHealthStore to request this permission:
For the purposes of writing sample data to the health store we only need share access so here I pass an empty array of read values. This may be different from your actual app usage where you do need to read data. This helper is only for permission to write data. If your app already asked the user for read/write access and that access covers all the sample data you wish to write you may not need to request further permissions at all. The simulator either in the simulator app or Xcode previews will need this permission granted before any data can be written. tl;dr: if you already have permission in the simulator to write data you don’t need to call this method. If you do need permission this can help.
Keep in mind the values I’m asking to share are for my app needs. You may need to edit the list to request access to other data types.
In my case I want to create workouts that occur at various intervals such as 7 day periods. For my sample data I created a struct to allow me to specify enough information to create workouts with specific start/end dates and pass along a few stats, in this case the workout distance and energy burned. In my app it’s important the data is relative to the current date so I create data offset from Date.now. Your needs will most certainly vary but I explain this here so that the code to add workouts will make sense.
Next I have the actual code to add workouts to the health store. Notice the method takes a PreviewWorkout which we defined above.
The add(…) method uses the new HKWorkoutBuilder to create a workout and add samples. Here we create a workout with the type defined in our PreviewWorkout, add two different HKQuantitySamples and then finish the sample collection and workout in that order. Assuming you have the correct permissions, the workout will be written to the devices health store.
To use this sample data in your Xcode previews you’ll need to call these methods prior to displaying the preview. Since add(…) is async you need to wrap the call in a Task{} but you may quickly notice that your samples are growing each time you refresh the preview. We need a way to clear the samples so we don’t keep adding new versions of the same data.
self.fetchWorkouts() calls another method in a HKHealthStore extension that I created to grab all workouts matching a type and between two dates. There are plenty of examples of fetching workout data available so I’ll not share that code here. Suffice to say, it will return an array of workouts matching the criteria.
Here we just grab all the workouts for the past 5 years (adjust if needed for your samples) and remove all the existing workouts. Calling this prior to adding new samples ensures we only have one set of our sample data.
Putting it all together in one method can look something like this:
Here we get our preview data from another extension, again your data and methods may vary. We then grab the default store, remove existing data and write our samples. We return the store if it’s helpful, I’m not currently using the returned value.
So to make all this work within previews you can do something along these lines:
There are two issues with this. First this will needlessly create the samples each time the preview is refreshed. This is unnecessary unless you’ve updated the sample data and need to refresh it. The other problem is there is a race and the data will not always show on first drawing of the preview. Interacting with the preview should show the sample data as that will redraw the UI. I have not figured out how to get this first rendering of the preview to wait for the sample data (without needlessly mucking up my actual code) but as long as the data is present it will be displayed on any subsequent preview renders.
If you only need it for previews/simulator debugging you can place this extension and any sample data in the Preview Content folder within Xcode and it will only be compiled into the debug builds.
I’ve settled on this solution for my usage. Here I have a boolean value I can manually set to update the preview data. I generally leave it set to false unless I need to update the sample data. The key is once the sample data is in the simulators health store it remains there unless you wipe the simulator.
If you need sample data in the full simulator you can adapt these same methods. I’ve created an .onAppear{} block that requests share access and writes the data. I can comment this block out after the data is written and uncomment for updates. It works the same as described here for previews. For my usage, I am mostly using the previews for UI adjustments and when I need real hard data I run on my personal iPhone
I welcome any input on how this may be improved. I already understand this is a bit hacky and rigid but sometimes that’s all that’s needed to get back to work on the actual project. Obviously you will need to adjust everything I outline here for match your needs. I thought it was helpful to at least document one approach to working with HKWorkoutBuilder.
I’ve uploaded both the HKHealthStore extension and my sample data structs to Gitlab as a snippet if you wish to make use of the code. The code is provided as is and you are free to use/adapt it for your use.