startDate, endDate, and adding text to label in ViewController - ios

So I am having a few bugs and errors in my ViewController code in my app. First, in my line:
let predicate = HKQuery.predicateForSamplesWithStartDate(startDate,endDate: endDate ,options: .None)
I get the following error: use of unresolved identifier 'endDate'
This is weird. Is it because I have not made them into NSDate objects? How would I go about doing that?
As for my second question, it is not exactly a bug, I simply do not know how to do something. I have connected my label from the storyboard into my view controller. I want to get the data I collect to simply print onto the screen in the label. I know how to do this in most situations but I am lost as to how I can do this within my current configuration. I don't want the user to press a button or anything, I just want it to automatically display by default...Any help is greatly appreciated!!!
I have included all my code below:
import UIKit
import HealthKit
class ViewController: UIViewController {
#IBOutlet weak var displayData: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// create store
let healthStore = HKHealthStore()
// create an object type to request an authorization for a specific category, here is SleepAnalysis
if let sleepType = HKObjectType.categoryTypeForIdentifier(HKCategoryTypeIdentifierSleepAnalysis) {
let setType = Set<HKSampleType>(arrayLiteral: sleepType)
healthStore.requestAuthorizationToShareTypes(setType, readTypes: setType, completion: { (success, error) -> Void in
// here is your code
})
}
if let sleepType = HKObjectType.categoryTypeForIdentifier(HKCategoryTypeIdentifierSleepAnalysis) {
// we create a predicate to filter our data
let predicate = HKQuery.predicateForSamplesWithStartDate(startDate,endDate: endDate ,options: .None)
// I had a sortDescriptor to get the recent data first
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false)
// we create our query with a block completion to execute
let query = HKSampleQuery(sampleType: sleepType, predicate: predicate, limit: 30, sortDescriptors: [sortDescriptor]) { (query, tmpResult, error) -> Void in

endDate is undefined - you have not actually created a variable that represents endDate - thus the compiler is telling you that. And, also, startDate is also undefined, even though the compiler hasn't told you that yet. In the least, you need to create NSDates.
let startDate = NSDate()
let endDate = NSDate()
More specifically, you'll need to create them in the range for which you want to query.
Regarding showing the data by default, simply set the UILabel's text in viewDidLoad. I'm assuming that you want to set this text in response to the HealthKit query? If so, you can still do that in viewDidLoad
So, to use your query, it needs to be provided to executeQuery on healthStore Here's some code that you could use with what you posted:
healthStore.executeQuery(HKSampleQuery(
sampleType: sleepType,
predicate: predicate,
limit: 30,
sortDescriptors: [sortDescriptor],
resultsHandler: { (query: HKSampleQuery!, results: [AnyObject]!, err: NSError?) -> Void in
if err != nil {
// ERROR Occurred, handled it
println(err)
return
}
var labelText = ""
for result in results as [HKQuantitySample]! {
// SUCCESS, use results here
labelText += result
}
displayData.text = labelText
}
))

Related

Unable to read workout data in healthkit in SwiftUI

I'm trying to get the number of workouts performed within the week and display the count in a view and also display the workout like you see in the Apple Fitness app. I'm having issues reading the data and displaying it. I'm trying to only get functional strength training and traditional strength training workouts. Below is the code I'm using. I'm omitting the authorization method because I have that with other health variables I'm getting. Also, when I try to get the workoutActivity type to display, nothing is showing up. I've looked over apple's healthkit documentation but getting a big confused/lost as a beginner, particularly with the workout data. Any help would be appreciated
class HealthStoreViewModel: ObservableObject {
var selectedWorkoutQuery: HKQuery?
#Published var muscleStrength: [HKWorkout] = [HKWorkout]()
func getStrengthTrainingWorkouts() {
let date = Date()
let startDate = Calendar.current.dateInterval(of: .weekOfYear, for: date)?.start
let datePredicate = HKQuery.predicateForSamples(withStart: startDate, end: nil, options: .strictStartDate)
let traditionalStrengthTrainingPredicate = HKQuery.predicateForWorkouts(with: .traditionalStrengthTraining)
let functionalStrengthTrainingPredicate = HKQuery.predicateForWorkouts(with: .functionalStrengthTraining)
let strengthCompound = NSCompoundPredicate(andPredicateWithSubpredicates: [datePredicate, traditionalStrengthTrainingPredicate, functionalStrengthTrainingPredicate])
let selectedWorkoutQuery = HKSampleQuery(sampleType: HKWorkoutType.workoutType(), predicate: strengthCompound, limit: HKObjectQueryNoLimit, sortDescriptors: nil) { strengthQuery, samples, error in
guard let samples = samples else {
fatalError("An error has occured \(error?.localizedDescription)")
}
DispatchQueue.main.async {
if let workouts = samples as? [HKWorkout] {
for workout in workouts {
self.muscleStrength.append(workout)
}
}
}
}
self.healthStore?.execute(selectedWorkoutQuery)
}
Here is the view I would like to display the count and workouts but nothing is showing
struct MuscleView: View {
#ObservedObject var healthStoreVM: HealthStoreViewModel
var body: some View {
List(healthStoreVM.muscleStrength, id: \.self) {
workout in
Text("\(workout.workoutActivityType.rawValue)")
}
}
}
refer the link here. You have this workoutActivityType property in HKWorkout objects. The error thrown in valid as you are trying to assign a HKWorkout class object into a Enum definition of HKWorkoutActivityType
self.muscleStrength.append(workouts.workoutActivityType)

How to return result of HKStatisticsQuery to a variable in Swift 2?

UPDATED at bottom on 01/30/16 # 7:40PM EST
So I'm trying to run a StatisticsQuery to get the total DistanceRunningWalking of the day stored in HealthKit and then store the results of that query in a variable, to be worked with later. The query seems to be working fine, as I've tested printing the results of the query (totalDistance) to a Label from within the function. The problem I'm running into is when trying to save the result to a variable instead.
Here is the code in my HealthKitManager.swift file:
import HealthKit
class HealthKitManager {
class var sharedInstance: HealthKitManager {
struct Singleton {
static let instance = HealthKitManager()
}
return Singleton.instance
}
let healthStore: HKHealthStore? = {
if HKHealthStore.isHealthDataAvailable() {
return HKHealthStore()
} else {
return nil
}
}()
let distanceCount = HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierDistanceWalkingRunning)
let distanceUnit = HKUnit(fromString: "mi")
}
Code at top of ViewController: (this is the variable I'd like to save to)
let healthKitManager = HealthKitManager.sharedInstance
//Set up variable to contain result of query
var distanceTotalLength:Double?
Code in viewDidLoad:
//Run the function
requestHealthKitAuthorization()
//Set value of variable to query result
distanceTotalLength = queryDistanceSum()
Code in body of ViewController:
func requestHealthKitAuthorization() {
let dataTypesToRead = NSSet(objects: healthKitManager.distanceCount!)
healthKitManager.healthStore?.requestAuthorizationToShareTypes(nil, readTypes: dataTypesToRead as NSSet as? Set<HKObjectType>, completion: { [unowned self] (success, error) in
if success {
self.queryDistanceSum()
} else {
print(error!.description)
}
})
}
func queryDistanceSum() {
let sumOption = HKStatisticsOptions.CumulativeSum
let startDate = NSDate().dateByRemovingTime()
let endDate = NSDate()
let predicate = HKQuery.predicateForSamplesWithStartDate(startDate, endDate: endDate, options: [])
let statisticsSumQuery = HKStatisticsQuery(quantityType: healthKitManager.distanceCount!, quantitySamplePredicate: predicate, options: sumOption) {
[unowned self] (query, result, error) in
if let sumQuantity = result?.sumQuantity() {
dispatch_async(dispatch_get_main_queue(), {
let totalDistance = sumQuantity.doubleValueForUnit(self.healthKitManager.distanceUnit)
self.distanceTotalLength = totalDistance
})
}
}
healthKitManager.healthStore?.executeQuery(statisticsSumQuery)
}
On the last line return (distanceTotalLength)! I'm getting an error when launching the application which reads fatal error: unexpectedly found nil while unwrapping an Optional value. I've realized that this is most likely a problem dealing with scope (although I may be doing something else wrong, so please point anything out) but I'm failing to see/find the solution to the issue on my own.
Any help with this would be greatly appreciated, so thanks in advance!
UPDATE: 01/30/16 # ~7:40PM EST
Okay, so I've been working on trying to fix this myself and I've made a discovery: The code is definitely working. By assigning an initial value of 0 to distanceTotalLength at the top of the ViewController, I was able to run the application without getting the fatal error. However, when I then tried passing the value of distanceTotalLength to another view via a prepareForSegue function, I realized that it is being assigned after all. When I go to that view, it isn't using the initial value of 0, but the result of the query instead.
The way I tested this out was by setting up the variable: var distanceTotalLength:Double = 0 at the very top of my viewController, before viewDidLoad Then inside viewDidLoad I assigned the value to a label using distanceLabel.text = String(distanceTotalLength) and as I said, the label ends up reading 0. But when I transition to another view, passing the value of distanceTotalLength and printing the value out there, it works. On this second screen, it prints the result of the query, not 0.
So I'm assuming that the problem is that the query runs and then assigns the value AFTER the view has already loaded in with all of it's predefined values. Unfortunately, this is where I get stuck again. Anyone out there know how to help me out now that I've gotten this far?
You are right. The completionHandler closure that gets called after your HKStatisticsQuery has finished executing happens at an undermined later time. Think of the query execution as sending a letter in the mail to someone, then waiting for their response; it's not going to be immediate, but in the meantime you can go off and do other things.
To handle this in your code, add a completion closure of your own to the queryDistanceSum method. Then after setting the self.distanceTotalLength = totalDistance, call that closure. When implementing the code for the closure, add anything that needs to be done after the distance has been set, like update your UI.
func queryDistanceSum(completion: () -> Void) { // <----- add the closure here
let sumOption = HKStatisticsOptions.CumulativeSum
let startDate = NSDate().dateByRemovingTime()
let endDate = NSDate()
let predicate = HKQuery.predicateForSamplesWithStartDate(startDate, endDate: endDate, options: [])
let statisticsSumQuery = HKStatisticsQuery(quantityType: healthKitManager.distanceCount!, quantitySamplePredicate: predicate, options: sumOption) {
[unowned self] (query, result, error) in
if let sumQuantity = result?.sumQuantity() {
dispatch_async(dispatch_get_main_queue(), {
let totalDistance = sumQuantity.doubleValueForUnit(self.healthKitManager.distanceUnit)
self.distanceTotalLength = totalDistance
completion() // <----- call the closure here
})
}
}
healthKitManager.healthStore?.executeQuery(statisticsSumQuery)
}
// Then whenever you need to update the distance sum call the function
// with the closure, then handle the result as needed
queryDistanceSum { () -> () in
// distanceTotalLength has now been set.
// Update UI for new distance value or whatever you need to do
}
Whenever you implement a closure you have to assume that the code in the closure will be executed at a later time.

Restrict HKSampleQuery results to those input from your own app

I wish to restrict results returned from a HealthKit HKSampleQuery to those that have been input through my own app. Is there a way to specify results only with my application bundle identifier, and thereby exclude any other data sources returned from other applications?
Is there a way to specify this with an NSSortDescriptor or NSPredicate, as I have tried below?
func querySteps() {
// let sort = NSSortDescriptor(key: "bundleIdentifier", ascending: true, selector: "com.companyName.appName:")
// let resultPredicate = NSPredicate(format: "bundleIdentifier", "com.companyName.appName")
let sampleQuery = HKSampleQuery(sampleType: healthKitManager.stepsCount!,
predicate: nil,
limit: 100,
sortDescriptors: nil)
{ [unowned self] (query, results, error) in
if let results = results as? [HKQuantitySample] {
self.steps = results
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tableView.reloadData()
});
}
}
healthStore?.executeQuery(sampleQuery)
}
Simple one line solution to the question above. Use HKQuery to create a predicate object that specifies the data's source:
let thePredicate = HKQuery.predicateForObjectsFromSource(HKSource.defaultSource())
And then swap out the nil predicate parameter value with thePredicate, in this case. Then, the results in your table view will show only your own app's HKQuery results.

Reference Array in Different Class Swift

So I have this function, which gets and prints all the HealthKit step data from the past 24 hours, and saves it to an array:
func stepsInPastDay(completion: (Double, NSError?) -> () )
{
var dayStepData = [Double]()
for x in 1...24 {
// The type of data we are requesting
let type = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount)
var hoursAgo = -1 * x
var hoursSince = (-1 * x) + 1
// Our search predicate which will fetch data from now until a day ago
let predicate = HKQuery.predicateForSamplesWithStartDate(NSCalendar.currentCalendar().dateByAddingUnit(.CalendarUnitHour, value: hoursAgo, toDate: NSDate(), options: nil), endDate: NSCalendar.currentCalendar().dateByAddingUnit(.CalendarUnitHour, value: hoursSince, toDate: NSDate(), options: nil), options: .None)
// The actual HealthKit Query which will fetch all of the steps and sub them up for us.
let query = HKSampleQuery(sampleType: type, predicate: predicate, limit: 0, sortDescriptors: nil) { query, results, error in
var steps: Double = 0
if results?.count > 0
{
for result in results as! [HKQuantitySample]
{
steps += result.quantity.doubleValueForUnit(HKUnit.countUnit())
}
}
completion(steps, error)
dayStepData.append(steps)
if dayStepData.count > 23 {
for item in dayStepData {
println(item)
}
}
}
self.healthKitStore.executeQuery(query)
println(dayStepData.count)
}
println(dayStepData.count)
}
However, when I tried to access the array (dayStepData) in my AppDelegate file, with "HKManager.stepsInPastDay.dayStepData" (HKManager is the class), Xcode returns an error. Is there a way to get the array from my function?
This is OOP (Object-oriented-programmming) 101 stuff. You are saving your value to a local variable. Of course it isn't visible in your app delegate.
Put the function in a singleton class of some sort make the function return the array as the function result.
If you're putting app logic in your app delegate you're doing it wrong. Keep your app delegate small and lightweight. It should ONLY handle startup and other app delegate tasks. Put your app-specific logic in other modules.

