The HKActivitySummaryQuery is useful for apps that are interested in what the user has done each day, in regards to exercise, stand hours, active calories, etc… The query can be used to query a single day, or a range of days.
The easiest way to think about this query is to look at the activity ring, or a collection of activity rings as pictured here. Apple Watch owners will be very aware of what each ring represents. This class allows you to fetch the data that builds up this ring, but also pass it to the HKActivityRingView to represent those numbers in a familiar format to Apple Watch users. For those wanting to use the Apple Watch version of the ring, Apple also has made available WKInterfaceActivityRing.
This tutorial will focus on the query and fetching the data. We might touch upon the activity ring view in a future tutorial, but for now we will focus on executing the query to fetch the available data.
Getting Started
Lets work with our previous project to demonstrate this query. You can download it from here. This starter project contains all code from previous tutorials we have done about queries.
Building the HKActivitySummaryQuery
init(predicate: NSPredicate?, resultsHandler handler: @escaping (HKActivitySummaryQuery, [HKActivitySummary]?, Error?) -> Void)
This particular query is very easy to implement. It has just 1 parameter that is needed. This is a predicate which can be used to set the date range. Alternatively, you might just leave it nil for it to fetch all activity data held.
The initialiser contains a results handler as well which provides the query and an array of HKActivitySummary objects. An error is also provided when needed.
Before you begin, please note that this particular query requires that the user is running version 9.3 or greater of iOS. You have two options. The first is to stop any device not running iOS 9.3 or greater, or use the if #available syntax, and if they are not on iOS 9.3 or greater you can graciously fail and let the user know that this feature is not available.
For this tutorial we will use the if #available syntax.
func testActivitySummaryQuery() { if #available(iOS 9.3, *) { let query = HKActivitySummaryQuery.init(predicate: nil) { (query, summaries, error) in print(summaries ?? "Nothing Returned") } healthStore.execute(query) } else { // Fallback on earlier versions } }
Line 2 we check if the device is running 9.3 or greater. If not, we skip to line 7 so that we can act as needed. If we are running 9.3 or greater then we go to line 3 to create the query.
Line 3. Here we create a very simple query. Unlike previous queries where we had to specify the quantity type to query, this one we simply provide a predicate. We pass in nil here because we are interested in all data. We also have a results handler which provides the query, the array (optional) of HKActivitySummary, and an optional error.
Line 6 is where we execute the query. This is kept within if #available because we do not want to run the query if the device is not running a new enough version of iOS.
If you go ahead and run the app now you will likely get a Nothing Returned message printed in the console. The reason for this is because HKActivitySummary needs to access data stored in the health store that we haven’t requested the users permission to retrieve yet. Lets go and fix that now by adding in another permission request for activitySummaryType().
var readDataTypes : Set<HKObjectType> = [] if #available(iOS 9.3, *) { readDataTypes = [HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.stepCount)!, HKObjectType.characteristicType(forIdentifier: HKCharacteristicTypeIdentifier.biologicalSex)!, HKObjectType.characteristicType(forIdentifier: HKCharacteristicTypeIdentifier.dateOfBirth)!, HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.bodyMass)!, HKObjectType.activitySummaryType()] } else { // Fallback on earlier versions readDataTypes = [HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.stepCount)!, HKObjectType.characteristicType(forIdentifier: HKCharacteristicTypeIdentifier.biologicalSex)!, HKObjectType.characteristicType(forIdentifier: HKCharacteristicTypeIdentifier.dateOfBirth)!, HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.bodyMass)!] }
To keep inline with working with older versions of iOS, I opted to use the if #available for setting the readDataTypes.
Line 1 declares an empty set containing HKObjectTypes.
Line 2 checks if we are on iOS version 9.3 or newer. If we are, we use the same permission requests as before (although you can delete those if you want, but code from previous tutorials will break). I added in line 7 which is a request for HKObjectType.activitySummaryType().
If the version of iOS is too low, then we go to the fallback and our readDataTypes does not include the activitySummaryType() request.
When you run the app now the permission sheet will be made visible and you can accept or decline permission for the user to access activity ring data.
Making Sense of the HKActivitySummary
At the moment we have literally just printed the HKActivitySummary array to the console. By doing this, the information isn’t very readable. Lets look at how we can make this more friendly.
func testActivitySummaryQuery() { if #available(iOS 9.3, *) { let query = HKActivitySummaryQuery.init(predicate: nil) { (query, summaries, error) in let calendar = Calendar.current for summary in summaries! { let dateComponants = summary.dateComponents(for: calendar) let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd" let date = dateComponants.date print("Date: \(dateFormatter.string(from: date!)), Active Energy Burned: \(summary.activeEnergyBurned), Active Energy Burned Goal: \(summary.activeEnergyBurnedGoal)") print("Date: \(dateFormatter.string(from: date!)), Exercise Time: \(summary.appleExerciseTime), Exercise Goal: \(summary.appleExerciseTimeGoal)") print("Date: \(dateFormatter.string(from: date!)), Stand Hours: \(summary.appleStandHours), Stand Hours Goal: \(summary.appleStandHoursGoal)") print("----------------") } } healthStore.execute(query) } else { // Fallback on earlier versions } }
We can log this information to the console in a more readable way.
Line 4 we get the current calendar from the users device.
Line 5 we create a for loop and loop around the provided summaries, calling each item retrieved “summary”.
Line 6 we get the date components from the HKActivitySummary.
Lines 8 and 9 we create a date formatter just to make the date a little more friendly to read.
Line 11 we convert the date property of dateComponents which is a Date().
Lines 13-15 are used to print to the console. Here I separated the lines because there was too much information to log to the console. I started each line with the date of the current HKActivitySummary, and then separated each data type to it’s own line so that it contains the result followed by the goal.
On line 16 I just provided a simple separator to make it more readable.
When you run the app now, you will see all of your activity data logged to the screen. It is at this point we might consider using the HKActivityRingView to display this information visually.
HKActivitySummaryQuery Long-Running Query
The HKActivitySummaryQuery also contains a long-running query which can be used to update you each time new data is added to the health store. You can think of this as being a good way to keep the user updated of todays status. We will look at this query in our next tutorial, as well as the HKActivityRingView.
You can download the full project here.
Leave a Reply
You must be logged in to post a comment.