I'm doing some tutorials about how to send and receive data to and from Apple HealthKit app.
Part of the tutorial I'm doing is how to get the height from the healthKitStore.
I want to do the same thing but to retrieve the glucose data instead of the height, I did all the steps but got stuck at this piece of code:
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);
}
As shown, the meters var is being assigned a double value from the meterUnit, and then creating a constant formatter to format the meters var and assign it to the pre-declared var (heightLocalizedString)
My question is, when I use this method for the glucose reading, I face a couple of issues, the first problem is I can't figure out what glucose units are available, the only one I get is
HKUnitMolarMassBloodGlucose
and when I use it an error appears says "'NSNumber' is not a subtype of 'HKUnit'", it's clear from the error this parameter is not a subtype of the HKUnit class.
Another issue is, as shown in the previous code there is a formatter for the height (NSLengthFormatter()), but I can't see a such formatter for the Glucose.
Actually I'm not sure if have to follow exactly the tutorial to get the Glucose data, but also I don't see another manner to do so.
Any ideas please?
Here is the code I'm using to retrieve the glucose data:
func updateGluco(){
let sampleType = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierBloodGlucose)
self.healthManager?.readMostRecentSample(sampleType, completion: {(mostRecentGluco, error) -> Void in
if (error != nil){
println("Error reading blood glucose from HealthKit store: \(error.localizedDescription)")
return;
}
var glucoLocalizedString = self.kUnknownString;
self.gluco = mostRecentGluco as? HKQuantitySample
println("\(self.gluco?.quantity.doubleValueForUnit(HKUnit.moleUnitWithMolarMass(HKUnitMolarMassBloodGlucose)))")
self.gluco?.quantity.doubleValueForUnit(HKUnit.moleUnitWithMolarMass(HKUnitMolarMassBloodGlucose))
if let mmol = self.gluco?.quantity.doubleValueForUnit(HKUnit.moleUnitWithMolarMass(HKUnitMolarMassBloodGlucose)) {
glucoLocalizedString = "\(mmol)"
} else {
println("error reading gluco data!")
}
dispatch_async(dispatch_get_main_queue(), {() -> Void in
self.glucoLabel.text = glucoLocalizedString})
})
}
The "mmol" variable returns a nil value.
I don't know if this relates to my problem, but I've just read an article said that Apple brought back blood glucose tracking in iOS 8.2 and my application deployment target is 8.1. (I can't upgrade the deployment target to the latest iOS despite that the XCode is updated to the last release!)
Looking in the header, blood glucose requires units of the type (Mass / Volume), which means you need to specify a compound unit with a Mass unit divided by a Volume unit:
HK_EXTERN NSString * const HKQuantityTypeIdentifierBloodGlucose NS_AVAILABLE_IOS(8_0); // Mass/Volume, Discrete
Typically, the units that people measure blood glucose in are mg/dL, and mmol/L. You can construct these by using:
HKUnit *mgPerdL = [HKUnit unitFromString:#"mg/dL"];
HKUnit *mmolPerL = [[HKUnit moleUnitWithMetricPrefix:HKMetricPrefixMilli molarMass:HKUnitMolarMassBloodGlucose] unitDividedByUnit:[HKUnit literUnit]];
Note that doubleValueForUnit: requires an HKUnit, not an NSNumber. See for more information
I figured out the solution
To get the actual value of the sample gulco, I have only to use this property: self.gluco?.quantity, that's exactly what I wanted before.
The default unit for the Glucose in HealthKit is mg\dL, in order to change the unit simply I extract the number value from the result and then divide it by 18.
Here is the code I'm using to retrieve the glucose data:
NSInteger limit = 0;
NSPredicate* predicate = [HKQuery predicateForSamplesWithStartDate:[NSDate date] endDate:[NSDate date] options:HKQueryOptionStrictStartDate];;
NSString *endKey = HKSampleSortIdentifierEndDate;
NSSortDescriptor *endDate = [NSSortDescriptor sortDescriptorWithKey: endKey ascending: NO];
HKSampleQuery *query = [[HKSampleQuery alloc] initWithSampleType: [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierBloodGlucose]
predicate: predicate
limit: limit
sortDescriptors: #[endDate]
resultsHandler:^(HKSampleQuery *query, NSArray* results, NSError *error)
{
dispatch_async(dispatch_get_main_queue(), ^{
// sends the data using HTTP
// Determine the Blood Glucose.
NSLog(#"BloodGlucose=%#",results);
if([results count]>0){
NSMutableArray *arrBGL=[NSMutableArray new];
for (HKQuantitySample *quantitySample in results) {
HKQuantity *quantity = [quantitySample quantity];
double bloodGlucosegmgPerdL = [quantity doubleValueForUnit:[HKUnit bloodGlucosegmgPerdLUnit]];
NSLog(#"%#",[NSString stringWithFormat:#"%.f g",bloodGlucosegmgPerdL]);
}
}
});
}];
[self.healthStore executeQuery:query];
// And Units :
#implementation HKUnit (HKManager)
+ (HKUnit *)heartBeatsPerMinuteUnit {
return [[HKUnit countUnit] unitDividedByUnit:[HKUnit minuteUnit]];
}
+ (HKUnit *)bloodGlucosegmgPerdLUnit{
return [[HKUnit gramUnit] unitDividedByUnit:[HKUnit literUnit]];
}
Related
I'm currently building an food tracker iOS app (using swift 2) and I would like to have a database with all the foods (and their info) stored in the app and accessible.
The idea is that when some add a 'ice cream' to their meal, his calories/sugar/fat 'counters' increase by the respective nutritional value of the ice cream. (so that this data can be processed later on)
I have found a database of food in what seems like JSON format (see bellow) but I have no idea how to process all this data with swift so that I could access the number of calories in a specific ingredient for example.
So far I tried this:
let url = NSURL(string: "myURL")
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url!, completionHandler: { (data, response, error) -> Void in
if error != nil {
print(error)
} else {
let jsonResult = (try! NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)) as! NSDictionary
print(jsonResult)
}
})
task.resume()
}
It allows me to process the JSON format into a dictionary that I can access but what I need (I think) would be maybe a dictionary of arrays and I can't manage to make it work with the JSON format that I have bellow.
[
{
"Description": "Juice",
"Energy(kcal)Per 100 g": 29,
},
{
"Description": "Alcoholic beverage, daiquiri, canned",
"Energy(kcal)Per 100 g": 125,
}
...
]
I admit my question wasn't quite clear at first (I'm really new at this I apologize) but I actually tried to research it on Stackoverflow before posting, but I haven't find something that works for my case. Sorry again and many thank you for taking the time to still answer it :)
Have a look into NSJSONSerialization. That is what you get for free once installed xcode and the SDK. And it is not that bad actually.
This is Ray's approach to Swifty Json:
http://www.raywenderlich.com/82706/working-with-json-in-swift-tutorial
This is what you find when you use the search. You will have to "translate" it to swift though.
How do I parse JSON with Objective-C?
You may want to look at RestKit for some more convenient way of dealing with JSON sources.
Give it a try. And when you run into concrete problems, then get back to SO.
Just give it a try
var arrDicts: [Dictionary<String, AnyObject>] = []
arrDicts = try! NSJSONSerialization.JSONObjectWithData(dataFromService!, options: NSJSONReadingOptions.AllowFragments) as! [Dictionary<String, AnyObject>]
dataFromService is the data that you have received from web service.
Answer for reference pruposes. How to do this in Objective-C
1- First get the info
a) If you are getting the JSON from an API or any online site:
//Set url of the JSON
NString *urlReq = #"http://www.URLHERE.com/PATH/TO/THE/JSON"
//Get data from the JSON
NSData *jsonData = [NSData dataWithContentsOfURL:[NSURL URLWithString:urlReq]];
//Parse JSON
if(jsonData != nil){ //If the response is nil, the next line will crash
NSArray *resultArray = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:nil];
//Do stuff with the result...
}
b) If you are getting the information from the Core Data:
//Get context
NSManagedObjectContext *context = [self managedObjectContext];
//Preapre your fetch
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"Products" inManagedObjectContext:context];
NSFetchRequest *requestCoreData = [[NSFetchRequest alloc] init];
[requestCoreData setEntity:entityDescription];
//add predicates if needed
//Execute fetch
NSArray *resultArray = [context executeFetchRequest:requestCoreData error:nil];
//do stuff with the result....
2- Then parse the retrieved info
a) If you want a specific index:
NSString *description = resultArray[0][#"description"];
b) if you don't know what is the index you want (most likely what happens in your JSON):
BOOL found = NO;
int index = 0;
while(index < [resultArray count] && !found){
if([resultArray[index][#"description"] isEqualToString:#"Juice"])
found = YES;
else
++index;
}
if(found){
//'index' => index where the info you are searching can be found
}
else{
//The info couldn't be found in the array
}
There are quite many questions about this kind of question, but I can't find any for CoreData. The problem is: I want to update a large amount of NSManagedObject (you can think of resetting a property's of a large amount of NSManagedObject).
Right now, all I can think of is:
Fetch all object.
Loop through all of it, using forin-loop.
Setting the property in each for block.
The data might be large, and I also want to write a Utility for this action. So the question is:
Is there any more efficient way to perform this? I don't believe using for-loop is efficent.
BONUS QUESTION
Also, I would like to delete all object that satisfied a condition (most likely a boolean flag). My solution is rather simple like the one above:
Fetch all object, with NSPredicate and condition.
Loop through all, forin.
Delete each one of it.
Same question for solution.
The real problem
The real problem is, I want to set all the object's flag (call it willDelete) = YES. Then synchronize from server, then update them and set willDelete = NO. After that, whichever has willDelete = YES would be delete from context.
EDIT 1
My question might be different this one. I want to update the property first. And I care about performance time, not the memory.
EDIT 2
Okay, I managed to use NSBatchUpdateRequest. But the problem is: I got nsmergeConflict. Not sure what to do with this progress. Here's the code anyway:
- (void)resetProductUpdatedStatus
{
NSBatchUpdateRequest *request = [NSBatchUpdateRequest batchUpdateRequestWithEntityName:NSStringFromClass([Product class])];
request.propertiesToUpdate = #{#"hasUpdated" : #(NO)};
request.resultType = NSUpdatedObjectIDsResultType;
NSBatchUpdateResult *result = (NSBatchUpdateResult *)[[CoreDataUtil managedObjectContext] executeRequest:request error:nil];
[result.result enumerateObjectsUsingBlock:^(NSManagedObjectID *objId, NSUInteger idx, BOOL *stop) {
NSManagedObject *obj = [[CoreDataUtil managedObjectContext] objectWithID:objId];
if (!obj.isFault) {
[[CoreDataUtil managedObjectContext] refreshObject:obj mergeChanges:YES];
}
}];
}
This will set all hasUpdated = NO. Next, I'll perform the sync progress. With all the products caught from the synchronization will update the hasUpdated = YES. Next perform delete:
- (void)updateProductActiveStatus
{
NSBatchUpdateRequest *request = [NSBatchUpdateRequest batchUpdateRequestWithEntityName:NSStringFromClass([Product class])];
request.predicate = [NSPredicate predicateWithFormat:#"hasUpdated = NO"];
request.propertiesToUpdate = #{#"isActive" : #(NO)};
request.resultType = NSUpdatedObjectIDsResultType;
[[CoreDataUtil managedObjectContext] executeRequest:request error:nil];
}
As you can see, I've deleted the merge in the update status. So probably, it cause merge conflict in the reset status. So, I guess I will have to fix the merge progress. So I'll ask to people here if you have any idea about this.
Here you can also check the time taken for badge update is quite less than what happens in normal update .As in batchupdate we don't need to fetch the data from file itself. Also check my github link : https://github.com/tapashm42/CoreDataInSwift.git .
I am working on the batchdelete part as well.
func batchUpdate(entityName: String,context:NSManagedObjectContext) {
let batchUpdateRequest = NSBatchUpdateRequest(entityName: entityName)
batchUpdateRequest.propertiesToUpdate = ["name":"Tapash Mollick","designation":"Associate IT Consultant"]
batchUpdateRequest.resultType = .updatedObjectIDsResultType
do {
let start = Date().currentTimeMillis
let batchUpdateResult = try context.execute(batchUpdateRequest) as! NSBatchUpdateResult
let result = batchUpdateResult.result as! [NSManagedObjectID]
print("time taken to update\(Date().currentTimeMillis - start)")
for objectId in result {
let manageObject = context.object(with: objectId)
if (!manageObject.isFault) {
context.stalenessInterval = 0
context.refresh(manageObject, mergeChanges: true)
}
}
}
catch{
fatalError("Unable to batchUpdate")
}
}
extension Date{
var currentTimeMillis: Int64 {
return Int64(Date().timeIntervalSince1970 * 1000)
}
}
I have a Message/RLMObject model that has a NSString *jabberID property/row and I want to retrieve every unique value inside that row.
In other word, I want to retrieve non-repeated jabberID values from my Message model. Can anyone help out figuring this?
The way I use to do with coredata was using returnsDistinctResults setting on the NSFetchRequest.
Functional programming approach since Swift has it, and Realm lazy loads; Not as easy/available a solution in Objective-C but for Swift at least:
Swift
let distinctTypes = reduce(Realm().objects(User), []) { $0 + (!contains($0, $1.type) ? [$1.type] : [] ) }
UPDATED:
Swift reduce is kind of a performance intensive, allocating a bunch of intermediate array's, instead the following should be much better performance wise, but must be explicitly cast
let distinctTypes = Array(Set(Realm().objects(User).valueForKey("type") as! [String]))
I found out Realm doesn't fully support distinct queries yet. The good news is I also found a workaround for it, on this github issue.
Objective-c
RLMResults *messages = [Message allObjects];
NSMutableArray *uniqueIDs = [[NSMutableArray alloc] init];
NSMutableArray *uniqueMessages = [[NSMutableArray alloc] init];
for (Message *msg in messages) {
NSString *jabberID = msg.jabberID;
Message *uniqueMSG = (Message *)msg;
if (![uniqueIDs containsObject:jabberID]) {
[uniqueMessages addObject:uniqueMSG];
[uniqueIDs addObject:jabberID];
}
}
Swift 3.0
let realm = try! Realm()
let distinctIDs = Set(realm.objects(Message.self).value(forKey: "jabberID") as! [String])
var distinctMessages = [Message]()
for jabberID in distinctIDs {
if let message = realm.objects(Message.self).filter("jabberID = '\(jabberID)'").first {
distinctMessages.append(message)
}
}
I am trying to make a leaderboard using GameCenter on iOS7 that shows only scores submitted in the last week.
I am aware that with iOS7 the use of timescope when opening the gamecenter view controller has been deprecated, but every post I find on this suggests building a leaderboard within the game to get around this.
We have our own leaderboards implemented in game which we fetch using loadScoresWithCompletionHandler, but the timeScope settings still does not appear to work. What ever I set it to, I receive the best score of all time for the user, not the best sore in the last day / week.
Has anyone else found this or does it work for everyone else? I confirm the score is wrong be looking at the "date" on any received GKScores.
The code in question is below:
const char* szName("LeaderboardName_Week1");
NSString* pxLeaderboardName = [NSString stringWithUTF8String:szName];
GKLeaderboardTimeScope eTimeScope = GKLeaderboardTimeScopeWeek;
GKLeaderboard* leaderboardViewer = [[[GKLeaderboard alloc] init] autorelease];
if (leaderboardViewer)
{
[leaderboardViewer setIdentifier: pxLeaderboardName];
[leaderboardViewer setTimeScope: eTimeScope];
[leaderboardViewer setRange: NSMakeRange(1, 100)];
[leaderboardViewer setPlayerScope: (bFriendsOnly)?GKLeaderboardPlayerScopeFriendsOnly:GKLeaderboardPlayerScopeGlobal];
[leaderboardViewer loadScoresWithCompletionHandler:^(NSArray* scores, NSError* error)
{
if (error || !scores)
{
NSLog(#"GameKit Score retrieval Error:\n%#", error);
}
else
{
NSLog(#"GameKit Score retrieval complete, %d scores. Retrieving player names.", (u_int)scores.count);
GKScore* playerScore = leaderboardViewer.localPlayerScore;
u_int uIndex2;
for (uIndex2 = 0; uIndex2 < scores.count; ++uIndex2)
{
GKScore* score = [scores objectAtIndex:uIndex2];
//score.date is out of the requested range at this point.
xEntry.m_uRank = static_cast<u_int> (score.rank);
xEntry.m_lScore = score.value;
xEntry.m_uContext = score.context;
}
}
}];
}
I am doing all this in the sand box. Any help appreciated!
I'm trying to get the nearby places using the foursquare api.
Here's the json data that is returned from
NSDictionary *results = [jsonString JSONValue];
NSLog(#"%#", results);
(
{
code = 200;
errorDetail = "This endpoint will stop returning groups in the future. Please use a current version, see http://bit.ly/lZx3NU.";
errorType = deprecated;
},
{
groups = (
{
items = (
{
categories = (
{
icon = "https://foursquare.com/img/categories/parks_outdoors/default.png";
id = 4bf58dd8d48988d163941735;
name = Park;
parents = (
"Great Outdoors"
);
pluralName = Parks;
primary = 1;
shortName = Park;
}
);
Then I try to get the list of the groups in an array with
NSArray *groups = [ (NSDictionary *)results objectForKey:#"groups"];
This returns the following error
2011-11-05 11:42:12.907 XperienzApp[1972:207] No of results returned: 0 Results : (null)
2011-11-05 11:42:13.225 XperienzApp[1972:207] -JSONValue failed. Error trace is: (
"Error Domain=org.brautaset.JSON.ErrorDomain Code=3 \"Unrecognised leading character\" UserInfo=0x5849cd0 {NSLocalizedDescription=Unrecognised leading character}"
)
2011-11-05 11:42:13.225 XperienzApp[1972:207] No of results returned: 0 Results : (null)
How should I parse this?
Edit:
I tried the suggested technique, this gives me an array
id groups = [[(NSDictionary *)results objectForKey:#"response"] objectForKey:#"groups"];
if ([results count] > 1){
NSLog(#"groups class %#\ngroups %# %d", groups, [groups class], [groups count]);
The log output is of the form:
{
categories = (
{
icon = "https://foursquare.com/img/categories/nightlife/danceparty.png";
id = 4bf58dd8d48988d11f941735;
name = Nightclub;
parents = (
"Nightlife Spots"
);
pluralName = Nightclubs;
primary = 1;
shortName = Nightclub;
}
);
contact = {
};
hereNow = {
count = 0;
};
id = 4eb33ba561af0dda8f673c1b;
location = {
address = "144 Willow St 4R";
city = Brooklyn;
crossStreet = Pierrepont;
distance = 462;
lat = "40.696864";
lng = "-73.996409";
postalCode = 11201;
state = NY;
};
name = "Entertainment 720, Ltd.";
stats = {
checkinsCount = 3;
tipCount = 0;
usersCount = 1;
};
verified = 0;
}
);
name = Nearby;
type = nearby;
}
)
groups __NSArrayM 1
This is again not json and is hard to parse, how do I get the output in json.
I'm the iPhone lead at foursquare. I'll try to take a stab at what's going on here.
First of all, I highly recommend you use JSONKit for your parser. It's lightweight and insanely fast: https://github.com/johnezang/JSONKit
It appears that you are parsing the JSON properly and getting the dictionary properly. Then you are logging the parsed object, not the original JSON. The output you are seeing is how Objective-C chooses to serialize the parsed dictionary to text. It is definitely not JSON. Using JSONKit, you could send the JSONString selector to your parsed result and convert it back to JSON and log that.
If you could provide some details on the problem you are trying to solve, I might be able to help you out more. And as Maudicus said, please pay attention to the error you are getting back. You don't want your app to break when we make the change to the API.
If the output below NSLog(#"%#", results); is your log statement. It appears your results variable is an array of dictionary objects.
Try to log the class of results to verify that NSLog(#"%#", [results class]);
If it is an array your groups object is the second object.
if ([results count] > 1)
id groups = [results objectAtIndex:1];
NSLog(#"groups class %#\ngroups %#", [groups class], groups);
Keep doing this until you understand the format of your data
Also the line
errorDetail = "This endpoint will stop returning groups in the future. Please use a current version, see http://bit.ly/lZx3NU.";
should be cause for concern. Check the documentation on foursquare for the current way of getting groups.