How to get metadata from HealthKit? - ios

I'm working on an application that reads different health data from HealthKit application.
So far I managed to get the DOB, most recent records of height, weight and blood glucose.
What I still need is how to get the metadata for these objects, specifically I need to get the date/time the record was entered.
For example, to get the record of the height, I'm using this method:
func updateHeight()
{
// 1. Construct an HKSampleType for Height
let sampleType = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeight)
// 2. Call the method to read the most recent Height sample
self.healthManager?.readMostRecentSample(sampleType, completion: { (mostRecentHeight, error) -> Void in
if( error != nil )
{
println("Error reading height from HealthKit Store: \(error.localizedDescription)")
return;
}
var heightLocalizedString = self.kUnknownString;
self.height = mostRecentHeight as? HKQuantitySample;
// 3. Format the height to display it on the screen
if let meters = self.height?.quantity.doubleValueForUnit(HKUnit.meterUnit()) {
let heightFormatter = NSLengthFormatter()
heightFormatter.forPersonHeightUse = true;
heightLocalizedString = heightFormatter.stringFromMeters(meters);
}
// 4. Update UI. HealthKit use an internal queue. We make sure that we interact with the UI in the main thread
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.heightLabel.text = heightLocalizedString
});
})
}
As you notice I'm creating an HKSampleType constant then pass it to a method called readMostRecentSample which takes this parameter and then returns the most recent record for this sample type.
I tried to print line the returned object and I've got this output:
1.9 m "Health" metadata: { HKWasUserEntered = 1; } 2015-05-17 10:11:00 +0300 2015-05-17 10:11:00 +0300
As you see the output includes the metadata of the object, but actually I couldn't extract only the date.
Also I found that there is a property of the object called metadata, I printed it out but it only retrieved me a boolean of whether the data was entered by the user (manually) or automatically from a third party:
println(self.height?.metadata)
The output was:
[HKWasUserEntered = 1]
I would be grateful and thankful if someone can give me any idea of how to extract the metadata of each object.

A HKSample object and its subclasses like HKQuantitySample have 2 fields that store date information : startDate and endDate. If you are trying to get the date this is where you should look.
Some samples—for example, body temperature—represent a single point in
time. For these samples, both the start and the end date are the same,
because they both refer to the point in time when the sample was
taken.
Other samples—for example, step count—represent data over a time
interval. Here, the sample should use different start and end dates.
These dates mark the beginning and end of the sample’s time interval,
respectively.
From the documentation https://developer.apple.com/library/ios/documentation/HealthKit/Reference/HKSample_Class/index.html#//apple_ref/occ/instp/HKSample/startDate

Related

How can I use the section parameter to iterate over each document that's inside of a collection to access the count of a subcollection?

So I'm working on my first workout tracking app and this is my first time using Firebase/Firestore, so I'm just trying to figure out if there is a simple query that I can use for this...
Here is what my Firestore Database structure looks like:
/Users/mi9P3TrLwkQ3oDIut/Days/WZ3Q6LDuu1kja/Workouts/BpLGFREoJNzNQW/Exercises/5vRWuHlcJHc/WeightReps/cKrB0Dpf0myEDQV0
Basically I need to return a value for numberOfRowsInSection, but the value that I need to access is the number of workouts that are associated with each day of the week, and I'm not too sure how to go about using the section parameter to iterate over each day document in my Days collection in order to access the Workouts subcollections and get the count of the documents there for each day collection. Does that make sense?
I hope that the question makes sense. Any help would be greatly appreciated!
Not entirely sure if I am getting your question right but if you want to retrieve several documents with all their attributes this is how you can do it:
var counter = 0
func getData() {
let db = Firestore.firestore()
let userID = Auth.auth().currentUser!.uid
for data in self.dataSourceArray {
db.collection("users").document(userID).collection("yourCollectionName").document(data.name).collection("yourCollectionName").getDocuments() { ( querySnapshot, error) in
if let error = error {
print(error.localizedDescription)
} else {
for document in querySnapshot!.documents {
self.counter += 1
}
}
}
}
}
Is this what youre looking for ?

How can i update an object (map) in an array with swift in firestore database?

