5

I want to retrieve the value that indicates whether or not the user has stood this hour. I also want to be able to retrieve the StandHours count for the day.

Here are the Apple links that I've been trying to understand in order get the value from HealthKit. I provide these links to help provide understanding for what I'm looking for and also to help you answer my question.

Bruno's answer is only half of the answer. For example, his standUnit variable is how he pulls the # of hours that the user has stood today. I tested it. Also, I made the assumption that it had to be pulled from within the scope of the summaries variable.

I have found another question on StackOverflow that might provide some clues. I think they managed to pull a value via a HKCategoryTypeIdentifier: Watch os 2.0 beta: access heart beat rate

Here's my attempted code as far as I have been able to get:

import UIKit
import HealthKit
import HealthKitUI

class ViewController: UIViewController {

let hkStoreOnVC : HKHealthStore = HKHealthStore()

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
    authorizeHealthKit()
    hkTest()
    hkTest2()
}

func authorizeHealthKit() {   //-> Bool {
    print("health kit authorize?")
    
    let healthStore = HKHealthStore()
    
    let objectTypes: Set<HKObjectType> = [
        HKObjectType.activitySummaryType()
    ]
    
    healthStore.requestAuthorization(toShare: nil, read: objectTypes) { (success, error) in
        
        // Authorization request finished, hopefully the user allowed access!
        print("health kit authorized")
    }
}

func hkTest() {
    print("health kit test.")
    
    let calendar = Calendar.autoupdatingCurrent
    
    var dateComponents = calendar.dateComponents(
        [ .year, .month, .day ],
        from: Date()
    )
    
    // This line is required to make the whole thing work
    dateComponents.calendar = calendar
    
    let predicate = HKQuery.predicateForActivitySummary(with: dateComponents)
    
    //----------------------
    
    let query = HKActivitySummaryQuery(predicate: predicate) { (query, summaries, error) in
        print("query")
        guard let summaries = summaries, summaries.count > 0
            else {
                print("no summaries")
                return
        }
        
        // Handle data
        
        for thisSummary in summaries {
            //                print("for each summary")
            let standUnit = HKUnit.count()
            let standHours = thisSummary.appleStandHours.doubleValue(for: standUnit)
            print("stand hours \(standHours)")
        }//end for
    } //end query
}