How do I return the results of this function?

How can I return steps from this HealthKit Query?
I get error: Missing argument for parameter #1 in call. Code:
import UIKit
import HealthKit
class ViewController: UIViewController {
var steps = Double()
var date = NSDate()
var query = performHKQuery() -->error: Missing argument for parameter #1 in call
override func viewDidLoad() {
super.viewDidLoad()
performHKQuery()
printsteps()
}
func performHKQuery () -> (steps: Double, date: NSDate){
let healthKitManager = HealthKitManager.sharedInstance
let stepsSample = HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount)
let stepsUnit = HKUnit.countUnit()
let sampleQuery = HKSampleQuery(
sampleType: stepsSample,
predicate: nil,
limit: 0,
sortDescriptors: nil)
{
(sampleQuery, samples, error) in
for sample in samples as [HKQuantitySample]
{
self.steps = sample.quantity.doubleValueForUnit(stepsUnit)
self.date = sample.startDate
}
}
healthKitManager.healthStore.executeQuery(sampleQuery)
return (steps, date)
}
func printsteps() {
println(query.steps) -->error: ViewController does not have a member named query
}
Any help would be much appreciated! If you can also please explain why your solution works then that would be great! Thanks
Code has been updated after feedback from #jrturton
HKQuery is the name of a class, and you're using it as a variable name. This is confusing the compiler.
Change the name to query or something instead.
You also have some structural problems:
class ViewController: UIViewController {
var steps = Double()
var date = NSDate()
var HKQuery = performHKQuery() -->error: Missing argument for parameter #1 in call
Here you're declaring a property and initialising it straight away. You can't use this as an initializer since self, that owns the method, doesn't exist at initialization.
Change the declaration to:
var query : (steps: Double,date: NSDate)?
This sets it as an optional tuple type. In viewDidLoad, you do this:
query = performHKQuery()
To set the value.
Then, in printSteps, do this:
func printsteps() {
println(query?.steps)
}

Resources