I want to update a field of an object that the object is into an array in firestore database with swift 4. These two function (arrayUnion() and arrayRemove() ) are not working for me.
here my Database schema:
enter image description here
I want to update "Status" field in The first array element.
Please help me.
first of all I want to thanks to #Doug Stevenson for his kindly response.
in my case, I must change the array of the objects to sub collections.
In a transaction, fetch the document, modify the field of the object as you see fit in memory on the client, then update that entire field back to the document.
I believe the question is:
how can I update a specific field that's stored within a document in
an array.
As long as you know the documentId to the document you want to update - here's the solution.
Assuming a structure similar to what's in the question
Friends (a collection)
0 (a document)
Name: "Simon"
Status: "Sent"
1
Name: "Garfunkle"
Status: "Unsent"
and say we want to change Garfunkle's status to Sent
I will include one function to read document 1, Garfunkle's and then a second fuction to update the Status to sent.
Read the document at index 1 and print it's fields - this function is just for testing to show the field's values before and after changing the status field.
func readFriendStatus() {
let docRef = self.db.collection("Friends").document("1")
docRef.getDocument(completion: { document, error in
if let document = document, document.exists {
let name = document.get("Name") ?? "no name"
let status = document.get("Status") ?? "no status"
print(name, status)
} else {
print("no document")
}
})
}
and the output
Garfunkle Unsent
then the code to update the Status field to Sent
func writeFriendStatus() {
let data = ["Status": "Sent"]
let docRef = self.db.collection("Friends").document("1")
docRef.setData(data, merge: true)
}
and the output
Garfunkle, Sent

Couchbase Lite View Aggregation Values are null (empty)

I'm trying to do a simple view, that takes my data and groups all of my records by a key "sortDate". I feel like i've followed all the examples, read all the documents multiple times and it just doesn't work as expected.
Here is the view code i'm creating:
guard let database = database else { return }
database.viewNamed("byDate").setMapBlock({ (doc, emit) in
if let date = doc["sortDate"] as? String {
emit(date, doc)
}
}, version: "8")
let query = database.viewNamed("byDate").createQuery()
query.groupLevel = 1
query.descending = true
do {
let result = try query.run()
print(result)
while let row = result.nextRow() {
print(row)
print(row.value) //EMPTY
}
} catch {
print("Failed to retrieve all documents for \(database.name) database")
}
My row.value is NULL even though there are multiple records in the database, my allDocs query is returning just fine.
Specifying a grouplevel when the key is not an array causes CBL to aggregate the results using the reduce function. You haven't specified one, so that's why you get no results.
From the docs:
The value property of each row will be the result of running the view's reduce function over all the rows that were aggregated; or if the view has no reduce function, there's no value. (See the View documentation for information on reduce functions.)

How to properly call `fetchAllSubscriptionsOperation()` to retrieve CloudKit subscriptions?

I am trying to get all the subscriptions associated with the current user. According to the documentation, the function to call is:
CKFetchSubscriptionsOperation.fetchAllSubscriptionsOperation()
I call the function this way:
let op = CKFetchSubscriptionsOperation.fetchAllSubscriptionsOperation()
op.fetchSubscriptionCompletionBlock = { (subs, error) in
print("*** fetched subs: \(subs)")
}
let q = OperationQueue()
q.addOperation(op)
However, the subs parameter returns an empty dictionary (Optional([:])). The error param is nil. I'm new to using NSOperation, so I'm wondering if I am doing something wrong.
(As confirmation that subscriptions exist to be retrieved, I separately call the container's publicDatabase.fetchAllSubscriptions() function. This one returns the current user's subscriptions as expected. Unfortunately, it also returns the subscriptions associated with every other user in the schema.)
Your sample code does not set the container and the database on the CKFetchSubscriptionsOperation. And since you are adding the operation to your own OperationQueue, you need to set these.
Option #1:
Set the container and the database on the operation directly.
let op = CKFetchSubscriptionsOperation.fetchAllSubscriptionsOperation()
op.fetchSubscriptionCompletionBlock = { (subs, error) in
print("*** fetched subs: \(subs)")
}
op.container = CKContainer.default()
op.database = CKContainer.default().publicCloudDatabase
let q = OperationQueue()
q.addOperation(op)
Option #2:
Add the operation to the desired database's queue (which sets these for you).
let op = CKFetchSubscriptionsOperation.fetchAllSubscriptionsOperation()
op.fetchSubscriptionCompletionBlock = { (subs, error) in
print("*** fetched subs: \(subs)")
}
CKContainer.default().publicCloudDatabase.add(op)

Core Data - Fetching, Sorting and Editing/Updating Attributes