func hkTest2() {
    var isEnabled = true
    print ("authorize health kit" )
    if HKHealthStore.isHealthDataAvailable() {
        let stepsCount  = NSSet(objects: HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.stepCount ) )
        
        for thisValue in stepsCount {
            //              thisValue.
            print("thisValue: \(thisValue)")
        }

        print(" authorize HK - steps count \(stepsCount) ")
    }
    
    // Create the date components for the predicate
    guard let calendar = NSCalendar(calendarIdentifier: NSCalendar.Identifier.gregorian) else {
        fatalError("*** This should never fail. ***")
    }
    
    let endDate = NSDate()
    
    guard let startDate = calendar.date(byAdding: .day, value: -7, to: endDate as Date, options: []) else {
        fatalError("*** unable to calculate the start date ***")
    }
    
    let units: NSCalendar.Unit = [.day, .month, .year, .era]
    
    var startDateComponents = calendar.components(units, from: startDate)
    startDateComponents.calendar = calendar as Calendar
    
    var endDateComponents = calendar.components(units, from: endDate as Date)
    endDateComponents.calendar = calendar as Calendar
    
    // Create the predicate for the query
    let summariesWithinRange =  HKQuery.predicate(forActivitySummariesBetweenStart: startDateComponents, end: endDateComponents)
    
    // Build the query
    let query = HKActivitySummaryQuery(predicate: summariesWithinRange) { (query, summaries, error) -> Void in
        guard let activitySummaries = summaries else {
            guard let queryError = error else {
                fatalError("*** Did not return a valid error object. ***")
            }
            
            // Handle the error here...
            return
        }
        
        for thisSummary in activitySummaries {
            //                print("for each summary")
            let standUnit = HKUnit.count()
            let standHours = thisSummary.appleStandHours.doubleValue(for: standUnit)
            //                let stoodThisHourMaybe = thisSummary.appleStandHours.categ //doubleValue(for: standUnit)
            //\(thisSummary.description) //stand unit _\(standUnit)_
            print("day#\(thisSummary.dateComponents(for: calendar as Calendar).day) stand hours \(standHours)  ")
        }//end for
        
        // Do something with the summaries here...
        
        for thisItem in activitySummaries {
            //thisItem.appleStandHours
            
            print("abc \( thisItem.appleStandHours ) " )
        }//end for
    }
    
    // Run the query
    let hkStore : HKHealthStore = HKHealthStore()
    hkStore.execute(query)
    
    //***
    let aStandHour =  HKCategoryType.categoryType(forIdentifier: .appleStandHour)
   
    // This is the type you want updates on. It can be any health kit type, including heart rate.
    //            let distanceType = HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.distanceWalkingRunning)
    
    // Match samples with a start date after the workout start
    //        let predicate =  .predicat //( , endDate: nil, options: .None)
    //        let theDate : Date =
    let thepredicate =  HKQuery.predicateForCategorySamples(with: .greaterThanOrEqualTo, value: 0) //.predicateForSamplesWithStartDate(startDate , endDate: nil, options: .None)
    //        predicate
    
    //        let predicate = .  //(theDate , endDate: nil, options: .None)
    let hka : HKQueryAnchor = HKQueryAnchor(fromValue: 0)
    let sHourQuery = HKAnchoredObjectQuery(type: aStandHour!, predicate: thepredicate, anchor: hka, limit: 0, resultsHandler: { ( query, samples, deletedObjects, anchor, error) -> Void in
        // Handle when the query first returns results
        // TODO: do whatever you want with samples (note you are not on the main thread)
        print("getting here A?")
        //            for thisSample in samples! {
        //                print("A smpLType \(thisSample.sampleType) thisSample \(thisSample)")
        //            }
    })
    
    // This is called each time a new value is entered into HealthKit (samples may be batched together for efficiency)
    
    sHourQuery.updateHandler = { (query, samples, deletedObjects, anchor, error) -> Void in
        // Handle update notifications after the query has initially run
        // TODO: do whatever you want with samples (note you are not on the main thread)
        print("getting here B?")
        for thisSample in samples! {
            print("B smpLType \(thisSample.sampleType) thisSample \(thisSample)")
        }
    }
    
    // Start the query
    self.hkStoreOnVC.execute(sHourQuery)
    //***
    
}//end func

func myCompletionHandler(bTest: Bool ) {
    print("my completion handler")
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}

}//end viewController Class

Here's the code output - the log never prints to "getting here b?":

health kit authorize?
health kit test.
authorize health kit
health kit authorized
thisValue: HKQuantityTypeIdentifierStepCount
 authorize HK - steps count {(
    HKQuantityTypeIdentifierStepCount
)} 
2017-11-04 19:18:30.100562-0500 watchapptest[25048:4695625] refreshPreferences: HangTracerEnabled: 0
2017-11-04 19:18:30.100600-0500 watchapptest[25048:4695625] refreshPreferences: HangTracerDuration: 500
2017-11-04 19:18:30.100615-0500 watchapptest[25048:4695625] refreshPreferences: ActivationLoggingEnabled: 0 ActivationLoggingTaskedOffByDA:0
getting here A?
day#Optional(28) stand hours 14.0  
day#Optional(29) stand hours 14.0  
day#Optional(30) stand hours 14.0  
day#Optional(31) stand hours 14.0  
day#Optional(1) stand hours 16.0  
day#Optional(2) stand hours 13.0  
day#Optional(3) stand hours 15.0  
day#Optional(4) stand hours 13.0  
abc 14 count 
abc 14 count 
abc 14 count 
abc 14 count 
abc 16 count 
abc 13 count 
abc 15 count 
abc 13 count 
ricardopereira
  • 11,118
  • 5
  • 63
  • 81
Neo42
  • 642
  • 3
  • 10
  • 31

2 Answers2

3

I am new to HealthKit, so there probably is a nicer way to do this. But this seems to work for me. I check the actually standing minutes and call the completion handler with minutes > 0.

    private let store = HKHealthStore()

    func askPermission() {
        let standType = HKQuantityType.quantityType(forIdentifier: .appleStandTime)!
        store.requestAuthorization(toShare: [], read: [standType], completion: { (success, error) in
            self.didStandThisHour { (didStand) in
                print("Did stand this hour: \(didStand)")
            }
        })
    }

    func didStandThisHour(_ didStand: @escaping (Bool) -> ()) {
        let store = HKHealthStore()
        let calendar = Calendar.autoupdatingCurrent
        let dateComponents = calendar.dateComponents([.year, .month, .day, .hour], from: Date())
        let endDate = Date()
        let startDate = calendar.date(from: dateComponents)!
        let standTime = HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.appleStandTime)!
        var interval = DateComponents()
        interval.hour = 1
        let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictStartDate)
        let query = HKStatisticsCollectionQuery(quantityType: standTime, quantitySamplePredicate: predicate, options: [.cumulativeSum], anchorDate: startDate, intervalComponents:interval)
        query.initialResultsHandler = { query, results, error in
            guard error == nil, let myResults = results else {
                fatalError("Something is wrong with HealthKit link")
            }
            myResults.enumerateStatistics(from: startDate, to: endDate, with: { (statistics, stop) in
                guard let quantity = statistics.sumQuantity() else {
                    didStand(false)
                    return
                }
                let minutes = quantity.doubleValue(for: .minute())
                didStand(minutes > 0)
            })
        }
        store.execute(query)
    }
dacvrijma
  • 41
  • 5
  • 1
    I found out this approach is not 100% reliable. Sometimes the minutes are still zero although the stand hours in activity has already been checked off. I don't know if this has something to do with HealthKit synchronization or not. Maybe there is a small delay to when a standing minute is added to HealthKit – dacvrijma Oct 01 '19 at 09:27
  • Thanks. I did stop looking at this when the watch got updated to again to show whether or not you've stood this hour. For a minor version or two, apple watch wasn't showing if your current stand goal was achieved. Which is why I was interested in building a watch app to re-show this indicator. – Neo42 Oct 01 '19 at 14:46
  • Why not use this with seconds instead of minutes? Maybe that's why it has false negatives. – Radu Vlad Dec 08 '19 at 16:00
2

Ok, if you want to retrieve today's activity ring info (including stand hours) you first request user authorization for the object type you want to retrieve:

let healthStore = HKHealthStore()

let objectTypes: Set<HKObjectType> = [
    HKObjectType.activitySummaryType()
]

healthStore.requestAuthorization(toShare: nil, read: objectTypes) { (success, error) in

    // Authorization request finished, hopefully the user allowed access!
}

Then you can use this predicate to retrieve today's date:

let calendar = Calendar.autoupdatingCurrent

var dateComponents = calendar.dateComponents(
    [ .year, .month, .day ],
    from: Date()
)

// This line is required to make the whole thing work
dateComponents.calendar = calendar

let predicate = HKQuery.predicateForActivitySummary(with: dateComponents)

Create a query...

let query = HKActivitySummaryQuery(predicate: predicate) { (query, summaries, error) in

    guard let summaries = summaries, summaries.count > 0
    else {
        return
    }

    // Handle data
}

The data you'll receive is of type HKActivitySummary and you can retrieve, for example:

let sandUnit = HKUnit.count()
let standHours = summary.appleStandHours.doubleValue(for: standUnit)
  • 1
    I need the value that indicates whether they stood in the current hour. – Neo42 Oct 28 '17 at 21:55
  • Your answer isn't full. The current stand hour is the hard part and nobody online gives an example of how to get it. – Neo42 Oct 29 '17 at 15:12
  • 1
    you can specify that in the dateComponents variable, try and get the current hour there. edit: https://stackoverflow.com/questions/2927028/how-do-i-get-hour-and-minutes-from-nsdate – Bruno Fulber Wide Oct 29 '17 at 17:26
  • Please re-read my question(s) as you still haven't answered my main question. I'll try to explain further too. The Apple Watch stores TWO seperate values. StandHour, which is = to whether or not I stood this hour. AND it stores StandHours which = how many hours I stood today. – Neo42 Oct 30 '17 at 13:57
  • Did anyone ever figure out how to do this!? Ie: get the AppleStandHour for CURRENT HOUR? – Gerard Jun 15 '20 at 16:32