So I've dipped my toes into the Core Data pool, and a dark and mysterious pool it is indeed...
I have the following:
An entity named RAS.
The following attributes exist in RAS:
rasIdentifier of type String (not optional)
rasAutoRetire of type Bool (optional)
rasAutoRetireDate of type NSDate (optional)
rasReassessmentDate of type NSDate (optional)
rasReassesmentDueNow of type Bool (optional)
rasAutoRetireDone of type Bool (optional)
rasReassessmentDone of type Bool (optional)
When I first save a "record" the following happens:
If the user selected for the item to auto-retire on a specific future date, the values are set as follows:
rasIdentifier gets a unique value.
rasAutoRetire is set to true
rasAutoRetireDate is set to a specific date (say a week in the future).
rasReassessmentDate is left blank
rasReassessmentDueNow is set to false
rasAutoRetireDone is set to false
rasReassessmentDone is set to false
If the user selected for the item to be reassessed on a specific future date, the values are set as follows:
rasIdentifier gets a unique value.
rasAutoRetire is set to false
rasAutoRetireDate is left blank.
rasReassessmentDate is set to a specific date.
rasReassessmentDueNow is set to false
rasAutoRetireDone is left blank
rasReassessmentDone is set to false
From the above it should be clear that an item has two options in the future: to either auto-retire or to be reassessed.
To check if an item has reached and passed its reassessment date I have started writing the following function, but the code is clunky and I am getting nowhere with it, and I would REALLY appreciate some genius help here... Here goes:
func HandleItemsThatNeedReassessment() {
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedObjContext = appDelegate.managedObjectContext
let fetchRequest = NSFetchRequest(entityName: "RAS")
var error: NSError?
//Step 1: Check if database exists
let count = managedObjContext.countForFetchRequest(fetchRequest, error: &error)
if count == NSNotFound {
print("Database does not exist")
} else {
print("Database exists. Check now if there are any records.")
//Step 2: Check if there are any records in the database
do {
let results = try managedObjContext.executeFetchRequest(fetchRequest)
let varRecordsExist = results as! [NSManagedObject]
if varrecordsExist.count == 0 {
print("No records in the database")
//do nothing further!
} else {
print("Yes, there are \(varRecordsExist.count) records in the database.")
//Step 3: Select all the existing records that are due for reassessment (and leave those that are due for auto-retirement - for now)
//NO IDEA WHAT TO DO HERE!
//Step 4: Check which items are due for reassessment, but exclude those that have already been reassessed (rasReassessmentDone = true)
//NO IDEA WHAT TO DO HERE EITHER!
//Step 5: Because the reassessment date is in the past, change the valueForKey("rasReassessmentDueNow") to TRUE
if varIsAssessmentOverdue == true {
//NO IDEA WHAT TO DO HERE - AGAIN!
??SomethingHere??.setValue(true, forKey: "rasReassessmentDueNow")
//Do this for all items that needs reassessment
}
//Step 6: If any changes were made/values were changed in Step 5, SAVE those changes to Core Data
if managedObjContext.hasChanges {
do {
try managedObjContext.save()
//How do you save these changes???
} catch let error as NSError {
NSLog("Could not save \(error), \(error.userInfo)")
}
}
}
}
}
catch
let error as NSError {
print("Print error stuff here \(error)")
}
}
}
Yeah, the attempted code sucks. I don't even know if my steps are logical. Is it even possible to do this all at once? A lot of the above does not make sense to me - despite hours of googling and reading - and a kind, comprehensive explanation would be highly appreciated.
By the way, this function is called in the viewDidLoad of the first view controller of the app, and that's why I do all those checks in Step 1 and Step 2, otherwise things just crash to a grinding halt if there are no records.
I wouldn't check if there is anything in database at the beginning, I would jump straight to step 3.
You just need to specify predicates which match your requirements, for example:
//Step 3: Select all the existing records that are due for reassessment (and leave those that are due for auto-retirement - for now)
//You could also combine it with Step 4 in the same predicate:
let predicate1 = NSPredicate(format: "%K == true AND %K == false AND %K = false", "rasReassessmentDueNow", "rasAutoRetire", "rasReassessmentDone")
Now create fetch request as you did above, add predicate to the request and run it.
fetchRequest.predicate = predicate1
do {
let results = try managedObjContext.executeFetchRequest(fetchRequest)
let records = results as! [NSManagedObject]
...
Now you have an array with object and here you can check count property to see have you got any data.
In step 5 just enumerate the array and check if varIsAssessmentOverdue is true.
But to be honest I would add another predictate to to the predicate above which check the varIsAssessmentOverdue is true (..AND %K == true",.."varIsAssessmentOverdue") in that case you have only the object you want and you can only enumerate the array and set value to required one, every object in the array will be the one you looking for
for ras in results {
// I don't understand what you are trying to do here, all of the items here will be true and you want to change it to true again?
ras.setValue(value, forKey: attribute)
}
Save changes as you have in the code.
Consider this code mostly as pseudo code some bits will require small amendments but you should be able to pick it up.
The idea here is to hit the database as rarely as possible. You should create NSPredicate which brings you back the result with only the data you want, after then just make changes to the data and save it.

Resources