├── Pod ├── Assets │ └── .gitkeep └── Classes │ ├── .gitkeep │ ├── OMHError.m │ ├── OMHError.h │ ├── NSDate+RFC3339.m │ ├── OMHSerializer.h │ ├── NSDate+RFC3339.h │ ├── OMHHealthKitConstantsMapper.h │ ├── OMHHealthKitConstantsMapper.m │ └── OMHSerializer.m ├── Example ├── Tests │ ├── en.lproj │ │ └── InfoPlist.strings │ ├── Tests-Prefix.pch │ ├── Tests-Info.plist │ ├── OMHSampleFactory.h │ ├── OMHSchemaStore.h │ ├── HKObject+Private.h │ ├── NSDate+RFC3339Tests.m │ ├── OMHSchemaStore.m │ └── OMHSampleFactory.m ├── Podfile ├── Granola.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── Podfile.lock └── Granola.xcodeproj │ ├── xcshareddata │ └── xcschemes │ │ └── Granola-Example.xcscheme │ └── project.pbxproj ├── .gitmodules ├── .travis.yml ├── .gitignore ├── Granola.podspec ├── README.md ├── LICENSE └── Docs └── hkobject_type_coverage.md /Pod/Assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Pod/Classes/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Example/Tests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Example/Tests/omh-schemas"] 2 | path = Example/Tests/omh-schemas 3 | url = https://github.com/openmhealth/schemas.git 4 | branch = master -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/CocoaPods/Specs.git' 2 | 3 | target 'Granola' do 4 | pod "Granola", :path => "../" 5 | 6 | pod 'Specta', '~> 1.0' 7 | pod 'Expecta', '~> 1.0' 8 | pod 'VVJSONSchemaValidation', '~> 1.3' 9 | end 10 | 11 | -------------------------------------------------------------------------------- /Example/Granola.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/Granola.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/Tests/Tests-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every test case source file. 5 | // 6 | 7 | #ifdef __OBJC__ 8 | 9 | #define EXP_SHORTHAND 10 | #import 11 | #import 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # references: 2 | # * http://www.objc.io/issue-6/travis-ci.html 3 | # * https://github.com/supermarin/xcpretty#usage 4 | 5 | language: objective-c 6 | # cache: cocoapods 7 | # podfile: Example/Podfile 8 | before_install: 9 | - gem install cocoapods # Since Travis is not always on latest version 10 | - pod install --project-directory=Example 11 | install: 12 | - gem install xcpretty --no-rdoc --no-ri --no-document --quiet 13 | script: 14 | - set -o pipefail && xcodebuild test -workspace Example/HealthKitIO.xcworkspace -scheme HealthKitIO-Example -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO | xcpretty -c 15 | - pod lib lint --quick 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | 22 | # Bundler 23 | .bundle 24 | 25 | # We recommend against adding the Pods directory to your .gitignore. However 26 | # you should judge for yourself, the pros and cons are mentioned at: 27 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 28 | # 29 | # Note: if you ignore the Pods directory, make sure to uncomment 30 | # `pod install` in .travis.yml 31 | # 32 | Pods/ 33 | -------------------------------------------------------------------------------- /Pod/Classes/OMHError.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Open mHealth 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #import "OMHError.h" 18 | 19 | NSString *const OMHErrorDomain = @"org.openmhealth.Granola"; 20 | 21 | -------------------------------------------------------------------------------- /Example/Tests/Tests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Example/Tests/OMHSampleFactory.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Open mHealth 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #import 18 | @import HealthKit; 19 | 20 | @interface OMHSampleFactory : NSObject 21 | + (HKSample*)typeIdentifier:(NSString*)typeIdentifier attrs:(NSDictionary*)attrs; 22 | + (HKSample*)typeIdentifier:(NSString*)typeIdentifier; 23 | @end 24 | 25 | -------------------------------------------------------------------------------- /Example/Tests/OMHSchemaStore.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Open mHealth 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #import 18 | 19 | @interface OMHSchemaStore : NSObject 20 | + (BOOL)validateObject:(id)object 21 | againstSchemaAtPath:(NSString*)path 22 | withError:(NSError**)validationError; 23 | + (NSArray*) schemaPartialPaths; 24 | 25 | @end 26 | 27 | -------------------------------------------------------------------------------- /Example/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Expecta (1.0.5) 3 | - Granola (0.6.0): 4 | - ObjectiveSugar (~> 1.1) 5 | - ObjectiveSugar (1.1.0) 6 | - Specta (1.0.5) 7 | - VVJSONSchemaValidation (1.4.0) 8 | 9 | DEPENDENCIES: 10 | - Expecta (~> 1.0) 11 | - Granola (from `../`) 12 | - Specta (~> 1.0) 13 | - VVJSONSchemaValidation (~> 1.3) 14 | 15 | SPEC REPOS: 16 | https://github.com/CocoaPods/Specs.git: 17 | - Expecta 18 | - ObjectiveSugar 19 | - Specta 20 | - VVJSONSchemaValidation 21 | 22 | EXTERNAL SOURCES: 23 | Granola: 24 | :path: "../" 25 | 26 | SPEC CHECKSUMS: 27 | Expecta: e1c022fcd33910b6be89c291d2775b3fe27a89fe 28 | Granola: 45b768c2c454eb3dc91c6b402b291437736892c3 29 | ObjectiveSugar: a6a25f23d657c19df0a0b972466d5b5ca9f5295c 30 | Specta: ac94d110b865115fe60ff2c6d7281053c6f8e8a2 31 | VVJSONSchemaValidation: 11f5ba886e80baccae5d606f3b10f20c1303200d 32 | 33 | PODFILE CHECKSUM: abd6914286eceb417a0fbf867e2c1826546083a7 34 | 35 | COCOAPODS: 1.8.4 36 | -------------------------------------------------------------------------------- /Example/Tests/HKObject+Private.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Open mHealth 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #import 18 | @import HealthKit; 19 | 20 | @interface HKObject (Private) 21 | 22 | // source: 23 | // https://github.com/LeoNatan/Apple-Runtime-Headers/blob/master/iOS/Frameworks/HealthKit.framework/HKObject.h 24 | - (BOOL)_validateForSavingWithClientEntitlements:(id)arg1 applicationSDKVersion:(unsigned int)arg2 error:(id *)arg3; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /Granola.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "Granola" 3 | s.version = "0.6.0" 4 | s.summary = "A healthful serializer for your HealthKit data." 5 | s.homepage = "https://github.com/openmhealth/Granola" 6 | s.license = { :type => 'Apache 2.0', 7 | :file => 'LICENSE' } 8 | s.authors = { "Brent Hargrave" => "brent@brent.is", 9 | "Chris Schaefbauer" => "chris.schaefbauer@openmhealth.org", 10 | "Emerson Farrugia" => "emerson@openmhealth.org", 11 | "Simona Carini" => "simona@openmhealth.org" } 12 | s.source = { :git => "https://github.com/openmhealth/Granola.git", 13 | :tag => s.version.to_s } 14 | s.social_media_url = 'https://twitter.com/openmhealth' 15 | s.platform = :ios, '11.0' 16 | s.requires_arc = true 17 | s.source_files = 'Pod/Classes/**/*' 18 | s.frameworks = 'HealthKit' 19 | s.dependency 'ObjectiveSugar', '~> 1.1' 20 | end 21 | 22 | -------------------------------------------------------------------------------- /Pod/Classes/OMHError.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Open mHealth 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #import 18 | 19 | FOUNDATION_EXPORT NSString *const OMHErrorDomain; 20 | 21 | /** 22 | Error types for Granola used to identify different problematic conditions within the library. 23 | */ 24 | typedef NS_ENUM(NSInteger, OMHErrorCode) { 25 | 26 | /** Indicates that the sample type is a valid type, but not currently supported by Granola.*/ 27 | OMHErrorCodeUnsupportedType = 1000, 28 | 29 | /** Indicates that the input value is not supported by the specific method in Granola.*/ 30 | OMHErrorCodeUnsupportedValues = 1001, 31 | 32 | /** Indicates that the sample type is the incorrect type for the specific method or serializer.*/ 33 | OMHErrorCodeIncorrectType = 1002 34 | }; 35 | 36 | -------------------------------------------------------------------------------- /Pod/Classes/NSDate+RFC3339.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Open mHealth 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #import "NSDate+RFC3339.h" 18 | 19 | @implementation NSDate (RFC3339) 20 | 21 | + (NSDateFormatter*)RFC3339Formatter:(NSTimeZone*)timeZone { 22 | 23 | NSDateFormatter* formatter = [[NSDateFormatter alloc] init]; 24 | 25 | NSLocale* locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; 26 | 27 | formatter.timeZone = timeZone; 28 | formatter.locale = locale; 29 | formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSSXXX"; 30 | 31 | return formatter; 32 | } 33 | 34 | - (NSString *)RFC3339String { 35 | return [[[self class] RFC3339Formatter:[NSTimeZone defaultTimeZone]] stringFromDate:self]; 36 | } 37 | 38 | - (NSString *)RFC3339StringAtTimeZone:(NSTimeZone*)timeZone { 39 | return [[[self class] RFC3339Formatter:timeZone] stringFromDate:self]; 40 | } 41 | 42 | + (NSDate*)fromRFC3339String:(NSString*)dateString { 43 | return [[self RFC3339Formatter:[NSTimeZone defaultTimeZone]] dateFromString:dateString]; 44 | } 45 | 46 | @end 47 | 48 | -------------------------------------------------------------------------------- /Pod/Classes/OMHSerializer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Open mHealth 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #import 18 | @import HealthKit; 19 | #import "OMHError.h" 20 | 21 | /** 22 | Translates HealthKit samples of varying types into JSON representations that conform with Open mHealth schemas. 23 | */ 24 | @interface OMHSerializer : NSObject 25 | 26 | /** 27 | Returns a list of the HealthKit type identifiers that can be serialized to Open mHealth curated schemas. These are schemas that are not specific to Granola and are consistent with data points generated across the Open mHealth ecosystem. 28 | 29 | @return A list of the HealthKit type identifiers serializable to Open mHealth curated schemas. 30 | */ 31 | + (NSArray*)supportedTypeIdentifiersWithOMHSchema; 32 | 33 | /** 34 | Lists all of the HealthKit type identifiers that are supported by Granola, regardless of whether they use Open mHealth curated schemas or Granola-based generic schemas. 35 | 36 | @return A list of all HealthKit type identifiers that are supported by Granola. 37 | */ 38 | + (NSArray*)supportedTypeIdentifiers; 39 | 40 | + (NSString*)getCategoryValueForTypeWithValue: (HKCategoryType*) categoryType categoryValue:(NSInteger)categoryValue; 41 | 42 | /** 43 | Serializes HealthKit samples into Open mHealth compliant JSON data points. 44 | @param sample The HealthKit sample to be serialized. 45 | @param error An NSError that is passed by reference and can be checked to identify specific errors. 46 | @return A formatted JSON string containing the sample's data in a format that adheres to the appropriate Open mHealth schema. 47 | */ 48 | - (NSString*)jsonForSample:(HKSample*)sample error:(NSError**)error; 49 | 50 | @end 51 | 52 | -------------------------------------------------------------------------------- /Pod/Classes/NSDate+RFC3339.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Open mHealth 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #import 18 | 19 | /** 20 | Extension for the `NSDate` class to provide support for RFC3339 formatting. 21 | 22 | @warning Due to the limited precision of the `NSDate` class, we can only support millsecond-level precision for RFC3339 timestamps. In translating an `NSDate` to an RFC3339 string or vice versa, values with greater than millisecond-level precision may lose fidelity and have values that deviate from the expected value by a number of microseconds or nanoseconds. 23 | */ 24 | @interface NSDate (RFC3339) 25 | 26 | /** 27 | Generates an RFC3339 formatted string representation of the object. This method uses the default timezone in generating the RFC3339 timestamp. 28 | 29 | @return An RFC3339 formatted string representation of the `NSDate` object. 30 | */ 31 | - (NSString *)RFC3339String; 32 | 33 | /** 34 | Generates an RFC3339 formatted string representation of the object. This method uses the `timeZone` parameter as the offset for the RFC3339 timestamp. 35 | 36 | @param timeZone The timezone to use in rendering the RFC3339 timestamp. 37 | 38 | @return An RFC3339 formatted string representation of the `NSDate` object. 39 | */ 40 | - (NSString *)RFC3339StringAtTimeZone:(NSTimeZone*)timeZone; 41 | 42 | /** 43 | Static method to create an `NSDate` object from an RFC3339 formatted timestamp. 44 | 45 | @param dateString The RFC3339 formatted string that you want to represent as an `NSDate` object. 46 | 47 | @return An `NSDate` that refers to the same _moment in time_ that the input string represented. 48 | */ 49 | + (NSDate*)fromRFC3339String:(NSString*)dateString; 50 | 51 | @end 52 | 53 | -------------------------------------------------------------------------------- /Example/Granola.xcodeproj/xcshareddata/xcschemes/Granola-Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /Example/Tests/NSDate+RFC3339Tests.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Open mHealth 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | #import 19 | #import "NSDate+RFC3339.h" 20 | 21 | SpecBegin(NSDate) 22 | 23 | __block NSInteger offsetHours; 24 | __block NSCalendar* calendar; 25 | __block NSDateComponents* dateBuilder; 26 | __block float offsetNumber; 27 | __block float hour; 28 | 29 | beforeAll(^{ 30 | offsetNumber = [[NSTimeZone defaultTimeZone] secondsFromGMT] / 3600; 31 | 32 | calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar]; 33 | 34 | hour = 60*60; 35 | offsetHours = 6*hour; // +06:00 offset 36 | dateBuilder = [NSDateComponents new]; 37 | 38 | dateBuilder.year = 2015; 39 | dateBuilder.day = 28; 40 | dateBuilder.month = 6; 41 | dateBuilder.hour = 8; 42 | dateBuilder.minute = 6; 43 | dateBuilder.second = 9; 44 | dateBuilder.nanosecond = 100000000; 45 | 46 | }); 47 | 48 | describe(@"NSDate RFC3339 Formatter RFC3339String", ^{ 49 | 50 | it(@"should create timestamps with default time zone when time zone is not provided",^{ 51 | 52 | NSString* expectedDateString = @"2015-06-28T09:06:09.100+06:00"; 53 | 54 | dateBuilder.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:(offsetHours-(1*hour))]; 55 | 56 | NSDate* date = [calendar dateFromComponents:dateBuilder]; 57 | 58 | [NSTimeZone setDefaultTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:offsetHours]]; 59 | 60 | NSString* testDateString = [date RFC3339String]; 61 | expect(testDateString).to.equal(expectedDateString); 62 | }); 63 | 64 | it(@"should create timestamps with correct offset when date is created in UTC and time zone is provided",^{ 65 | 66 | NSString* expectedDateString = @"2015-06-28T14:06:09.100+06:00"; 67 | 68 | dateBuilder.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"UTC"]; 69 | 70 | NSDate* date = [calendar dateFromComponents:dateBuilder]; 71 | NSString* testDateString = [date RFC3339StringAtTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:offsetHours]]; 72 | expect(testDateString).to.equal(expectedDateString); 73 | }); 74 | 75 | it(@"should create timestamps with correct offset when date is created in offset from UTC and time zone provided",^{ 76 | 77 | NSString* expectedDateString = @"2015-06-28T08:06:09.100+06:00"; 78 | 79 | NSTimeZone* timezone = [NSTimeZone timeZoneForSecondsFromGMT:offsetHours]; 80 | 81 | dateBuilder.timeZone = timezone; 82 | 83 | NSDate* date = [calendar dateFromComponents:dateBuilder]; 84 | NSString* testDateString = [date RFC3339StringAtTimeZone:timezone]; 85 | expect(testDateString).to.equal(expectedDateString); 86 | }); 87 | 88 | it(@"should create timestamps with correct fractional offset when date is created in offset from UTC and fractional time zone provided", 89 | ^{ 90 | NSString* expectedDateString = @"2015-06-28T13:36:09.100+05:30"; 91 | 92 | dateBuilder.timeZone = [NSTimeZone timeZoneWithAbbreviation: @"UTC"]; 93 | 94 | // +05:30 with not DST 95 | NSTimeZone* timezone = [NSTimeZone timeZoneWithName:@"Asia/Kolkata"]; 96 | 97 | NSDate* date = [calendar dateFromComponents:dateBuilder]; 98 | NSString* testDateString = [date RFC3339StringAtTimeZone:timezone]; 99 | expect(testDateString).to.equal(expectedDateString); 100 | } 101 | ); 102 | 103 | }); 104 | 105 | describe(@"NSDate RFC3339 Formatter fromRFC3339String", ^{ 106 | 107 | it(@"should create correct date when at second level precision", ^{ 108 | 109 | NSString* testDateString = @"2016-02-19T05:35:10.000Z"; 110 | 111 | NSDate* expectedDate = [NSDate dateWithTimeIntervalSince1970:[@1455860110 doubleValue]]; 112 | 113 | NSDate* testDate = [NSDate fromRFC3339String:testDateString]; 114 | 115 | expect(testDate).to.equal(expectedDate); 116 | }); 117 | 118 | it(@"should create correct date at millisecond level precision", ^{ 119 | 120 | NSString* testDateString = @"2016-02-19T05:35:10.282Z"; 121 | 122 | NSDate* expectedDate = [NSDate dateWithTimeIntervalSince1970:[@1455860110.282 doubleValue]]; 123 | 124 | NSDate* testDate = [NSDate fromRFC3339String:testDateString]; 125 | 126 | expect(testDate).to.equal(expectedDate); 127 | }); 128 | 129 | it(@"should create correct date when string contains non-UTC offset", ^{ 130 | 131 | NSString* testDateString = @"2016-02-19T12:35:10.282+07:00"; 132 | 133 | NSDate* expectedDate = [NSDate dateWithTimeIntervalSince1970:[@1455860110.282 doubleValue]]; 134 | 135 | NSDate* testDate = [NSDate fromRFC3339String:testDateString]; 136 | 137 | expect(testDate).to.equal(expectedDate); 138 | }); 139 | }); 140 | 141 | SpecEnd 142 | 143 | -------------------------------------------------------------------------------- /Example/Tests/OMHSchemaStore.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Open mHealth 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #import "OMHSchemaStore.h" 18 | #import 19 | #import 20 | 21 | @interface OMHSchemaStore() 22 | @property (nonatomic, retain) VVMutableJSONSchemaStorage* storage; 23 | @end 24 | 25 | @implementation OMHSchemaStore 26 | 27 | + (BOOL)validateObject:(id)object 28 | againstSchemaAtPath:(NSString*)path 29 | withError:(NSError**)validationError { 30 | VVJSONSchema* schema = [[self sharedStore] schemaForPartialPath:path]; 31 | return [schema validateObject:object withError:validationError]; 32 | } 33 | 34 | #pragma mark - Private 35 | 36 | + (id)sharedStore { 37 | static OMHSchemaStore *shared = nil; 38 | static dispatch_once_t onceToken; 39 | dispatch_once(&onceToken, ^{ 40 | shared = [[self alloc] init]; 41 | if (shared) [shared loadSchemas]; 42 | }); 43 | return shared; 44 | } 45 | 46 | - (void)loadSchemas { 47 | _storage = [VVMutableJSONSchemaStorage storage]; 48 | [[OMHSchemaStore schemaPartialPaths] 49 | each:^(NSString* partialPath) { 50 | NSURL* schemaURI = [self schemaURIForPartialPath:partialPath]; 51 | // add schema at URI 52 | NSError *error = nil; 53 | VVJSONSchema* schema = 54 | [VVJSONSchema schemaWithData:[NSData dataWithContentsOfURL:schemaURI] 55 | baseURI:schemaURI 56 | referenceStorage:_storage 57 | error:&error]; 58 | NSAssert(schema, 59 | @"Failed to create schema: %@, error: %@", schemaURI, error); 60 | NSAssert([_storage addSchema:schema], 61 | @"Failed to add schema to storage: %@", schemaURI); 62 | }]; 63 | } 64 | 65 | - (VVJSONSchema*)schemaForPartialPath:(NSString*)path { 66 | NSURL* schemaURI = [self schemaURIForPartialPath:path]; 67 | return [_storage schemaForURI:schemaURI]; 68 | } 69 | 70 | - (NSURL*)schemaURIForPartialPath:(NSString*)fname { 71 | NSArray* components = [fname componentsSeparatedByString:@"/"]; 72 | NSString* dirname = 73 | [NSString stringWithFormat:@"schema/%@", components.firstObject]; 74 | NSBundle* bundle = [NSBundle bundleForClass:[self class]]; 75 | NSURL* schemaURI = 76 | [bundle URLForResource:components.lastObject 77 | withExtension:@"json" 78 | subdirectory:dirname]; 79 | NSAssert(schemaURI, @"No schema %@ in %@", components.lastObject, dirname); 80 | return schemaURI; 81 | } 82 | 83 | + (NSArray*)schemaPartialPaths{ 84 | static NSArray* schemaPartialPaths = nil; 85 | 86 | if (schemaPartialPaths == nil) 87 | { 88 | schemaPartialPaths = @[ 89 | @"omh/schema-id-1.x", 90 | @"omh/date-time-1.x", 91 | @"omh/unit-value-1.x", 92 | @"omh/duration-unit-value-1.x", 93 | @"omh/part-of-day-1.x", 94 | @"omh/time-interval-1.x", 95 | @"omh/time-frame-1.x", 96 | @"omh/header-1.x", 97 | @"omh/data-point-1.x", 98 | @"omh/step-count-1.x", 99 | @"omh/length-unit-value-1.x", 100 | @"omh/mass-unit-value-1.x", 101 | @"omh/temperature-unit-value-1.x", 102 | @"omh/descriptive-statistic-1.x", 103 | @"omh/body-height-1.x", 104 | @"omh/body-weight-1.x", 105 | @"omh/activity-name-1.x", 106 | @"omh/area-unit-value-1.x", 107 | @"omh/temporal-relationship-to-sleep-1.x", 108 | @"omh/blood-specimen-type-1.x", 109 | @"omh/temporal-relationship-to-meal-1.x", 110 | @"omh/blood-glucose-1.x", 111 | @"omh/position-during-measurement-1.x", 112 | @"omh/systolic-blood-pressure-1.x", 113 | @"omh/diastolic-blood-pressure-1.x", 114 | @"omh/blood-pressure-1.x", 115 | @"omh/temporal-relationship-to-physical-activity-1.x", 116 | @"omh/heart-rate-1.x", 117 | @"omh/body-mass-index-1.x", 118 | @"omh/sleep-duration-1.x", 119 | @"omh/physical-activity-1.x", 120 | @"omh/kcal-unit-value-1.x", 121 | @"omh/calories-burned-1.x", 122 | @"omh/body-fat-percentage-1.x", 123 | @"omh/oxygen-saturation-1.x", 124 | @"omh/respiratory-rate-1.x", 125 | @"omh/body-temperature-2.x", 126 | @"granola/hk-metadata-1.x", 127 | @"granola/hk-quantity-type-1.x", 128 | @"granola/hk-quantity-sample-1.x", 129 | @"granola/hk-category-type-1.x", 130 | @"granola/hk-category-sample-1.x", 131 | @"granola/hk-correlation-type-1.x", 132 | @"granola/hk-correlation-1.x", 133 | @"granola/hk-workout-1.x"]; 134 | } 135 | 136 | return schemaPartialPaths; 137 | } 138 | 139 | 140 | 141 | @end 142 | 143 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Granola 2 | 3 | _*A healthful serializer for your HealthKit data.*_ 4 | 5 | ## Overview 6 | 7 | So you want to store your app's [HealthKit](https://developer.apple.com/healthkit/) 8 | data somewhere *outside* of HealthKit, perhaps a remote server for analysis or 9 | backup? Use Granola to serialize your data. 10 | 11 | Granola spares you the effort of mapping HealthKit's API to JSON yourself, 12 | and emits JSON that validates against 13 | [schemas developed by Open mHealth](http://www.openmhealth.org/developers/schemas/) 14 | to ensure the data is intuitive and clinically meaningful. 15 | 16 | 17 | *** 18 | 19 | ## Installation 20 | 21 | ### CocoaPods 22 | 23 | Granola is available through [CocoaPods](http://cocoapods.org), a dependency manager for Objective-C. If you don't already have CocoaPods installed, you can install it with the following command: 24 | ```ruby 25 | $ gem install cocoapods 26 | ``` 27 | 28 | To integrate Granola with your Xcode project, simply add the following line to your `Podfile`: 29 | ```ruby 30 | pod "Granola" 31 | ``` 32 | 33 | and then run the following command: 34 | ```ruby 35 | $ pod install 36 | ``` 37 | 38 | *** 39 | 40 | ## Usage 41 | 42 | ### Quick start 43 | 44 | First, be sure to study Apple's 45 | [HealthKit Framework Reference](https://developer.apple.com/library/ios/documentation/HealthKit/Reference/HealthKit_Framework), 46 | which includes code samples illustrating how to ask your app's user for 47 | permission to access their HealthKit data, and how to query that data after 48 | you've gained permission. 49 | 50 | Now, let's say you want to see what a "steps" sample data point looks like 51 | serialized to JSON. 52 | 53 | ```objective-c 54 | // Granola includes OMHSerializer for serializing HealthKit data 55 | #import "OMHSerializer.h" 56 | 57 | // (initialize your HKHealthStore instance, request permissions with it) 58 | // ... 59 | 60 | // create a query for steps data 61 | HKSampleType *stepsSampleType = 62 | [HKSampleType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount]; 63 | HKSampleQuery* query = 64 | [[HKSampleQuery alloc] initWithSampleType:stepsSampleType 65 | predicate:nil 66 | limit:HKObjectQueryNoLimit 67 | sortDescriptors:nil 68 | resultsHandler:^(HKSampleQuery *query, 69 | NSArray *results, 70 | NSError *error) { 71 | if (!results) abort(); 72 | // pick a sample to serialize 73 | HKQuantitySample *sample = [results first]; 74 | // create and use a serializer instance 75 | OMHSerializer *serializer = [OMHSerializer new]; 76 | NSString* jsonString = [serializer jsonForSample:sample error:nil]; 77 | NSLog(@"sample json: %@", jsonString); 78 | }]; 79 | // run the query with your authorized HKHealthStore instance 80 | [self.healthStore executeQuery:query]; 81 | ``` 82 | 83 | Upon running your code, the console would render the data sample as Open mHealth compliant JSON: 84 | 85 | ```json 86 | { 87 | "body" : { 88 | "step_count" : 45, 89 | "effective_time_frame" : { 90 | "time_interval" : { 91 | "start_date_time" : "2015-05-12T18:58:06.969Z", 92 | "end_date_time" : "2015-05-12T18:58:32.524Z" 93 | } 94 | } 95 | }, 96 | "header" : { 97 | "id" : "4A00E553-B01D-4757-ADD0-A4283BABAC6F", 98 | "creation_date_time" : "2015-05-12T18:58:06.969Z", 99 | "schema_id" : { 100 | "namespace" : "omh", 101 | "name" : "step-count", 102 | "version" : "1.0" 103 | } 104 | } 105 | } 106 | ``` 107 | 108 | ### HKObjectType support 109 | 110 | The serializer has support for all HealthKit samples (`HKSample`), either through curated Open mHealth schemas or through generic HealthKit schemas. You can take a look at the [mapping table of supported types and their associated schemas](Docs/hkobject_type_coverage.md) to understand how data gets mapped. The `HKObjectType` identifiers are pulled from the 111 | [HealthKit Constant Reference](https://developer.apple.com/library/ios/documentation/HealthKit/Reference/HealthKit_Constants/#//apple_ref/doc/uid/TP40014710-CH2-DontLinkElementID_3). 112 | 113 | You can retrieve a map (`NSDictionary`) of the supported types in Granola and the class name of the specific serializer they use by calling the static method: 114 | ```objective-c 115 | [OMHHealthKitConstantsMapper allSupportedTypeIdentifiersToClasses] 116 | ``` 117 | 118 | You can also retrieve a list of those types, without their associated serializers, using the method: 119 | ```objective-c 120 | [OMHSerializer supportedTypeIdentifiers] 121 | ``` 122 | 123 | And retrieve a list of types that serialize with Open mHealth curated schemas (instead of the generic type schemas) by using the method: 124 | ```objective-c 125 | [OMHSerializer supportedTypeIdentifiersWithOMHSchema] 126 | ``` 127 | 128 | Over time, as curated schemas are developed that correspond to the HealthKit data represented by the generic schemas, the generic mappings will be replaced by mappings to the curated schemas. 129 | 130 | [Contact us](#contact) to request support for a particular type or 131 | [contribute](#contributing) support with a pull request. 132 | 133 | ### Time zones 134 | 135 | Granola uses the time zone specified for the `HKMetadataKeyTimeZone` key to serialize timestamps when it is present. If time zone metadata is not provided, Granola uses the default time zone of the application for the UTC offset in timestamps. Although these timestamps are correct, some data, especially older data, that is being serialized may be offset incorrectly if it was originally created by HealthKit in a different time zone than the device is in when Granola serializes it. For example, if a data point was originally created by HealthKit in San Francisco on June 1st, 2015 at 8:00am (-07:00), but then serialized three months later in New York, the timestamp would read 2015-06-01T11:00-04:00. These are technically the same point in time, however they are offset differently. 136 | 137 | In a future update, we plan to allow developers to specify the prefered time zone for serializing data points to give them control over how timestamps are serialized. 138 | 139 | ## Contact 140 | 141 | Have a question? Please [open an issue](https://github.com/openmhealth/Granola/issues/new)! 142 | 143 | Also, feel free to tweet at Open mHealth ([@openmhealth](http://twitter.com/openmhealth)). 144 | 145 | 146 | ## Contributing 147 | 148 | 1. Fork it 149 | 2. Create your feature branch (`git checkout -b my-new-feature`) 150 | 3. Commit your changes (`git commit -am 'Add some feature'`) 151 | 4. Push to the branch (`git push origin my-new-feature`) 152 | 5. Create new Pull Request 153 | 154 | To setup Granola for development, first pull the code. Then install CocoaPods: 155 | ```ruby 156 | $ gem install cocoapods 157 | ``` 158 | 159 | After that, change into the Example directory: 160 | ```ruby 161 | $ cd Example 162 | ``` 163 | 164 | and type: 165 | ```ruby 166 | pod install 167 | ``` 168 | 169 | Then open the .xcworkspace file instead of the .xcodeproj file. This will allow you to make changes to the code and run and add tests for any changes you make. 170 | 171 | 172 | ## License 173 | 174 | Granola is available under the Apache 2 license. See the [LICENSE](/LICENSE) file for more info. 175 | 176 | 177 | ## Authors 178 | 179 | Brent Hargrave ([@brenthargrave](http://twitter.com/brenthargrave)) 180 | Chris Schaefbauer (chris.schaefbauer@openmhealth.org) 181 | Emerson Farrugia (emerson@openmhealth.org) 182 | 183 | -------------------------------------------------------------------------------- /Pod/Classes/OMHHealthKitConstantsMapper.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Open mHealth 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | Provides mappings between HealthKit domain constants and Open mHealth representations. This includes mappings from sample values that are represented by constants in HealthKit (e.g., workout activities and category values) and mappings from HealthKit sample types to the appropriate JSON serializer. 19 | */ 20 | @interface OMHHealthKitConstantsMapper : NSObject 21 | 22 | /** 23 | Translates `HKWorkoutActivityType` constants into more semantically meaningful string representations. 24 | 25 | Used in generating values for workout types in serializing `HKWorkout` objects to JSON. 26 | 27 | @param enumValue The numeric value from the `HKWorkoutActivityType` enum. 28 | 29 | @return A string representation of the constant. 30 | 31 | @see [HKWorkoutActivityType](https://developer.apple.com/library/ios/documentation/HealthKit/Reference/HealthKit_Constants/index.html#//apple_ref/c/tdef/HKWorkoutActivityType) 32 | @see [HKWorkout](https://developer.apple.com/library/ios/documentation/HealthKit/Reference/HKWorkout_Class/index.html) 33 | */ 34 | + (NSString*) stringForHKWorkoutActivityType:(int) enumValue; 35 | 36 | /** 37 | Translates `HKCategoryValueSleepAnalysis` constants into more semantically meaningful string representations. 38 | 39 | Used in generating values for sleep analysis in serializing samples of `HKCategoryValueSleepAnalysis` type, with the value _inBed_, into JSON. 40 | 41 | @param enumValue The numeric value from the `HKCategoryValueSleepAnalysis` enum. 42 | 43 | @return A string representation of the constant. 44 | 45 | @see [HKCategoryValueSleepAnalysis](https://developer.apple.com/library/ios/documentation/HealthKit/Reference/HealthKit_Constants/index.html#//apple_ref/c/tdef/HKCategoryValueSleepAnalysis) 46 | @see [HKCategorySample](https://developer.apple.com/library/ios/documentation/HealthKit/Reference/HKCategorySample_Class/index.html) 47 | */ 48 | + (NSString*) stringForHKSleepAnalysisValue:(int) enumValue; 49 | 50 | /** 51 | Translates `HKCategoryValueAppleStandHour` constants into more semantically meaningful string representations. 52 | 53 | @param enumValue The numeric value from the `HKCategoryValueAppleStandHour` enum. 54 | 55 | @return A string representation of the constant. 56 | 57 | @see [HKCategoryValueAppleStandHour](https://developer.apple.com/library/ios/documentation/HealthKit/Reference/HealthKit_Constants/index.html#//apple_ref/c/tdef/HKCategoryValueAppleStandHour) 58 | @see [HKCategorySample](https://developer.apple.com/library/ios/documentation/HealthKit/Reference/HKCategorySample_Class/index.html) 59 | */ 60 | + (NSString*) stringForHKAppleStandHourValue:(int) enumValue; 61 | 62 | /** 63 | Translates `HKCategoryValueCervicalMucusQuality` constants into more semantically meaningful string representations. 64 | 65 | @param enumValue The numeric value from the `HKCategoryValueCervicalMucusQuality` enum. 66 | 67 | @return A string representation of the constant. 68 | 69 | @see [HKCategoryValueCervicalMucusQuality](https://developer.apple.com/library/ios/documentation/HealthKit/Reference/HealthKit_Constants/index.html#//apple_ref/c/tdef/HKCategoryValueCervicalMucusQuality) 70 | @see [HKCategorySample](https://developer.apple.com/library/ios/documentation/HealthKit/Reference/HKCategorySample_Class/index.html) 71 | */ 72 | + (NSString*) stringForHKCervicalMucusQualityValue:(int) enumValue; 73 | 74 | 75 | /** 76 | Translates `HKCategoryValueMenstrualFlow` constants into more semantically meaningful string representations. 77 | 78 | @param enumValue The numeric value from the `HKCategoryValueMenstrualFlow` enum. 79 | 80 | @return A string representation of the constant. 81 | 82 | @see [HKCategoryValueMenstrualFlow](https://developer.apple.com/library/ios/documentation/HealthKit/Reference/HealthKit_Constants/index.html#//apple_ref/c/tdef/HKCategoryValueMenstrualFlow) 83 | @see [HKCategorySample](https://developer.apple.com/library/ios/documentation/HealthKit/Reference/HKCategorySample_Class/index.html) 84 | */ 85 | + (NSString*) stringForHKMenstrualFlowValue:(int) enumValue; 86 | 87 | /** 88 | Translates `HKCategoryValueOvulationTestResult` constant values into more semantically meaningful string representations. 89 | 90 | @param enumValue The constant value from the `HKCategoryValueOvulationTestResult` enum. 91 | 92 | @return A string representation of the constant. 93 | 94 | @see [HKCategoryValueOvulationTestResult](https://developer.apple.com/library/ios/documentation/HealthKit/Reference/HealthKit_Constants/index.html#//apple_ref/c/tdef/HKCategoryValueOvulationTestResult) 95 | @see [HKCategorySample](https://developer.apple.com/library/ios/documentation/HealthKit/Reference/HKCategorySample_Class/index.html) 96 | */ 97 | + (NSString*) stringForHKOvulationTestResultValue:(int) enumValue; 98 | 99 | /** 100 | Describes the mappings between all HealthKit type identifiers suppported by Granola and the specific serializer class used by Granola to serialize samples of that type into JSON. The type identifiers are keys in the dictionary with their corresponding serializer class name as the value. 101 | 102 | This dictionary is an aggregation of all the type-specific dictionaries returned by allSupportedCategoryTypeIdentifiersToClasses, allSupportedCorrelationTypeIdentifiersToClass, and allSupportedQuantityTypeIdentifiersToClass, along with the HKWorkout type identifier and its associated serializer class. 103 | 104 | @return A dictionary containing tuples of the HealthKit types supported by Granola with the name of the serializer class they use. 105 | 106 | @see allSupportedCategoryTypeIdentifiersToClasses 107 | @see allSupportedCorrelationTypeIdentifiersToClass 108 | @see allSupportedQuantityTypeIdentifiersToClass 109 | */ 110 | + (NSDictionary*) allSupportedTypeIdentifiersToClasses; 111 | 112 | /** 113 | Describes the mappings between HealthKit category type identifiers suppported by Granola and the specific serializer class used by Granola to serialize samples of that type into JSON. The type identifiers are keys in the dictionary with their corresponding serializer class name as the value. 114 | 115 | @return A dictionary containing tuples of the HealthKit category types supported by Granola with the name of the serializer class they use. 116 | */ 117 | + (NSDictionary*) allSupportedCategoryTypeIdentifiersToClasses; 118 | 119 | /** 120 | Describes the mappings between HealthKit correlation type identifiers suppported by Granola and the specific serializer class used by Granola to serialize samples of that type into JSON. The type identifiers are keys in the dictionary with their corresponding serializer class name as the value. 121 | 122 | @return A dictionary containing tuples of the HealthKit correlation types supported by Granola with the name of the serializer class they use. 123 | */ 124 | + (NSDictionary*) allSupportedCorrelationTypeIdentifiersToClass; 125 | 126 | /** 127 | Describes the mappings between HealthKit quantity type identifiers suppported by Granola and the specific serializer class used by Granola to serialize samples of that type into JSON. The type identifiers are keys in the dictionary with their corresponding serializer class name as the value. 128 | 129 | @return A dictionary containing tuples of the HealthKit quantity types supported by Granola with the name of the serializer class they use. 130 | */ 131 | + (NSDictionary*) allSupportedQuantityTypeIdentifiersToClass; 132 | 133 | @end 134 | 135 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Example/Tests/OMHSampleFactory.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Open mHealth 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #import "OMHSampleFactory.h" 18 | #import "HKObject+Private.h" 19 | #import 20 | #include 21 | 22 | @implementation OMHSampleFactory 23 | 24 | + (HKSample*)typeIdentifier:(NSString*)sampleTypeIdentifier 25 | attrs:(NSDictionary*)attrs { 26 | id (^or)(id this, id that) = ^(id this, id that) { 27 | return (this) ? this : that; 28 | }; 29 | NSCalendar* calendar = [NSCalendar currentCalendar]; 30 | NSDate* defaultStart = [NSDate date]; 31 | NSDate* defaultEnd = [calendar dateByAddingUnit:NSCalendarUnitSecond 32 | value:30 33 | toDate:defaultStart 34 | options:kNilOptions]; 35 | NSDate* start = or(attrs[@"start"], defaultStart); 36 | NSDate* end = or(attrs[@"end"], defaultEnd); 37 | NSDictionary *metadata = nil; 38 | if(attrs[@"metadata"]){ 39 | metadata = attrs[@"metadata"]; 40 | } 41 | HKSample* sample = nil; 42 | if ([sampleTypeIdentifier hasPrefix:@"HKCategoryTypeIdentifier"]) { 43 | HKCategoryType* type = 44 | [HKObjectType categoryTypeForIdentifier:sampleTypeIdentifier]; 45 | NSNumber *defaultValue = [NSNumber numberWithInt:0]; 46 | 47 | // Apple starts their category enums at different values, for some reproductive health enums, it starts at 1 instead of 0 48 | if ([type.description isEqualToString:HKCategoryTypeIdentifierCervicalMucusQuality] || [type.description isEqualToString:HKCategoryTypeIdentifierOvulationTestResult] || [type.description isEqualToString:HKCategoryTypeIdentifierMenstrualFlow]){ 49 | 50 | defaultValue = [NSNumber numberWithInt:1]; 51 | } 52 | 53 | NSNumber *value = or(attrs[@"value"], defaultValue); 54 | 55 | if ([type.description isEqualToString:HKCategoryTypeIdentifierMenstrualFlow]) { 56 | 57 | // Menstrual flow category samples require an HKMetadataKeyMenstrualCycleStart entry. Without it, HealthKit will throw an exception. 58 | NSMutableDictionary *menstrualStartMetadata = [NSMutableDictionary dictionaryWithDictionary: @{ HKMetadataKeyMenstrualCycleStart : @true}]; 59 | 60 | if (metadata !=nil){ 61 | [menstrualStartMetadata addEntriesFromDictionary:metadata]; 62 | } 63 | 64 | metadata = menstrualStartMetadata; 65 | } 66 | 67 | sample = 68 | [HKCategorySample categorySampleWithType:type 69 | value:[value integerValue] 70 | startDate:start 71 | endDate:end 72 | metadata:metadata]; 73 | } 74 | else if (sampleTypeIdentifier == HKCorrelationTypeIdentifierBloodPressure) { 75 | NSSet* defaultSamples = [NSSet setWithArray:[@[ 76 | HKQuantityTypeIdentifierBloodPressureSystolic, 77 | HKQuantityTypeIdentifierBloodPressureDiastolic 78 | ] map:^(NSString* identifier) { 79 | NSDate* sampledAt = [NSDate date]; 80 | return [OMHSampleFactory typeIdentifier:identifier 81 | attrs:@{ @"start": sampledAt, 82 | @"end": sampledAt }]; 83 | }]]; 84 | NSSet* objects = or(attrs[@"objects"], defaultSamples); 85 | HKCorrelationType *bloodPressureType = 86 | [HKObjectType correlationTypeForIdentifier:HKCorrelationTypeIdentifierBloodPressure]; 87 | NSDictionary *metadata = nil; 88 | 89 | sample = 90 | (HKSample*)[HKCorrelation correlationWithType:bloodPressureType 91 | startDate:start 92 | endDate:end 93 | objects:objects 94 | metadata:metadata]; 95 | } 96 | else if (sampleTypeIdentifier == HKCorrelationTypeIdentifierFood){ 97 | NSSet *nutritionContentSamples = nil; 98 | if(attrs[@"objects"]){ 99 | nutritionContentSamples = attrs[@"objects"]; 100 | } 101 | else{ 102 | HKQuantitySample *defaultCarbQuantitySample = [HKQuantitySample quantitySampleWithType:[HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierDietaryCarbohydrates] quantity:[HKQuantity quantityWithUnit:[HKUnit unitFromString:@"g"] doubleValue:29.3] startDate:start endDate:end metadata:nil]; 103 | HKQuantitySample *defaultCalorieQuantitySample = [HKQuantitySample quantitySampleWithType:[HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierDietaryEnergyConsumed] quantity:[HKQuantity quantityWithUnit:[HKUnit unitFromString:@"kcal"] doubleValue:105] startDate:start endDate:end metadata:nil]; 104 | nutritionContentSamples = [NSSet setWithArray:@[defaultCarbQuantitySample,defaultCalorieQuantitySample]]; 105 | } 106 | NSDictionary *metadata = nil; 107 | 108 | sample = (HKSample*)[HKCorrelation correlationWithType:[HKCorrelationType correlationTypeForIdentifier:HKCorrelationTypeIdentifierFood] 109 | startDate:start 110 | endDate:end 111 | objects:nutritionContentSamples 112 | metadata:metadata]; 113 | } 114 | else if (sampleTypeIdentifier == HKWorkoutTypeIdentifier){ 115 | //If we pass attributes into the sample factory, then they should be used in creating the sample 116 | if([attrs count]>0){ 117 | 118 | NSTimeInterval duration = 100; 119 | int activityType = HKWorkoutActivityTypeCycling; 120 | if(attrs[@"activity_type"]){ 121 | NSString *activityTypeStr = (NSString*)attrs[@"activity_type"]; 122 | activityType = [activityTypeStr intValue]; 123 | } 124 | 125 | //If one of these three keys has been set in the attributes that were passed to the factory, then we want to create a more complex workout sample 126 | if([attrs hasKey:@"duration"] || [attrs hasKey:@"energy_burned"] || [attrs hasKey:@"distance"]){ 127 | if(attrs[@"duration"]){ 128 | NSString *timeIntervalStr = (NSString*)attrs[@"duration"]; 129 | duration = [timeIntervalStr doubleValue]; 130 | } 131 | 132 | NSDictionary *metadata = nil; 133 | 134 | HKQuantity *defaultEnergyBurned = [HKQuantity quantityWithUnit:[HKUnit unitFromString:@"kcal"] doubleValue:120.1]; 135 | HKQuantity *defaultDistance = [HKQuantity quantityWithUnit:[HKUnit unitFromString:@"km"] doubleValue:5.2]; 136 | sample = (HKSample*)[HKWorkout workoutWithActivityType:activityType startDate:or(attrs[@"start"],defaultStart) endDate:or(attrs[@"end"],defaultEnd) duration:duration totalEnergyBurned:or(attrs[@"energy_burned"],defaultEnergyBurned) totalDistance:or(attrs[@"distance"],defaultDistance) metadata:metadata]; 137 | } 138 | //If we do not have keys in the attribute dictionary for the more complex pieces of workout data, then we can create a simple workout sample 139 | else{ 140 | sample = (HKSample*)[HKWorkout workoutWithActivityType:activityType startDate:or(attrs[@"start"],defaultStart) endDate:or(attrs[@"end"],defaultEnd)]; 141 | } 142 | 143 | } 144 | //If no attributes are passed into the sample factory, then we can create a simple workout with start and end date set to now 145 | else{ 146 | sample = (HKSample*)[HKWorkout workoutWithActivityType:HKWorkoutActivityTypeCycling startDate:[NSDate date] endDate:[NSDate date]]; 147 | } 148 | 149 | } 150 | else if ([@[ HKQuantityTypeIdentifierHeight, 151 | HKQuantityTypeIdentifierBodyMass, 152 | HKQuantityTypeIdentifierHeartRate, 153 | HKQuantityTypeIdentifierStepCount, 154 | HKQuantityTypeIdentifierNikeFuel, 155 | HKQuantityTypeIdentifierBloodGlucose, 156 | HKQuantityTypeIdentifierBasalEnergyBurned, 157 | HKQuantityTypeIdentifierActiveEnergyBurned, 158 | HKQuantityTypeIdentifierBloodPressureSystolic, 159 | HKQuantityTypeIdentifierBloodPressureDiastolic, 160 | HKQuantityTypeIdentifierBodyMassIndex, 161 | HKQuantityTypeIdentifierDietaryBiotin, 162 | HKQuantityTypeIdentifierDietaryWater, 163 | HKQuantityTypeIdentifierInhalerUsage, 164 | HKQuantityTypeIdentifierDietaryCarbohydrates, 165 | HKQuantityTypeIdentifierDietaryEnergyConsumed, 166 | HKQuantityTypeIdentifierUVExposure, 167 | HKQuantityTypeIdentifierOxygenSaturation, 168 | HKQuantityTypeIdentifierBodyFatPercentage, 169 | HKQuantityTypeIdentifierRespiratoryRate, 170 | HKQuantityTypeIdentifierBodyTemperature, 171 | HKQuantityTypeIdentifierBasalBodyTemperature, 172 | HKQuantityTypeIdentifierBloodAlcoholContent 173 | ] includes:sampleTypeIdentifier]) { 174 | 175 | NSString* defaultUnitString = nil; 176 | if (sampleTypeIdentifier == HKQuantityTypeIdentifierHeight) { 177 | defaultUnitString = @"cm"; 178 | } else if (sampleTypeIdentifier == HKQuantityTypeIdentifierBodyMass) { 179 | defaultUnitString = @"lb"; 180 | } else if (sampleTypeIdentifier == HKQuantityTypeIdentifierHeartRate || sampleTypeIdentifier == HKQuantityTypeIdentifierRespiratoryRate) { 181 | defaultUnitString = @"count/min"; 182 | } else if (sampleTypeIdentifier == HKQuantityTypeIdentifierBloodGlucose) { 183 | defaultUnitString = @"mg/dL"; 184 | } else if ([sampleTypeIdentifier containsString:@"EnergyBurned"]) { 185 | defaultUnitString = @"kcal"; 186 | } else if ([sampleTypeIdentifier containsString:@"HKQuantityTypeIdentifierBloodPressure"]) { 187 | defaultUnitString = @"mmHg"; 188 | } else if (sampleTypeIdentifier == HKQuantityTypeIdentifierDietaryBiotin) { 189 | defaultUnitString = @"mcg"; 190 | } else if (sampleTypeIdentifier == HKQuantityTypeIdentifierDietaryWater) { 191 | defaultUnitString = @"L"; 192 | } else if (sampleTypeIdentifier == HKQuantityTypeIdentifierOxygenSaturation || sampleTypeIdentifier == HKQuantityTypeIdentifierBodyFatPercentage 193 | || sampleTypeIdentifier == HKQuantityTypeIdentifierBloodAlcoholContent) { 194 | defaultUnitString = @"%"; 195 | } else if (sampleTypeIdentifier == HKQuantityTypeIdentifierBodyTemperature || sampleTypeIdentifier == HKQuantityTypeIdentifierBasalBodyTemperature) { 196 | defaultUnitString = @"degC"; 197 | } else { 198 | defaultUnitString = @"count"; 199 | }; 200 | NSString* unitString = or(attrs[@"unitString"], defaultUnitString); 201 | 202 | NSNumber* defaultValue = 203 | [NSNumber numberWithInteger:arc4random_uniform(74)]; 204 | NSNumber* value = or(attrs[@"value"], defaultValue); 205 | 206 | HKUnit* unit = [HKUnit unitFromString:unitString]; 207 | HKQuantityType* quantityType = 208 | [HKObjectType quantityTypeForIdentifier:sampleTypeIdentifier]; 209 | HKQuantity* quantity = [HKQuantity quantityWithUnit:unit 210 | doubleValue:value.doubleValue]; 211 | NSDictionary *metadata = nil; 212 | if(attrs[@"metadata"]){ 213 | metadata = attrs[@"metadata"]; 214 | } 215 | 216 | sample = 217 | [HKQuantitySample quantitySampleWithType:quantityType 218 | quantity:quantity 219 | startDate:start 220 | endDate:end 221 | metadata:metadata]; 222 | } 223 | // validate sample object using HK private API, exposed via category, 224 | // see HKObject+Private.h 225 | // NOTE: throws _HKObjectValidationFailureException if invalid 226 | 227 | [sample _validateForSavingWithClientEntitlements:nil applicationSDKVersion:11 error:nil]; 228 | return sample; 229 | } 230 | 231 | + (HKSample*)typeIdentifier:(NSString*)typeIdentifier { 232 | return [self typeIdentifier:typeIdentifier attrs:@{}]; 233 | } 234 | 235 | @end 236 | 237 | -------------------------------------------------------------------------------- /Docs/hkobject_type_coverage.md: -------------------------------------------------------------------------------- 1 | ## HKObjectType Coverage 2 | 3 | |HKObject type|Granola|Open mHealth schema| 4 | |-------------|:---------:|-------------| 5 | |*__Nutrition Identifiers__*||| 6 | |HKQuantityTypeIdentifierDietaryBiotin|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 7 | |HKQuantityTypeIdentifierDietaryCaffeine|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 8 | |HKQuantityTypeIdentifierDietaryCalcium|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 9 | |HKQuantityTypeIdentifierDietaryCarbohydrates|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 10 | |HKQuantityTypeIdentifierDietaryChloride|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 11 | |HKQuantityTypeIdentifierDietaryCholesterol|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 12 | |HKQuantityTypeIdentifierDietaryChromium|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 13 | |HKQuantityTypeIdentifierDietaryCopper|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 14 | |HKQuantityTypeIdentifierDietaryEnergyConsumed|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 15 | |HKQuantityTypeIdentifierDietaryFatMonounsaturated|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 16 | |HKQuantityTypeIdentifierDietaryFatPolyunsaturated|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 17 | |HKQuantityTypeIdentifierDietaryFatSaturated|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 18 | |HKQuantityTypeIdentifierDietaryFatTotal|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 19 | |HKQuantityTypeIdentifierDietaryFiber|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 20 | |HKQuantityTypeIdentifierDietaryFolate|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 21 | |HKQuantityTypeIdentifierDietaryIodine|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 22 | |HKQuantityTypeIdentifierDietaryIron|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 23 | |HKQuantityTypeIdentifierDietaryMagnesium|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 24 | |HKQuantityTypeIdentifierDietaryManganese|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 25 | |HKQuantityTypeIdentifierDietaryMolybdenum|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 26 | |HKQuantityTypeIdentifierDietaryNiacin|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 27 | |HKQuantityTypeIdentifierDietaryPantothenicAcid|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 28 | |HKQuantityTypeIdentifierDietaryPhosphorus|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 29 | |HKQuantityTypeIdentifierDietaryPotassium|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 30 | |HKQuantityTypeIdentifierDietaryProtein|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 31 | |HKQuantityTypeIdentifierDietaryRiboflavin|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 32 | |HKQuantityTypeIdentifierDietarySelenium|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 33 | |HKQuantityTypeIdentifierDietarySodium|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 34 | |HKQuantityTypeIdentifierDietarySugar|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 35 | |HKQuantityTypeIdentifierDietaryThiamin|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 36 | |HKQuantityTypeIdentifierDietaryVitaminA|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 37 | |HKQuantityTypeIdentifierDietaryVitaminB12|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 38 | |HKQuantityTypeIdentifierDietaryVitaminB6|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 39 | |HKQuantityTypeIdentifierDietaryVitaminC|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 40 | |HKQuantityTypeIdentifierDietaryVitaminD|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 41 | |HKQuantityTypeIdentifierDietaryVitaminE|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 42 | |HKQuantityTypeIdentifierDietaryVitaminK|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 43 | |HKQuantityTypeIdentifierDietaryWater|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 44 | |HKQuantityTypeIdentifierDietaryZinc|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 45 | |*__Body Measurements__*||| 46 | |HKQuantityTypeIdentifierBodyMassIndex|:white_check_mark:|[omh:body-mass-index:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_body-mass-index)| 47 | |HKQuantityTypeIdentifierBodyFatPercentage|:white_check_mark:|[omh:body-fat-percentage:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_body-fat-percentage)| 48 | |HKQuantityTypeIdentifierHeight|:white_check_mark:|[omh:body-height:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_body-height)| 49 | |HKQuantityTypeIdentifierBodyMass|:white_check_mark:|[omh:body-weight:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_body-weight)| 50 | |HKQuantityTypeIdentifierLeanBodyMass|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 51 | |*__Results Identifiers__*||| 52 | |HKQuantityTypeIdentifierOxygenSaturation|:white_check_mark:|[omh:oxygen-saturation:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_oxygen-saturation)| 53 | |HKQuantityTypeIdentifierPeripheralPerfusionIndex|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 54 | |HKQuantityTypeIdentifierBloodGlucose|:white_check_mark:|[omh:blood-glucose:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_blood-glucose)| 55 | |HKQuantityTypeIdentifierNumberOfTimesFallen|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 56 | |HKQuantityTypeIdentifierElectrodermalActivity|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 57 | |HKQuantityTypeIdentifierInhalerUsage|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 58 | |HKQuantityTypeIdentifierBloodAlcoholContent|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 59 | |HKQuantityTypeIdentifierForcedVitalCapacity|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 60 | |HKQuantityTypeIdentifierForcedExpiratoryVolume1|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 61 | |HKQuantityTypeIdentifierPeakExpiratoryFlowRate|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 62 | |HKQuantityTypeIdentifierUVExposure|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 63 | |*__Correlation Identifiers__*||| 64 | |HKCorrelationTypeIdentifierBloodPressure|:white_check_mark:|[omh:blood-pressure:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_blood-pressure)| 65 | |HKCorrelationTypeIdentifierFood|:white_check_mark:|[granola:hk-correlation:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-correlation)| 66 | |*__Sleep Identifiers__*||| 67 | |HKCategoryTypeIdentifierSleepAnalysis - Asleep|:white_check_mark:|[omh:sleep-duration:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_sleep-duration)| 68 | |HKCategoryTypeIdentifierSleepAnalysis - InBed|:white_check_mark:|[granola:hk-category-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-category-sample)| 69 | |*__Workout Identifier__*||| 70 | |HKWorkoutTypeIdentifier|:white_check_mark:|[granola:hk-workout:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-workout)| 71 | |*__Fitness Identifiers__*||| 72 | |HKQuantityTypeIdentifierStepCount|:white_check_mark:|[omh:step-count:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_step-count)| 73 | |HKQuantityTypeIdentifierDistanceWalkingRunning|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 74 | |HKQuantityTypeIdentifierDistanceCycling|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 75 | |HKQuantityTypeIdentifierBasalEnergyBurned|:white_check_mark:|[omh:calories-burned:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_calories-burned)| 76 | |HKQuantityTypeIdentifierActiveEnergyBurned|:white_check_mark:|[omh:calories-burned:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_calories-burned)| 77 | |HKQuantityTypeIdentifierFlightsClimbed|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 78 | |HKQuantityTypeIdentifierNikeFuel|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 79 | |HKCategoryTypeIdentifierAppleStandHour|:white_check_mark:|[granola:hk-category-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-category-sample)| 80 | |*__Vital Signs Identifiers__*||| 81 | |HKQuantityTypeIdentifierHeartRate|:white_check_mark:|[omh:heart-rate:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_heart-rate)| 82 | |HKQuantityTypeIdentifierBodyTemperature|:white_check_mark:|[omh:body-temperature:2.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_body-temperature)| 83 | |HKQuantityTypeIdentifierBasalBodyTemperature|:white_check_mark:|[omh:body-temperature:2.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_body-temperature)| 84 | |HKQuantityTypeIdentifierBloodPressureSystolic|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 85 | |HKQuantityTypeIdentifierBloodPressureDiastolic|:white_check_mark:|[granola:hk-quantity-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample)| 86 | |HKQuantityTypeIdentifierRespiratoryRate|:white_check_mark:|[omh:respiratory-rate:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_respiratory-rate)| 87 | |*__Sexual and Reproductive Health Identifiers__*||| 88 | |HKCategoryTypeIdentifierCervicalMucusQuality|:white_check_mark:|[granola:hk-category-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-category-sample)| 89 | |HKCategoryTypeIdentifierOvulationTestResult|:white_check_mark:|[granola:hk-category-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-category-sample)| 90 | |HKCategoryTypeIdentifierMenstrualFlow|:white_check_mark:|[granola:hk-category-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-category-sample)| 91 | |HKCategoryTypeIdentifierIntermenstrualBleeding|:white_check_mark:|[granola:hk-category-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-category-sample)| 92 | |HKCategoryTypeIdentifierSexualActivity|:white_check_mark:|[granola:hk-category-sample:1.x](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-category-sample)| 93 | |*__Characteristics Identifiers__*||| 94 | |HKCharacteristicTypeIdentifierBiologicalSex||| 95 | |HKCharacteristicTypeIdentifierBloodType||| 96 | |HKCharacteristicTypeIdentifierDateOfBirth||| 97 | |HKCharacteristicTypeIdentifierFitzpatrickSkinType||| 98 | 99 | -------------------------------------------------------------------------------- /Pod/Classes/OMHHealthKitConstantsMapper.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Open mHealth 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #import 18 | #import 19 | #import "OMHHealthKitConstantsMapper.h" 20 | 21 | @implementation OMHHealthKitConstantsMapper 22 | + (NSString*)stringForHKWorkoutActivityType:(int) enumValue{ 23 | switch( enumValue ){ 24 | case HKWorkoutActivityTypeAmericanFootball: 25 | return @"HKWorkoutActivityTypeAmericanFootball"; 26 | case HKWorkoutActivityTypeArchery: 27 | return @"HKWorkoutActivityTypeArchery"; 28 | case HKWorkoutActivityTypeAustralianFootball: 29 | return @"HKWorkoutActivityTypeAustralianFootball"; 30 | case HKWorkoutActivityTypeBadminton: 31 | return @"HKWorkoutActivityTypeBadminton"; 32 | case HKWorkoutActivityTypeBaseball: 33 | return @"HKWorkoutActivityTypeBaseball"; 34 | case HKWorkoutActivityTypeBasketball: 35 | return @"HKWorkoutActivityTypeBasketball"; 36 | case HKWorkoutActivityTypeBowling: 37 | return @"HKWorkoutActivityTypeBowling"; 38 | case HKWorkoutActivityTypeBoxing: 39 | return @"HKWorkoutActivityTypeBoxing"; 40 | case HKWorkoutActivityTypeClimbing: 41 | return @"HKWorkoutActivityTypeClimbing"; 42 | case HKWorkoutActivityTypeCricket: 43 | return @"HKWorkoutActivityTypeCricket"; 44 | case HKWorkoutActivityTypeCrossTraining: 45 | return @"HKWorkoutActivityTypeCrossTraining"; 46 | case HKWorkoutActivityTypeCurling: 47 | return @"HKWorkoutActivityTypeCurling"; 48 | case HKWorkoutActivityTypeCycling: 49 | return @"HKWorkoutActivityTypeCycling"; 50 | case HKWorkoutActivityTypeDance: 51 | return @"HKWorkoutActivityTypeDance"; 52 | case HKWorkoutActivityTypeDanceInspiredTraining: 53 | return @"HKWorkoutActivityTypeDanceInspiredTraining"; 54 | case HKWorkoutActivityTypeElliptical: 55 | return @"HKWorkoutActivityTypeElliptical"; 56 | case HKWorkoutActivityTypeEquestrianSports: 57 | return @"HKWorkoutActivityTypeEquestrianSports"; 58 | case HKWorkoutActivityTypeFencing: 59 | return @"HKWorkoutActivityTypeFencing"; 60 | case HKWorkoutActivityTypeFishing: 61 | return @"HKWorkoutActivityTypeFishing"; 62 | case HKWorkoutActivityTypeFunctionalStrengthTraining: 63 | return @"HKWorkoutActivityTypeFunctionalStrengthTraining"; 64 | case HKWorkoutActivityTypeGolf: 65 | return @"HKWorkoutActivityTypeGolf"; 66 | case HKWorkoutActivityTypeGymnastics: 67 | return @"HKWorkoutActivityTypeGymnastics"; 68 | case HKWorkoutActivityTypeHandball: 69 | return @"HKWorkoutActivityTypeHandball"; 70 | case HKWorkoutActivityTypeHiking: 71 | return @"HKWorkoutActivityTypeHiking"; 72 | case HKWorkoutActivityTypeHockey: 73 | return @"HKWorkoutActivityTypeHockey"; 74 | case HKWorkoutActivityTypeHunting: 75 | return @"HKWorkoutActivityTypeHunting"; 76 | case HKWorkoutActivityTypeLacrosse: 77 | return @"HKWorkoutActivityTypeLacrosse"; 78 | case HKWorkoutActivityTypeMartialArts: 79 | return @"HKWorkoutActivityTypeMartialArts"; 80 | case HKWorkoutActivityTypeMindAndBody: 81 | return @"HKWorkoutActivityTypeMindAndBody"; 82 | case HKWorkoutActivityTypeMixedMetabolicCardioTraining: 83 | return @"HKWorkoutActivityTypeMixedMetabolicCardioTraining"; 84 | case HKWorkoutActivityTypePaddleSports: 85 | return @"HKWorkoutActivityTypePaddleSports"; 86 | case HKWorkoutActivityTypePlay: 87 | return @"HKWorkoutActivityTypePlay"; 88 | case HKWorkoutActivityTypePreparationAndRecovery: 89 | return @"HKWorkoutActivityTypePreparationAndRecovery"; 90 | case HKWorkoutActivityTypeRacquetball: 91 | return @"HKWorkoutActivityTypeRacquetball"; 92 | case HKWorkoutActivityTypeRowing: 93 | return @"HKWorkoutActivityTypeRowing"; 94 | case HKWorkoutActivityTypeRugby: 95 | return @"HKWorkoutActivityTypeRugby"; 96 | case HKWorkoutActivityTypeRunning: 97 | return @"HKWorkoutActivityTypeRunning"; 98 | case HKWorkoutActivityTypeSailing: 99 | return @"HKWorkoutActivityTypeSailing"; 100 | case HKWorkoutActivityTypeSkatingSports: 101 | return @"HKWorkoutActivityTypeSkatingSports"; 102 | case HKWorkoutActivityTypeSnowSports: 103 | return @"HKWorkoutActivityTypeSnowSports"; 104 | case HKWorkoutActivityTypeSoccer: 105 | return @"HKWorkoutActivityTypeSoccer"; 106 | case HKWorkoutActivityTypeSoftball: 107 | return @"HKWorkoutActivityTypeSoftball"; 108 | case HKWorkoutActivityTypeSquash: 109 | return @"HKWorkoutActivityTypeSquash"; 110 | case HKWorkoutActivityTypeStairClimbing: 111 | return @"HKWorkoutActivityTypeStairClimbing"; 112 | case HKWorkoutActivityTypeSurfingSports: 113 | return @"HKWorkoutActivityTypeSurfingSports"; 114 | case HKWorkoutActivityTypeSwimming: 115 | return @"HKWorkoutActivityTypeSwimming"; 116 | case HKWorkoutActivityTypeTableTennis: 117 | return @"HKWorkoutActivityTypeTableTennis"; 118 | case HKWorkoutActivityTypeTennis: 119 | return @"HKWorkoutActivityTypeTennis"; 120 | case HKWorkoutActivityTypeTrackAndField: 121 | return @"HKWorkoutActivityTypeTrackAndField"; 122 | case HKWorkoutActivityTypeTraditionalStrengthTraining: 123 | return @"HKWorkoutActivityTypeTraditionalStrengthTraining"; 124 | case HKWorkoutActivityTypeVolleyball: 125 | return @"HKWorkoutActivityTypeVolleyball"; 126 | case HKWorkoutActivityTypeWalking: 127 | return @"HKWorkoutActivityTypeWalking"; 128 | case HKWorkoutActivityTypeWaterFitness: 129 | return @"HKWorkoutActivityTypeWaterFitness"; 130 | case HKWorkoutActivityTypeWaterPolo: 131 | return @"HKWorkoutActivityTypeWaterPolo"; 132 | case HKWorkoutActivityTypeWaterSports: 133 | return @"HKWorkoutActivityTypeWaterSports"; 134 | case HKWorkoutActivityTypeWrestling: 135 | return @"HKWorkoutActivityTypeWrestling"; 136 | case HKWorkoutActivityTypeYoga: 137 | return @"HKWorkoutActivityTypeYoga"; 138 | case HKWorkoutActivityTypeOther: 139 | return @"HKWorkoutActivityTypeOther"; 140 | 141 | // iOS 10+ 142 | case HKWorkoutActivityTypeBarre: 143 | return @"HKWorkoutActivityTypeBarre"; 144 | case HKWorkoutActivityTypeCoreTraining: 145 | return @"HKWorkoutActivityTypeCoreTraining"; 146 | case HKWorkoutActivityTypeCrossCountrySkiing: 147 | return @"HKWorkoutActivityTypeCrossCountrySkiing"; 148 | case HKWorkoutActivityTypeDownhillSkiing: 149 | return @"HKWorkoutActivityTypeDownhillSkiing"; 150 | case HKWorkoutActivityTypeFlexibility: 151 | return @"HKWorkoutActivityTypeFlexibility"; 152 | case HKWorkoutActivityTypeHighIntensityIntervalTraining: 153 | return @"HKWorkoutActivityTypeHighIntensityIntervalTraining"; 154 | case HKWorkoutActivityTypeJumpRope: 155 | return @"HKWorkoutActivityTypeJumpRope"; 156 | case HKWorkoutActivityTypeKickboxing: 157 | return @"HKWorkoutActivityTypeKickboxing"; 158 | case HKWorkoutActivityTypePilates: 159 | return @"HKWorkoutActivityTypePilates"; 160 | case HKWorkoutActivityTypeSnowboarding: 161 | return @"HKWorkoutActivityTypeSnowboarding"; 162 | case HKWorkoutActivityTypeStairs: 163 | return @"HKWorkoutActivityTypeStairs"; 164 | case HKWorkoutActivityTypeStepTraining: 165 | return @"HKWorkoutActivityTypeStepTraining"; 166 | case HKWorkoutActivityTypeWheelchairWalkPace: 167 | return @"HKWorkoutActivityTypeWheelchairWalkPace"; 168 | case HKWorkoutActivityTypeWheelchairRunPace: 169 | return @"HKWorkoutActivityTypeWheelchairRunPace"; 170 | case HKWorkoutActivityTypeTaiChi: 171 | return @"HKWorkoutActivityTypeTaiChi"; 172 | case HKWorkoutActivityTypeMixedCardio: 173 | return @"HKWorkoutActivityTypeMixedCardio"; 174 | case HKWorkoutActivityTypeHandCycling: 175 | return @"HKWorkoutActivityTypeHandCycling"; 176 | case HKWorkoutActivityTypeDiscSports: 177 | return @"HKWorkoutActivityTypeDiscSports"; 178 | case HKWorkoutActivityTypeFitnessGaming: 179 | return @"HKWorkoutActivityTypeFitnessGaming"; 180 | 181 | default:{ 182 | return @"HKWorkoutActivityTypeOther"; 183 | } 184 | } 185 | } 186 | 187 | + (NSString*) stringForHKSleepAnalysisValue:(int) enumValue { 188 | switch (enumValue) { 189 | case HKCategoryValueSleepAnalysisInBed: 190 | return @"InBed"; 191 | break; 192 | case HKCategoryValueSleepAnalysisAsleep: 193 | return @"Asleep"; 194 | break; 195 | case HKCategoryValueSleepAnalysisAwake: 196 | return @"Awake"; 197 | break; 198 | default:{ 199 | NSException *e = [NSException 200 | exceptionWithName:@"HKCategoryValueSleepAnalysisInvalidValue" 201 | reason:@"HKCategoryValueSleepAnalysis can only have a HKCategoryValueSleepAnalysisInBed or HKCategoryValueSleepAnalysisAsleep value" 202 | userInfo:nil]; 203 | @throw e; 204 | } 205 | } 206 | } 207 | 208 | + (NSString*) stringForHKAppleStandHourValue:(int) enumValue { 209 | switch (enumValue) { 210 | case HKCategoryValueAppleStandHourIdle: 211 | return @"Idle"; 212 | case HKCategoryValueAppleStandHourStood: 213 | return @"Standing"; 214 | default:{ 215 | NSException *e = [NSException 216 | exceptionWithName:@"HKCategoryValueAppleStandHourInvalidValue" 217 | reason:@"HKCategoryValueAppleStandHour can only have a HKCategoryValueAppleStandHourIdle or HKCategoryValueAppleStandHourStood value" 218 | userInfo:nil]; 219 | @throw e; 220 | } 221 | } 222 | } 223 | 224 | + (NSString*) stringForHKCervicalMucusQualityValue:(int) enumValue { 225 | switch (enumValue) { 226 | case HKCategoryValueCervicalMucusQualityCreamy: 227 | return @"Creamy"; 228 | case HKCategoryValueCervicalMucusQualityDry: 229 | return @"Dry"; 230 | case HKCategoryValueCervicalMucusQualityEggWhite: 231 | return @"Egg white"; 232 | case HKCategoryValueCervicalMucusQualitySticky: 233 | return @"Sticky"; 234 | case HKCategoryValueCervicalMucusQualityWatery: 235 | return @"Watery"; 236 | default:{ 237 | NSException *e = [NSException 238 | exceptionWithName:@"HKCategoryValueCervicalMucusQualityInvalidValue" 239 | reason:@"HKCategoryValueCervicalMucusQuality can only have a value specified in the HKCategoryValueCervicalMucusQuality enum" 240 | userInfo:nil]; 241 | @throw e; 242 | } 243 | } 244 | } 245 | 246 | + (NSString*) stringForHKMenstrualFlowValue:(int) enumValue { 247 | switch (enumValue) { 248 | case HKCategoryValueMenstrualFlowUnspecified: 249 | return @"Unspecified"; 250 | case HKCategoryValueMenstrualFlowLight: 251 | return @"Light"; 252 | case HKCategoryValueMenstrualFlowMedium: 253 | return @"Medium"; 254 | case HKCategoryValueMenstrualFlowHeavy: 255 | return @"Heavy"; 256 | default:{ 257 | NSException *e = [NSException 258 | exceptionWithName:@"HKCategoryValueMenstrualFlowInvalidValue" 259 | reason:@"HKCategoryValueMenstrualFlow can only have a value specified in the HKCategoryValueMenstrualFlow enum" 260 | userInfo:nil]; 261 | @throw e; 262 | } 263 | } 264 | } 265 | 266 | + (NSString*) stringForHKOvulationTestResultValue:(int) enumValue { 267 | switch (enumValue) { 268 | case HKCategoryValueOvulationTestResultNegative: 269 | return @"Negative"; 270 | case HKCategoryValueOvulationTestResultPositive: 271 | return @"Positive"; 272 | case HKCategoryValueOvulationTestResultIndeterminate: 273 | return @"Indeterminate"; 274 | default:{ 275 | NSException *e = [NSException 276 | exceptionWithName:@"HKCategoryValueOvulationTestResultInvalidValue" 277 | reason:@"HKCategoryValueOvulationTestResult can only have a value specified in the HKCategoryValueOvulationTestResult enum" 278 | userInfo:nil]; 279 | @throw e; 280 | } 281 | } 282 | } 283 | 284 | + (NSDictionary*)allSupportedTypeIdentifiersToClasses { 285 | static NSDictionary* typeIdsToClasses = nil; 286 | if (typeIdsToClasses == nil) { 287 | typeIdsToClasses = @{ HKWorkoutTypeIdentifier: @"OMHSerializerGenericWorkout"}; 288 | } 289 | 290 | NSMutableDictionary *allTypeIdsToClasses = [NSMutableDictionary dictionaryWithDictionary:typeIdsToClasses]; 291 | 292 | [allTypeIdsToClasses addEntriesFromDictionary:[self allSupportedCategoryTypeIdentifiersToClasses]]; 293 | [allTypeIdsToClasses addEntriesFromDictionary:[self allSupportedCorrelationTypeIdentifiersToClass]]; 294 | [allTypeIdsToClasses addEntriesFromDictionary:[self allSupportedQuantityTypeIdentifiersToClass]]; 295 | 296 | return allTypeIdsToClasses; 297 | 298 | } 299 | 300 | + (NSDictionary*)allSupportedCategoryTypeIdentifiersToClasses { 301 | 302 | static NSDictionary* allCategoryTypeIdsToClasses = nil; 303 | if (allCategoryTypeIdsToClasses == nil) { 304 | allCategoryTypeIdsToClasses = @{ 305 | HKCategoryTypeIdentifierSleepAnalysis : @"OMHSerializerSleepAnalysis", //Samples with Asleep value use this serializer, samples with InBed value use generic category serializer 306 | HKCategoryTypeIdentifierAppleStandHour : @"OMHSerializerGenericCategorySample", 307 | HKCategoryTypeIdentifierCervicalMucusQuality : @"OMHSerializerGenericCategorySample", 308 | HKCategoryTypeIdentifierIntermenstrualBleeding: @"OMHSerializerGenericCategorySample", 309 | HKCategoryTypeIdentifierMenstrualFlow: @"OMHSerializerGenericCategorySample", 310 | HKCategoryTypeIdentifierOvulationTestResult: @"OMHSerializerGenericCategorySample", 311 | HKCategoryTypeIdentifierSexualActivity: @"OMHSerializerGenericCategorySample", 312 | HKCategoryTypeIdentifierMindfulSession: @"OMHSerializerGenericCategorySample" 313 | }; 314 | } 315 | return allCategoryTypeIdsToClasses; 316 | 317 | } 318 | 319 | + (NSDictionary*)allSupportedCorrelationTypeIdentifiersToClass { 320 | 321 | static NSDictionary* allCorrelationTypeIdsToClasses = nil; 322 | if (allCorrelationTypeIdsToClasses == nil) { 323 | allCorrelationTypeIdsToClasses = @{ 324 | HKCorrelationTypeIdentifierBloodPressure: @"OMHSerializerBloodPressure", 325 | HKCorrelationTypeIdentifierFood: @"OMHSerializerGenericCorrelation" 326 | }; 327 | } 328 | return allCorrelationTypeIdsToClasses; 329 | } 330 | 331 | + (NSDictionary*)allSupportedQuantityTypeIdentifiersToClass { 332 | 333 | static NSDictionary* allQuantityTypeIdsToClasses = nil; 334 | if (allQuantityTypeIdsToClasses == nil) { 335 | allQuantityTypeIdsToClasses = @{ 336 | HKQuantityTypeIdentifierActiveEnergyBurned: @"OMHSerializerEnergyBurned", 337 | HKQuantityTypeIdentifierBasalBodyTemperature: @"OMHSerializerBodyTemperature", 338 | HKQuantityTypeIdentifierBasalEnergyBurned: @"OMHSerializerEnergyBurned", 339 | HKQuantityTypeIdentifierBloodAlcoholContent: @"OMHSerializerGenericQuantitySample", 340 | HKQuantityTypeIdentifierBloodGlucose : @"OMHSerializerBloodGlucose", 341 | HKQuantityTypeIdentifierBloodPressureDiastolic: @"OMHSerializerGenericQuantitySample", 342 | HKQuantityTypeIdentifierBloodPressureSystolic: @"OMHSerializerGenericQuantitySample", 343 | HKQuantityTypeIdentifierBodyFatPercentage: @"OMHSerializerBodyFatPercentage", 344 | HKQuantityTypeIdentifierBodyMass : @"OMHSerializerWeight", 345 | HKQuantityTypeIdentifierBodyMassIndex: @"OMHSerializerBodyMassIndex", 346 | HKQuantityTypeIdentifierBodyTemperature: @"OMHSerializerBodyTemperature", 347 | HKQuantityTypeIdentifierDietaryBiotin: @"OMHSerializerGenericQuantitySample", 348 | HKQuantityTypeIdentifierDietaryCaffeine: @"OMHSerializerGenericQuantitySample", 349 | HKQuantityTypeIdentifierDietaryCalcium: @"OMHSerializerGenericQuantitySample", 350 | HKQuantityTypeIdentifierDietaryCarbohydrates: @"OMHSerializerGenericQuantitySample", 351 | HKQuantityTypeIdentifierDietaryChloride: @"OMHSerializerGenericQuantitySample", 352 | HKQuantityTypeIdentifierDietaryCholesterol: @"OMHSerializerGenericQuantitySample", 353 | HKQuantityTypeIdentifierDietaryChromium: @"OMHSerializerGenericQuantitySample", 354 | HKQuantityTypeIdentifierDietaryCopper: @"OMHSerializerGenericQuantitySample", 355 | HKQuantityTypeIdentifierDietaryEnergyConsumed: @"OMHSerializerGenericQuantitySample", 356 | HKQuantityTypeIdentifierDietaryFatMonounsaturated: @"OMHSerializerGenericQuantitySample", 357 | HKQuantityTypeIdentifierDietaryFatPolyunsaturated: @"OMHSerializerGenericQuantitySample", 358 | HKQuantityTypeIdentifierDietaryFatSaturated: @"OMHSerializerGenericQuantitySample", 359 | HKQuantityTypeIdentifierDietaryFatTotal: @"OMHSerializerGenericQuantitySample", 360 | HKQuantityTypeIdentifierDietaryFiber: @"OMHSerializerGenericQuantitySample", 361 | HKQuantityTypeIdentifierDietaryFolate: @"OMHSerializerGenericQuantitySample", 362 | HKQuantityTypeIdentifierDietaryIodine: @"OMHSerializerGenericQuantitySample", 363 | HKQuantityTypeIdentifierDietaryIron: @"OMHSerializerGenericQuantitySample", 364 | HKQuantityTypeIdentifierDietaryMagnesium: @"OMHSerializerGenericQuantitySample", 365 | HKQuantityTypeIdentifierDietaryManganese: @"OMHSerializerGenericQuantitySample", 366 | HKQuantityTypeIdentifierDietaryMolybdenum: @"OMHSerializerGenericQuantitySample", 367 | HKQuantityTypeIdentifierDietaryNiacin: @"OMHSerializerGenericQuantitySample", 368 | HKQuantityTypeIdentifierDietaryPantothenicAcid: @"OMHSerializerGenericQuantitySample", 369 | HKQuantityTypeIdentifierDietaryPhosphorus: @"OMHSerializerGenericQuantitySample", 370 | HKQuantityTypeIdentifierDietaryPotassium: @"OMHSerializerGenericQuantitySample", 371 | HKQuantityTypeIdentifierDietaryProtein: @"OMHSerializerGenericQuantitySample", 372 | HKQuantityTypeIdentifierDietaryRiboflavin: @"OMHSerializerGenericQuantitySample", 373 | HKQuantityTypeIdentifierDietarySelenium: @"OMHSerializerGenericQuantitySample", 374 | HKQuantityTypeIdentifierDietarySodium: @"OMHSerializerGenericQuantitySample", 375 | HKQuantityTypeIdentifierDietarySugar: @"OMHSerializerGenericQuantitySample", 376 | HKQuantityTypeIdentifierDietaryThiamin: @"OMHSerializerGenericQuantitySample", 377 | HKQuantityTypeIdentifierDietaryVitaminA: @"OMHSerializerGenericQuantitySample", 378 | HKQuantityTypeIdentifierDietaryVitaminB12: @"OMHSerializerGenericQuantitySample", 379 | HKQuantityTypeIdentifierDietaryVitaminB6: @"OMHSerializerGenericQuantitySample", 380 | HKQuantityTypeIdentifierDietaryVitaminC: @"OMHSerializerGenericQuantitySample", 381 | HKQuantityTypeIdentifierDietaryVitaminD: @"OMHSerializerGenericQuantitySample", 382 | HKQuantityTypeIdentifierDietaryVitaminE: @"OMHSerializerGenericQuantitySample", 383 | HKQuantityTypeIdentifierDietaryVitaminK: @"OMHSerializerGenericQuantitySample", 384 | HKQuantityTypeIdentifierDietaryWater: @"OMHSerializerGenericQuantitySample", 385 | HKQuantityTypeIdentifierDietaryZinc: @"OMHSerializerGenericQuantitySample", 386 | HKQuantityTypeIdentifierDistanceCycling: @"OMHSerializerGenericQuantitySample", 387 | HKQuantityTypeIdentifierDistanceWalkingRunning: @"OMHSerializerGenericQuantitySample", 388 | HKQuantityTypeIdentifierElectrodermalActivity: @"OMHSerializerGenericQuantitySample", 389 | HKQuantityTypeIdentifierFlightsClimbed: @"OMHSerializerGenericQuantitySample", 390 | HKQuantityTypeIdentifierForcedExpiratoryVolume1: @"OMHSerializerGenericQuantitySample", 391 | HKQuantityTypeIdentifierForcedVitalCapacity: @"OMHSerializerGenericQuantitySample", 392 | HKQuantityTypeIdentifierHeight : @"OMHSerializerHeight", 393 | HKQuantityTypeIdentifierHeartRate : @"OMHSerializerHeartRate", 394 | HKQuantityTypeIdentifierInhalerUsage: @"OMHSerializerGenericQuantitySample", 395 | HKQuantityTypeIdentifierLeanBodyMass: @"OMHSerializerGenericQuantitySample", 396 | HKQuantityTypeIdentifierNikeFuel: @"OMHSerializerGenericQuantitySample", 397 | HKQuantityTypeIdentifierNumberOfTimesFallen: @"OMHSerializerGenericQuantitySample", 398 | HKQuantityTypeIdentifierOxygenSaturation: @"OMHSerializerOxygenSaturation", 399 | HKQuantityTypeIdentifierPeakExpiratoryFlowRate: @"OMHSerializerGenericQuantitySample", 400 | HKQuantityTypeIdentifierPeripheralPerfusionIndex: @"OMHSerializerGenericQuantitySample", 401 | HKQuantityTypeIdentifierRespiratoryRate: @"OMHSerializerRespiratoryRate", 402 | HKQuantityTypeIdentifierStepCount : @"OMHSerializerStepCount", 403 | HKQuantityTypeIdentifierUVExposure: @"OMHSerializerGenericQuantitySample" 404 | }; 405 | } 406 | return allQuantityTypeIdsToClasses; 407 | } 408 | 409 | @end 410 | 411 | -------------------------------------------------------------------------------- /Example/Granola.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 2E945AD21B753D1400A1FEB0 /* NSDate+RFC3339Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2E945AD11B753D1400A1FEB0 /* NSDate+RFC3339Tests.m */; }; 11 | 2EAAF0371C44CB1300897920 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EAAF0361C44CB1300897920 /* AppDelegate.swift */; }; 12 | 2EAAF0391C44CB1300897920 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EAAF0381C44CB1300897920 /* ViewController.swift */; }; 13 | 2EAAF03C1C44CB1300897920 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2EAAF03A1C44CB1300897920 /* Main.storyboard */; }; 14 | 2EAAF03E1C44CB1300897920 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2EAAF03D1C44CB1300897920 /* Assets.xcassets */; }; 15 | 2EAAF0411C44CB1300897920 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2EAAF03F1C44CB1300897920 /* LaunchScreen.storyboard */; }; 16 | 469369ACEB2BC8FFF67D5A3D /* libPods-Granola.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6225ED718EED723BF396A7F1 /* libPods-Granola.a */; }; 17 | 6003F5B0195388D20070C39A /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F5AF195388D20070C39A /* XCTest.framework */; }; 18 | 6003F5B1195388D20070C39A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; }; 19 | 6003F5B2195388D20070C39A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; }; 20 | 6003F5BA195388D20070C39A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6003F5B8195388D20070C39A /* InfoPlist.strings */; }; 21 | 6003F5BC195388D20070C39A /* OMHSerializerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6003F5BB195388D20070C39A /* OMHSerializerTests.m */; }; 22 | 9E272AB11ADEFF1B007DE4ED /* OMHSampleFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = 9E272AB01ADEFF1B007DE4ED /* OMHSampleFactory.m */; }; 23 | 9E272AB41ADF523F007DE4ED /* OMHSchemaStore.m in Sources */ = {isa = PBXBuildFile; fileRef = 9E272AB31ADF523F007DE4ED /* OMHSchemaStore.m */; }; 24 | 9ECA505C1AD60A3000E367CA /* schema in Resources */ = {isa = PBXBuildFile; fileRef = 9ECA505B1AD60A3000E367CA /* schema */; }; 25 | /* End PBXBuildFile section */ 26 | 27 | /* Begin PBXFileReference section */ 28 | 0713970833C99FFE3A26765F /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; 29 | 1497BA18A434AE1495648DE8 /* Granola.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = Granola.podspec; path = ../Granola.podspec; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; 30 | 2E945AD11B753D1400A1FEB0 /* NSDate+RFC3339Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDate+RFC3339Tests.m"; sourceTree = ""; }; 31 | 2EAAF0341C44CB1300897920 /* Granola Test Swift.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Granola Test Swift.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 32 | 2EAAF0361C44CB1300897920 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33 | 2EAAF0381C44CB1300897920 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 34 | 2EAAF03B1C44CB1300897920 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 35 | 2EAAF03D1C44CB1300897920 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 36 | 2EAAF0401C44CB1300897920 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 37 | 2EAAF0421C44CB1300897920 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 38 | 5F5286D8580E554F428C1841 /* Pods-Granola.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Granola.debug.xcconfig"; path = "Target Support Files/Pods-Granola/Pods-Granola.debug.xcconfig"; sourceTree = ""; }; 39 | 6003F58A195388D20070C39A /* Granola.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Granola.app; sourceTree = BUILT_PRODUCTS_DIR; }; 40 | 6003F58D195388D20070C39A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 41 | 6003F58F195388D20070C39A /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 42 | 6003F591195388D20070C39A /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 43 | 6003F5AE195388D20070C39A /* Granola.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Granola.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | 6003F5AF195388D20070C39A /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 45 | 6003F5B7195388D20070C39A /* Tests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Tests-Info.plist"; sourceTree = ""; }; 46 | 6003F5B9195388D20070C39A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 47 | 6003F5BB195388D20070C39A /* OMHSerializerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OMHSerializerTests.m; sourceTree = ""; }; 48 | 606FC2411953D9B200FFA9A0 /* Tests-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Tests-Prefix.pch"; sourceTree = ""; }; 49 | 6225ED718EED723BF396A7F1 /* libPods-Granola.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Granola.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 50 | 647C6D368DFEFC0AB9546C4F /* Pods-Granola.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Granola.release.xcconfig"; path = "Target Support Files/Pods-Granola/Pods-Granola.release.xcconfig"; sourceTree = ""; }; 51 | 9E272AAF1ADEFF1B007DE4ED /* OMHSampleFactory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OMHSampleFactory.h; sourceTree = ""; }; 52 | 9E272AB01ADEFF1B007DE4ED /* OMHSampleFactory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OMHSampleFactory.m; sourceTree = ""; }; 53 | 9E272AB21ADF523F007DE4ED /* OMHSchemaStore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OMHSchemaStore.h; sourceTree = ""; }; 54 | 9E272AB31ADF523F007DE4ED /* OMHSchemaStore.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OMHSchemaStore.m; sourceTree = ""; }; 55 | 9ECA505B1AD60A3000E367CA /* schema */ = {isa = PBXFileReference; lastKnownFileType = folder; name = schema; path = "omh-schemas/schema"; sourceTree = ""; }; 56 | 9EDEA5811ADB0B5200D3F174 /* HKObject+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "HKObject+Private.h"; sourceTree = ""; }; 57 | EF857A4BFD07184BC663D020 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 58 | /* End PBXFileReference section */ 59 | 60 | /* Begin PBXFrameworksBuildPhase section */ 61 | 2EAAF0311C44CB1300897920 /* Frameworks */ = { 62 | isa = PBXFrameworksBuildPhase; 63 | buildActionMask = 2147483647; 64 | files = ( 65 | ); 66 | runOnlyForDeploymentPostprocessing = 0; 67 | }; 68 | 6003F5AB195388D20070C39A /* Frameworks */ = { 69 | isa = PBXFrameworksBuildPhase; 70 | buildActionMask = 2147483647; 71 | files = ( 72 | 6003F5B0195388D20070C39A /* XCTest.framework in Frameworks */, 73 | 6003F5B2195388D20070C39A /* UIKit.framework in Frameworks */, 74 | 6003F5B1195388D20070C39A /* Foundation.framework in Frameworks */, 75 | 469369ACEB2BC8FFF67D5A3D /* libPods-Granola.a in Frameworks */, 76 | ); 77 | runOnlyForDeploymentPostprocessing = 0; 78 | }; 79 | /* End PBXFrameworksBuildPhase section */ 80 | 81 | /* Begin PBXGroup section */ 82 | 2EAAF0351C44CB1300897920 /* Granola Test Swift */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | 2EAAF0361C44CB1300897920 /* AppDelegate.swift */, 86 | 2EAAF0381C44CB1300897920 /* ViewController.swift */, 87 | 2EAAF03A1C44CB1300897920 /* Main.storyboard */, 88 | 2EAAF03D1C44CB1300897920 /* Assets.xcassets */, 89 | 2EAAF03F1C44CB1300897920 /* LaunchScreen.storyboard */, 90 | 2EAAF0421C44CB1300897920 /* Info.plist */, 91 | ); 92 | path = "Granola Test Swift"; 93 | sourceTree = ""; 94 | }; 95 | 6003F581195388D10070C39A = { 96 | isa = PBXGroup; 97 | children = ( 98 | 60FF7A9C1954A5C5007DD14C /* Podspec Metadata */, 99 | 6003F5B5195388D20070C39A /* Tests */, 100 | 2EAAF0351C44CB1300897920 /* Granola Test Swift */, 101 | 6003F58C195388D20070C39A /* Frameworks */, 102 | 6003F58B195388D20070C39A /* Products */, 103 | 8B68C31EFCA7C1245D73EC64 /* Pods */, 104 | ); 105 | sourceTree = ""; 106 | }; 107 | 6003F58B195388D20070C39A /* Products */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | 6003F58A195388D20070C39A /* Granola.app */, 111 | 6003F5AE195388D20070C39A /* Granola.xctest */, 112 | 2EAAF0341C44CB1300897920 /* Granola Test Swift.app */, 113 | ); 114 | name = Products; 115 | sourceTree = ""; 116 | }; 117 | 6003F58C195388D20070C39A /* Frameworks */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | 6003F58D195388D20070C39A /* Foundation.framework */, 121 | 6003F58F195388D20070C39A /* CoreGraphics.framework */, 122 | 6003F591195388D20070C39A /* UIKit.framework */, 123 | 6003F5AF195388D20070C39A /* XCTest.framework */, 124 | 6225ED718EED723BF396A7F1 /* libPods-Granola.a */, 125 | ); 126 | name = Frameworks; 127 | sourceTree = ""; 128 | }; 129 | 6003F5B5195388D20070C39A /* Tests */ = { 130 | isa = PBXGroup; 131 | children = ( 132 | 6003F5BB195388D20070C39A /* OMHSerializerTests.m */, 133 | 9EDEA5811ADB0B5200D3F174 /* HKObject+Private.h */, 134 | 9E272AAF1ADEFF1B007DE4ED /* OMHSampleFactory.h */, 135 | 9E272AB01ADEFF1B007DE4ED /* OMHSampleFactory.m */, 136 | 9E272AB21ADF523F007DE4ED /* OMHSchemaStore.h */, 137 | 9E272AB31ADF523F007DE4ED /* OMHSchemaStore.m */, 138 | 2E945AD11B753D1400A1FEB0 /* NSDate+RFC3339Tests.m */, 139 | 6003F5B6195388D20070C39A /* Supporting Files */, 140 | ); 141 | path = Tests; 142 | sourceTree = ""; 143 | }; 144 | 6003F5B6195388D20070C39A /* Supporting Files */ = { 145 | isa = PBXGroup; 146 | children = ( 147 | 9ECA505B1AD60A3000E367CA /* schema */, 148 | 6003F5B7195388D20070C39A /* Tests-Info.plist */, 149 | 6003F5B8195388D20070C39A /* InfoPlist.strings */, 150 | 606FC2411953D9B200FFA9A0 /* Tests-Prefix.pch */, 151 | ); 152 | name = "Supporting Files"; 153 | sourceTree = ""; 154 | }; 155 | 60FF7A9C1954A5C5007DD14C /* Podspec Metadata */ = { 156 | isa = PBXGroup; 157 | children = ( 158 | 1497BA18A434AE1495648DE8 /* Granola.podspec */, 159 | EF857A4BFD07184BC663D020 /* README.md */, 160 | 0713970833C99FFE3A26765F /* LICENSE */, 161 | ); 162 | name = "Podspec Metadata"; 163 | sourceTree = ""; 164 | }; 165 | 8B68C31EFCA7C1245D73EC64 /* Pods */ = { 166 | isa = PBXGroup; 167 | children = ( 168 | 5F5286D8580E554F428C1841 /* Pods-Granola.debug.xcconfig */, 169 | 647C6D368DFEFC0AB9546C4F /* Pods-Granola.release.xcconfig */, 170 | ); 171 | path = Pods; 172 | sourceTree = ""; 173 | }; 174 | /* End PBXGroup section */ 175 | 176 | /* Begin PBXNativeTarget section */ 177 | 2EAAF0331C44CB1300897920 /* Granola Test Swift */ = { 178 | isa = PBXNativeTarget; 179 | buildConfigurationList = 2EAAF0431C44CB1300897920 /* Build configuration list for PBXNativeTarget "Granola Test Swift" */; 180 | buildPhases = ( 181 | 2EAAF0301C44CB1300897920 /* Sources */, 182 | 2EAAF0311C44CB1300897920 /* Frameworks */, 183 | 2EAAF0321C44CB1300897920 /* Resources */, 184 | ); 185 | buildRules = ( 186 | ); 187 | dependencies = ( 188 | ); 189 | name = "Granola Test Swift"; 190 | productName = "Granola Test Swift"; 191 | productReference = 2EAAF0341C44CB1300897920 /* Granola Test Swift.app */; 192 | productType = "com.apple.product-type.application"; 193 | }; 194 | 6003F5AD195388D20070C39A /* Granola */ = { 195 | isa = PBXNativeTarget; 196 | buildConfigurationList = 6003F5C2195388D20070C39A /* Build configuration list for PBXNativeTarget "Granola" */; 197 | buildPhases = ( 198 | 8511FFFB80DACB7BD3C256D2 /* [CP] Check Pods Manifest.lock */, 199 | 6003F5AA195388D20070C39A /* Sources */, 200 | 6003F5AB195388D20070C39A /* Frameworks */, 201 | 6003F5AC195388D20070C39A /* Resources */, 202 | ); 203 | buildRules = ( 204 | ); 205 | dependencies = ( 206 | ); 207 | name = Granola; 208 | productName = GranolaTests; 209 | productReference = 6003F5AE195388D20070C39A /* Granola.xctest */; 210 | productType = "com.apple.product-type.bundle.unit-test"; 211 | }; 212 | /* End PBXNativeTarget section */ 213 | 214 | /* Begin PBXProject section */ 215 | 6003F582195388D10070C39A /* Project object */ = { 216 | isa = PBXProject; 217 | attributes = { 218 | CLASSPREFIX = OMH; 219 | LastSwiftUpdateCheck = 0710; 220 | LastUpgradeCheck = 0510; 221 | ORGANIZATIONNAME = "Open mHealth"; 222 | TargetAttributes = { 223 | 2EAAF0331C44CB1300897920 = { 224 | CreatedOnToolsVersion = 7.1.1; 225 | }; 226 | 6003F5AD195388D20070C39A = { 227 | TestTargetID = 6003F589195388D20070C39A; 228 | }; 229 | }; 230 | }; 231 | buildConfigurationList = 6003F585195388D10070C39A /* Build configuration list for PBXProject "Granola" */; 232 | compatibilityVersion = "Xcode 3.2"; 233 | developmentRegion = English; 234 | hasScannedForEncodings = 0; 235 | knownRegions = ( 236 | English, 237 | en, 238 | Base, 239 | ); 240 | mainGroup = 6003F581195388D10070C39A; 241 | productRefGroup = 6003F58B195388D20070C39A /* Products */; 242 | projectDirPath = ""; 243 | projectRoot = ""; 244 | targets = ( 245 | 6003F5AD195388D20070C39A /* Granola */, 246 | 2EAAF0331C44CB1300897920 /* Granola Test Swift */, 247 | ); 248 | }; 249 | /* End PBXProject section */ 250 | 251 | /* Begin PBXResourcesBuildPhase section */ 252 | 2EAAF0321C44CB1300897920 /* Resources */ = { 253 | isa = PBXResourcesBuildPhase; 254 | buildActionMask = 2147483647; 255 | files = ( 256 | 2EAAF0411C44CB1300897920 /* LaunchScreen.storyboard in Resources */, 257 | 2EAAF03E1C44CB1300897920 /* Assets.xcassets in Resources */, 258 | 2EAAF03C1C44CB1300897920 /* Main.storyboard in Resources */, 259 | ); 260 | runOnlyForDeploymentPostprocessing = 0; 261 | }; 262 | 6003F5AC195388D20070C39A /* Resources */ = { 263 | isa = PBXResourcesBuildPhase; 264 | buildActionMask = 2147483647; 265 | files = ( 266 | 6003F5BA195388D20070C39A /* InfoPlist.strings in Resources */, 267 | 9ECA505C1AD60A3000E367CA /* schema in Resources */, 268 | ); 269 | runOnlyForDeploymentPostprocessing = 0; 270 | }; 271 | /* End PBXResourcesBuildPhase section */ 272 | 273 | /* Begin PBXShellScriptBuildPhase section */ 274 | 8511FFFB80DACB7BD3C256D2 /* [CP] Check Pods Manifest.lock */ = { 275 | isa = PBXShellScriptBuildPhase; 276 | buildActionMask = 2147483647; 277 | files = ( 278 | ); 279 | inputFileListPaths = ( 280 | ); 281 | inputPaths = ( 282 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 283 | "${PODS_ROOT}/Manifest.lock", 284 | ); 285 | name = "[CP] Check Pods Manifest.lock"; 286 | outputFileListPaths = ( 287 | ); 288 | outputPaths = ( 289 | "$(DERIVED_FILE_DIR)/Pods-Granola-checkManifestLockResult.txt", 290 | ); 291 | runOnlyForDeploymentPostprocessing = 0; 292 | shellPath = /bin/sh; 293 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 294 | showEnvVarsInLog = 0; 295 | }; 296 | /* End PBXShellScriptBuildPhase section */ 297 | 298 | /* Begin PBXSourcesBuildPhase section */ 299 | 2EAAF0301C44CB1300897920 /* Sources */ = { 300 | isa = PBXSourcesBuildPhase; 301 | buildActionMask = 2147483647; 302 | files = ( 303 | 2EAAF0391C44CB1300897920 /* ViewController.swift in Sources */, 304 | 2EAAF0371C44CB1300897920 /* AppDelegate.swift in Sources */, 305 | ); 306 | runOnlyForDeploymentPostprocessing = 0; 307 | }; 308 | 6003F5AA195388D20070C39A /* Sources */ = { 309 | isa = PBXSourcesBuildPhase; 310 | buildActionMask = 2147483647; 311 | files = ( 312 | 6003F5BC195388D20070C39A /* OMHSerializerTests.m in Sources */, 313 | 9E272AB11ADEFF1B007DE4ED /* OMHSampleFactory.m in Sources */, 314 | 2E945AD21B753D1400A1FEB0 /* NSDate+RFC3339Tests.m in Sources */, 315 | 9E272AB41ADF523F007DE4ED /* OMHSchemaStore.m in Sources */, 316 | ); 317 | runOnlyForDeploymentPostprocessing = 0; 318 | }; 319 | /* End PBXSourcesBuildPhase section */ 320 | 321 | /* Begin PBXVariantGroup section */ 322 | 2EAAF03A1C44CB1300897920 /* Main.storyboard */ = { 323 | isa = PBXVariantGroup; 324 | children = ( 325 | 2EAAF03B1C44CB1300897920 /* Base */, 326 | ); 327 | name = Main.storyboard; 328 | sourceTree = ""; 329 | }; 330 | 2EAAF03F1C44CB1300897920 /* LaunchScreen.storyboard */ = { 331 | isa = PBXVariantGroup; 332 | children = ( 333 | 2EAAF0401C44CB1300897920 /* Base */, 334 | ); 335 | name = LaunchScreen.storyboard; 336 | sourceTree = ""; 337 | }; 338 | 6003F5B8195388D20070C39A /* InfoPlist.strings */ = { 339 | isa = PBXVariantGroup; 340 | children = ( 341 | 6003F5B9195388D20070C39A /* en */, 342 | ); 343 | name = InfoPlist.strings; 344 | sourceTree = ""; 345 | }; 346 | /* End PBXVariantGroup section */ 347 | 348 | /* Begin XCBuildConfiguration section */ 349 | 2EAAF0441C44CB1300897920 /* Debug */ = { 350 | isa = XCBuildConfiguration; 351 | buildSettings = { 352 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 353 | CLANG_WARN_UNREACHABLE_CODE = YES; 354 | DEBUG_INFORMATION_FORMAT = dwarf; 355 | ENABLE_STRICT_OBJC_MSGSEND = YES; 356 | ENABLE_TESTABILITY = YES; 357 | GCC_NO_COMMON_BLOCKS = YES; 358 | INFOPLIST_FILE = "Granola Test Swift/Info.plist"; 359 | IPHONEOS_DEPLOYMENT_TARGET = 9.1; 360 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 361 | MTL_ENABLE_DEBUG_INFO = YES; 362 | PRODUCT_BUNDLE_IDENTIFIER = "omh.Granola-Test-Swift"; 363 | PRODUCT_NAME = "$(TARGET_NAME)"; 364 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 365 | }; 366 | name = Debug; 367 | }; 368 | 2EAAF0451C44CB1300897920 /* Release */ = { 369 | isa = XCBuildConfiguration; 370 | buildSettings = { 371 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 372 | CLANG_WARN_UNREACHABLE_CODE = YES; 373 | COPY_PHASE_STRIP = NO; 374 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 375 | ENABLE_STRICT_OBJC_MSGSEND = YES; 376 | GCC_NO_COMMON_BLOCKS = YES; 377 | INFOPLIST_FILE = "Granola Test Swift/Info.plist"; 378 | IPHONEOS_DEPLOYMENT_TARGET = 9.1; 379 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 380 | MTL_ENABLE_DEBUG_INFO = NO; 381 | PRODUCT_BUNDLE_IDENTIFIER = "omh.Granola-Test-Swift"; 382 | PRODUCT_NAME = "$(TARGET_NAME)"; 383 | }; 384 | name = Release; 385 | }; 386 | 6003F5BD195388D20070C39A /* Debug */ = { 387 | isa = XCBuildConfiguration; 388 | buildSettings = { 389 | ALWAYS_SEARCH_USER_PATHS = NO; 390 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 391 | CLANG_CXX_LIBRARY = "libc++"; 392 | CLANG_ENABLE_MODULES = YES; 393 | CLANG_ENABLE_OBJC_ARC = YES; 394 | CLANG_WARN_BOOL_CONVERSION = YES; 395 | CLANG_WARN_CONSTANT_CONVERSION = YES; 396 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 397 | CLANG_WARN_EMPTY_BODY = YES; 398 | CLANG_WARN_ENUM_CONVERSION = YES; 399 | CLANG_WARN_INT_CONVERSION = YES; 400 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 401 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 402 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 403 | COPY_PHASE_STRIP = NO; 404 | GCC_C_LANGUAGE_STANDARD = gnu99; 405 | GCC_DYNAMIC_NO_PIC = NO; 406 | GCC_OPTIMIZATION_LEVEL = 0; 407 | GCC_PREPROCESSOR_DEFINITIONS = ( 408 | "DEBUG=1", 409 | "$(inherited)", 410 | ); 411 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 412 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 413 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 414 | GCC_WARN_UNDECLARED_SELECTOR = YES; 415 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 416 | GCC_WARN_UNUSED_FUNCTION = YES; 417 | GCC_WARN_UNUSED_VARIABLE = YES; 418 | IPHONEOS_DEPLOYMENT_TARGET = 7.1; 419 | ONLY_ACTIVE_ARCH = YES; 420 | SDKROOT = iphoneos; 421 | TARGETED_DEVICE_FAMILY = "1,2"; 422 | }; 423 | name = Debug; 424 | }; 425 | 6003F5BE195388D20070C39A /* Release */ = { 426 | isa = XCBuildConfiguration; 427 | buildSettings = { 428 | ALWAYS_SEARCH_USER_PATHS = NO; 429 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 430 | CLANG_CXX_LIBRARY = "libc++"; 431 | CLANG_ENABLE_MODULES = YES; 432 | CLANG_ENABLE_OBJC_ARC = YES; 433 | CLANG_WARN_BOOL_CONVERSION = YES; 434 | CLANG_WARN_CONSTANT_CONVERSION = YES; 435 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 436 | CLANG_WARN_EMPTY_BODY = YES; 437 | CLANG_WARN_ENUM_CONVERSION = YES; 438 | CLANG_WARN_INT_CONVERSION = YES; 439 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 440 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 441 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 442 | COPY_PHASE_STRIP = YES; 443 | ENABLE_NS_ASSERTIONS = NO; 444 | GCC_C_LANGUAGE_STANDARD = gnu99; 445 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 446 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 447 | GCC_WARN_UNDECLARED_SELECTOR = YES; 448 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 449 | GCC_WARN_UNUSED_FUNCTION = YES; 450 | GCC_WARN_UNUSED_VARIABLE = YES; 451 | IPHONEOS_DEPLOYMENT_TARGET = 7.1; 452 | SDKROOT = iphoneos; 453 | TARGETED_DEVICE_FAMILY = "1,2"; 454 | VALIDATE_PRODUCT = YES; 455 | }; 456 | name = Release; 457 | }; 458 | 6003F5C3195388D20070C39A /* Debug */ = { 459 | isa = XCBuildConfiguration; 460 | baseConfigurationReference = 5F5286D8580E554F428C1841 /* Pods-Granola.debug.xcconfig */; 461 | buildSettings = { 462 | FRAMEWORK_SEARCH_PATHS = ( 463 | "$(SDKROOT)/Developer/Library/Frameworks", 464 | "$(inherited)", 465 | "$(DEVELOPER_FRAMEWORKS_DIR)", 466 | ); 467 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 468 | GCC_PREFIX_HEADER = "Tests/Tests-Prefix.pch"; 469 | GCC_PREPROCESSOR_DEFINITIONS = ( 470 | "DEBUG=1", 471 | "$(inherited)", 472 | ); 473 | INFOPLIST_FILE = "Tests/Tests-Info.plist"; 474 | PRODUCT_NAME = "$(TARGET_NAME)"; 475 | TEST_HOST = "$(BUNDLE_LOADER)"; 476 | WRAPPER_EXTENSION = xctest; 477 | }; 478 | name = Debug; 479 | }; 480 | 6003F5C4195388D20070C39A /* Release */ = { 481 | isa = XCBuildConfiguration; 482 | baseConfigurationReference = 647C6D368DFEFC0AB9546C4F /* Pods-Granola.release.xcconfig */; 483 | buildSettings = { 484 | FRAMEWORK_SEARCH_PATHS = ( 485 | "$(SDKROOT)/Developer/Library/Frameworks", 486 | "$(inherited)", 487 | "$(DEVELOPER_FRAMEWORKS_DIR)", 488 | ); 489 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 490 | GCC_PREFIX_HEADER = "Tests/Tests-Prefix.pch"; 491 | INFOPLIST_FILE = "Tests/Tests-Info.plist"; 492 | PRODUCT_NAME = "$(TARGET_NAME)"; 493 | TEST_HOST = "$(BUNDLE_LOADER)"; 494 | WRAPPER_EXTENSION = xctest; 495 | }; 496 | name = Release; 497 | }; 498 | /* End XCBuildConfiguration section */ 499 | 500 | /* Begin XCConfigurationList section */ 501 | 2EAAF0431C44CB1300897920 /* Build configuration list for PBXNativeTarget "Granola Test Swift" */ = { 502 | isa = XCConfigurationList; 503 | buildConfigurations = ( 504 | 2EAAF0441C44CB1300897920 /* Debug */, 505 | 2EAAF0451C44CB1300897920 /* Release */, 506 | ); 507 | defaultConfigurationIsVisible = 0; 508 | defaultConfigurationName = Release; 509 | }; 510 | 6003F585195388D10070C39A /* Build configuration list for PBXProject "Granola" */ = { 511 | isa = XCConfigurationList; 512 | buildConfigurations = ( 513 | 6003F5BD195388D20070C39A /* Debug */, 514 | 6003F5BE195388D20070C39A /* Release */, 515 | ); 516 | defaultConfigurationIsVisible = 0; 517 | defaultConfigurationName = Release; 518 | }; 519 | 6003F5C2195388D20070C39A /* Build configuration list for PBXNativeTarget "Granola" */ = { 520 | isa = XCConfigurationList; 521 | buildConfigurations = ( 522 | 6003F5C3195388D20070C39A /* Debug */, 523 | 6003F5C4195388D20070C39A /* Release */, 524 | ); 525 | defaultConfigurationIsVisible = 0; 526 | defaultConfigurationName = Release; 527 | }; 528 | /* End XCConfigurationList section */ 529 | }; 530 | rootObject = 6003F582195388D10070C39A /* Project object */; 531 | } 532 | -------------------------------------------------------------------------------- /Pod/Classes/OMHSerializer.m: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Open mHealth 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #import "OMHSerializer.h" 18 | #import "NSDate+RFC3339.h" 19 | #import 20 | #import "OMHHealthKitConstantsMapper.h" 21 | 22 | @interface OMHSerializer() 23 | @property (nonatomic, retain) HKSample* sample; 24 | + (BOOL)canSerialize:(HKSample*)sample error:(NSError**)error; 25 | + (NSException*)unimplementedException; 26 | @end 27 | 28 | 29 | @implementation OMHSerializer 30 | 31 | + (NSArray*)supportedTypeIdentifiersWithOMHSchema { 32 | static NSArray* OMHSchemaTypeIds = nil; 33 | if(OMHSchemaTypeIds == nil){ 34 | OMHSchemaTypeIds = @[ 35 | HKQuantityTypeIdentifierHeight, 36 | HKQuantityTypeIdentifierBodyMass, 37 | HKQuantityTypeIdentifierStepCount, 38 | HKQuantityTypeIdentifierHeartRate, 39 | HKQuantityTypeIdentifierBloodGlucose, 40 | HKQuantityTypeIdentifierActiveEnergyBurned, 41 | HKQuantityTypeIdentifierBasalEnergyBurned, 42 | HKQuantityTypeIdentifierBodyMassIndex, 43 | HKQuantityTypeIdentifierBodyFatPercentage, 44 | HKQuantityTypeIdentifierOxygenSaturation, 45 | HKQuantityTypeIdentifierRespiratoryRate, 46 | HKQuantityTypeIdentifierBodyTemperature, 47 | HKQuantityTypeIdentifierBasalBodyTemperature, 48 | HKCategoryTypeIdentifierSleepAnalysis, //Samples with Asleep value use this serializer, samples with InBed value use generic category serializer 49 | HKCorrelationTypeIdentifierBloodPressure 50 | ]; 51 | 52 | } 53 | return OMHSchemaTypeIds; 54 | } 55 | 56 | + (NSArray*)supportedTypeIdentifiers { 57 | return [[OMHHealthKitConstantsMapper allSupportedTypeIdentifiersToClasses] allKeys]; 58 | } 59 | 60 | + (NSString*)getCategoryValueForTypeWithValue: (HKCategoryType*) categoryType categoryValue:(NSInteger)categoryValue { 61 | 62 | if ( [categoryType.description isEqualToString:HKCategoryTypeIdentifierAppleStandHour.description] ) { 63 | return [OMHHealthKitConstantsMapper stringForHKAppleStandHourValue:(int)categoryValue]; 64 | } 65 | else if ([categoryType.description isEqualToString:HKCategoryTypeIdentifierSleepAnalysis.description]) { 66 | return [OMHHealthKitConstantsMapper stringForHKSleepAnalysisValue:(int)categoryValue]; 67 | } 68 | else if ([categoryType.description isEqualToString:HKCategoryTypeIdentifierCervicalMucusQuality.description]) { 69 | return [OMHHealthKitConstantsMapper stringForHKCervicalMucusQualityValue:(int)categoryValue]; 70 | } 71 | else if ([categoryType.description isEqualToString:HKCategoryTypeIdentifierIntermenstrualBleeding]) { 72 | /* Samples of this type represent the presence of intermenstrual bleeding and as such does not have a categorical value. HealthKit 73 | specifies that the value field for this type is "HKCategoryValueNotApplicable" which is a nonsensical value, so we use the name 74 | of the represented measure as the value. */ 75 | return @"Intermenstrual bleeding"; 76 | } 77 | else if ([categoryType.description isEqualToString:HKCategoryTypeIdentifierMindfulSession]) { 78 | /* Samples of this type also use "HKCategoryValueNotApplicable". 79 | https://developer.apple.com/documentation/healthkit/hkcategorytypeidentifiermindfulsession?language=objc 80 | */ 81 | return @"Mindful session"; 82 | } 83 | else if ([categoryType.description isEqualToString:HKCategoryTypeIdentifierMenstrualFlow]) { 84 | return [OMHHealthKitConstantsMapper stringForHKMenstrualFlowValue:(int)categoryValue]; 85 | } 86 | else if ([categoryType.description isEqualToString:HKCategoryTypeIdentifierOvulationTestResult]) { 87 | return [OMHHealthKitConstantsMapper stringForHKOvulationTestResultValue:(int)categoryValue]; 88 | } 89 | else if ([categoryType.description isEqualToString:HKCategoryTypeIdentifierSexualActivity]) { 90 | /* Samples of this type represent times during which sexual activity occurred. This means that during the time frame of each 91 | sample, sexual activity was occurring. As such, this measure does not have a categorical value. HealthKit specifies that the 92 | value field for this type is "HKCategoryValueNotApplicable" which is a nonsensical value, so we use the name of the represented 93 | measure as the value. */ 94 | return @"Sexual activity"; 95 | } 96 | else{ 97 | NSException *e = [NSException 98 | exceptionWithName:@"InvalidHKCategoryType" 99 | reason:@"Incorrect category type parameter for method." 100 | userInfo:nil]; 101 | @throw e; 102 | } 103 | 104 | } 105 | 106 | + (BOOL)canSerialize:(HKSample*)sample error:(NSError**)error { 107 | @throw [self unimplementedException]; 108 | } 109 | 110 | - (id)initWithSample:(HKSample*)sample { 111 | self = [super init]; 112 | if (self) { 113 | _sample = sample; 114 | } else { 115 | return nil; 116 | } 117 | return self; 118 | } 119 | 120 | /** 121 | Serializes HealthKit samples into Open mHealth compliant JSON data points. 122 | @param sample the HealthKit sample to be serialized 123 | @param error an NSError that is passed by reference and can be checked to identify specific errors 124 | @return a formatted JSON string containing the sample's data in a format that adheres to the appropriate Open mHealth schema 125 | */ 126 | - (NSString*)jsonForSample:(HKSample*)sample error:(NSError**)error { 127 | NSParameterAssert(sample); 128 | // first, verify we support the sample type 129 | NSArray* supportedTypeIdentifiers = [[self class] supportedTypeIdentifiers]; 130 | NSString* sampleTypeIdentifier = sample.sampleType.identifier; 131 | NSString* serializerClassName; 132 | if ([supportedTypeIdentifiers includes:sampleTypeIdentifier]){ 133 | serializerClassName = [OMHHealthKitConstantsMapper allSupportedTypeIdentifiersToClasses][sampleTypeIdentifier]; 134 | } 135 | else{ 136 | if (error) { 137 | NSString* errorMessage = 138 | [NSString stringWithFormat: @"Unsupported HKSample type: %@", sampleTypeIdentifier]; 139 | NSDictionary* userInfo = @{ NSLocalizedDescriptionKey : errorMessage }; 140 | *error = [NSError errorWithDomain: OMHErrorDomain 141 | code: OMHErrorCodeUnsupportedType 142 | userInfo: userInfo]; 143 | } 144 | return nil; 145 | } 146 | // if we support it, select appropriate subclass for sample 147 | 148 | //For sleep analysis, the OMH schema does not capture an 'inBed' state, so if that value is set we need to use a generic category serializer 149 | //otherwise, it defaults to using the OMH schema for the 'asleep' state. 150 | if ([sampleTypeIdentifier isEqualToString:HKCategoryTypeIdentifierSleepAnalysis]){ 151 | HKCategorySample* categorySample = (HKCategorySample*)sample; 152 | if(categorySample.value == HKCategoryValueSleepAnalysisInBed){ 153 | serializerClassName = @"OMHSerializerGenericCategorySample"; 154 | } 155 | } 156 | Class serializerClass = NSClassFromString(serializerClassName); 157 | // subclass verifies it supports sample's values 158 | if (![serializerClass canSerialize:sample error:error]) { 159 | return nil; 160 | } 161 | // instantiate a serializer 162 | OMHSerializer* serializer = [[serializerClass alloc] initWithSample:sample]; 163 | NSData* jsonData = [NSJSONSerialization dataWithJSONObject:[serializer data] 164 | options:NSJSONWritingPrettyPrinted 165 | error:error]; 166 | if (!jsonData) { 167 | return nil; // return early if JSON serialization failed 168 | } 169 | NSString* jsonString = [[NSString alloc] initWithData:jsonData 170 | encoding:NSUTF8StringEncoding]; 171 | return jsonString; 172 | } 173 | 174 | + (NSString*)parseUnitFromQuantity:(HKQuantity*)quantity { 175 | NSArray *arrayWithSplitUnitAndValue = [quantity.description 176 | componentsSeparatedByCharactersInSet:[NSCharacterSet 177 | whitespaceCharacterSet]]; 178 | return arrayWithSplitUnitAndValue[1]; 179 | } 180 | 181 | - (NSDictionary*) populateTimeFrameProperty:(NSDate*)startDate endDate:(NSDate*)endDate { 182 | 183 | NSString* timeZoneString = [self.sample.metadata objectForKey:HKMetadataKeyTimeZone]; 184 | 185 | if (timeZoneString != nil) { 186 | NSTimeZone* timeZone = [NSTimeZone timeZoneWithName:timeZoneString]; 187 | if ([startDate isEqualToDate:endDate]) { 188 | return @{ 189 | @"date_time":[startDate RFC3339StringAtTimeZone:timeZone] 190 | }; 191 | } 192 | else { 193 | return @{ 194 | @"time_interval": @{ 195 | @"start_date_time": [startDate RFC3339StringAtTimeZone:timeZone], 196 | @"end_date_time": [endDate RFC3339StringAtTimeZone:timeZone] 197 | } 198 | }; 199 | } 200 | } 201 | 202 | if ([startDate isEqualToDate:endDate]) { 203 | return @{ 204 | @"date_time":[startDate RFC3339String] 205 | }; 206 | } 207 | else { 208 | return @{ 209 | @"time_interval": @{ 210 | @"start_date_time": [startDate RFC3339String], 211 | @"end_date_time": [endDate RFC3339String] 212 | } 213 | }; 214 | } 215 | } 216 | 217 | + (NSDictionary*) serializeMetadataArray:(NSDictionary*)metadata { 218 | if(metadata) { 219 | NSMutableArray *serializedArray = [NSMutableArray new]; 220 | for (id key in metadata) { 221 | if ([[metadata valueForKey:key] isKindOfClass:[NSDate class]]){ 222 | NSDate *dateMetadataValue = [metadata valueForKey:key]; 223 | [serializedArray addObject:@{@"key":key,@"value":[dateMetadataValue RFC3339String]}]; 224 | } 225 | else if ([[metadata valueForKey:key] isKindOfClass:[HKQuantity class]]) { 226 | // For now, ignore HKQuantity metadata values to avoid a JSON serialization ObjC exception. 227 | // See previous commit for how we might parse/add the value. That would also require schema 228 | // and version changes, which is more than we (LifeOmic) need for now. 229 | } 230 | else{ 231 | [serializedArray addObject:@{@"key":key,@"value":[metadata valueForKey:key]}]; 232 | } 233 | 234 | } 235 | return @{@"metadata":[serializedArray copy]}; 236 | } 237 | return @{}; 238 | } 239 | 240 | #pragma mark - Private 241 | 242 | - (id)data { 243 | NSDictionary *serializedBodyDictionaryWithoutMetadata = [self bodyData]; 244 | NSMutableDictionary *serializedBodyDictionaryWithMetadata = [NSMutableDictionary dictionaryWithDictionary:serializedBodyDictionaryWithoutMetadata]; 245 | [serializedBodyDictionaryWithMetadata addEntriesFromDictionary:[OMHSerializer serializeMetadataArray:self.sample.metadata]]; 246 | 247 | return @{ 248 | @"header": @{ 249 | @"id": self.sample.UUID.UUIDString, 250 | @"creation_date_time": [self.sample.startDate RFC3339String], 251 | @"schema_id": @{ 252 | @"namespace": [self schemaNamespace], 253 | @"name": [self schemaName], 254 | @"version": [self schemaVersion] 255 | }, 256 | }, 257 | @"body":serializedBodyDictionaryWithMetadata 258 | }; 259 | 260 | } 261 | 262 | - (NSString*)schemaName { 263 | @throw [[self class] unimplementedException]; 264 | } 265 | 266 | - (NSString*)schemaVersion { 267 | @throw [[self class] unimplementedException]; 268 | } 269 | 270 | - (id)bodyData { 271 | @throw [[self class] unimplementedException]; 272 | } 273 | 274 | - (NSString*)schemaNamespace { 275 | @throw [[self class] unimplementedException]; 276 | } 277 | 278 | + (NSException*)unimplementedException { 279 | return [NSException exceptionWithName:NSInternalInconsistencyException 280 | reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)] 281 | userInfo:nil]; 282 | } 283 | @end 284 | 285 | /** 286 | This serializer maps data from HKQuantityTypeIdentifierStepCount samples to JSON that conforms to the Open mHealth [step-count schema](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_step-count). 287 | */ 288 | @interface OMHSerializerStepCount : OMHSerializer; @end; 289 | @implementation OMHSerializerStepCount 290 | + (BOOL)canSerialize:(HKQuantitySample*)sample error:(NSError**)error { 291 | return YES; 292 | } 293 | - (id)bodyData { 294 | HKUnit* unit = [HKUnit unitFromString:@"count"]; 295 | double value = [[(HKQuantitySample*)self.sample quantity] doubleValueForUnit:unit]; 296 | 297 | return @{ 298 | @"step_count": [NSNumber numberWithDouble:value], 299 | @"effective_time_frame": [self populateTimeFrameProperty:self.sample.startDate endDate:self.sample.endDate] 300 | }; 301 | } 302 | - (NSString*)schemaName { 303 | return @"step-count"; 304 | } 305 | - (NSString*)schemaVersion { 306 | return @"1.0"; 307 | } 308 | - (NSString*)schemaNamespace{ 309 | return @"omh"; 310 | } 311 | @end 312 | 313 | /** 314 | This serializer maps data from HKQuantityTypeIdentifierHeight samples to JSON that conforms to the Open mHealth [body-height schema](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_body-height). 315 | */ 316 | @interface OMHSerializerHeight : OMHSerializer; @end; 317 | @implementation OMHSerializerHeight 318 | + (BOOL)canSerialize:(HKQuantitySample*)sample error:(NSError**)error { 319 | return YES; 320 | } 321 | - (id)bodyData { 322 | NSString* unitString = @"cm"; 323 | HKUnit* unit = [HKUnit unitFromString:unitString]; 324 | double value = [[(HKQuantitySample*)self.sample quantity] doubleValueForUnit:unit]; 325 | 326 | return @{ 327 | @"body_height": @{ 328 | @"value": [NSNumber numberWithDouble:value], 329 | @"unit": unitString 330 | }, 331 | @"effective_time_frame": [self populateTimeFrameProperty:self.sample.startDate endDate:self.sample.endDate] 332 | }; 333 | } 334 | - (NSString*)schemaName { 335 | return @"body-height"; 336 | } 337 | - (NSString*)schemaVersion { 338 | return @"1.0"; 339 | } 340 | - (NSString*)schemaNamespace{ 341 | return @"omh"; 342 | } 343 | @end 344 | 345 | /** 346 | This serializer maps data from HKQuantityTypeIdentifierBodyMass samples to JSON that conforms to the Open mHealth [body-weight schema](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_body-weight). 347 | */ 348 | @interface OMHSerializerWeight : OMHSerializer; @end; 349 | @implementation OMHSerializerWeight 350 | + (BOOL)canSerialize:(HKQuantitySample*)sample error:(NSError**)error { 351 | return YES; 352 | } 353 | - (id)bodyData { 354 | NSString* unitString = @"lb"; 355 | HKUnit* unit = [HKUnit unitFromString:unitString]; 356 | double value = [[(HKQuantitySample*)self.sample quantity] doubleValueForUnit:unit]; 357 | 358 | return @{ 359 | @"body_weight": @{ 360 | @"value": [NSNumber numberWithDouble:value], 361 | @"unit": unitString 362 | }, 363 | @"effective_time_frame": [self populateTimeFrameProperty:self.sample.startDate endDate:self.sample.endDate] 364 | }; 365 | } 366 | - (NSString*)schemaName { 367 | return @"body-weight"; 368 | } 369 | - (NSString*)schemaVersion { 370 | return @"1.0"; 371 | } 372 | - (NSString*)schemaNamespace{ 373 | return @"omh"; 374 | } 375 | @end 376 | 377 | /** 378 | This serializer maps data from HKQuantityTypeIdentifierHeartRate samples to JSON that conforms to the Open mHealth [heart-rate schema](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_heart-rate). 379 | */ 380 | @interface OMHSerializerHeartRate : OMHSerializer; @end; 381 | @implementation OMHSerializerHeartRate 382 | + (BOOL)canSerialize:(HKQuantitySample*)sample error:(NSError**)error { 383 | return YES; 384 | } 385 | - (id)bodyData { 386 | HKUnit* unit = [HKUnit unitFromString:@"count/min"]; 387 | double value = [[(HKQuantitySample*)self.sample quantity] doubleValueForUnit:unit]; 388 | 389 | return @{ 390 | @"heart_rate": @{ 391 | @"value": [NSNumber numberWithDouble:value], 392 | @"unit": @"beats/min" 393 | }, 394 | @"effective_time_frame":[self populateTimeFrameProperty:self.sample.startDate endDate:self.sample.endDate] 395 | }; 396 | } 397 | - (NSString*)schemaName { 398 | return @"heart-rate"; 399 | } 400 | - (NSString*)schemaVersion { 401 | return @"1.0"; 402 | } 403 | - (NSString*)schemaNamespace{ 404 | return @"omh"; 405 | } 406 | @end 407 | 408 | /** 409 | This serializer maps data from HKQuantityTypeIdentifierBloodGlucose samples to JSON that conforms to the Open mHealth [blood-glucose schema](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_blood-glucose). 410 | */ 411 | @interface OMHSerializerBloodGlucose : OMHSerializer; @end; 412 | @implementation OMHSerializerBloodGlucose 413 | + (BOOL)canSerialize:(HKQuantitySample*)sample error:(NSError**)error { 414 | return YES; 415 | } 416 | - (id)bodyData { 417 | NSString* unitString = @"mg/dL"; 418 | HKUnit* unit = [HKUnit unitFromString:unitString]; 419 | double value = [[(HKQuantitySample*)self.sample quantity] doubleValueForUnit:unit]; 420 | 421 | return @{ 422 | @"blood_glucose": @{ 423 | @"value": [NSNumber numberWithDouble:value], 424 | @"unit": unitString 425 | }, 426 | @"effective_time_frame": [self populateTimeFrameProperty:self.sample.startDate endDate:self.sample.endDate] 427 | }; 428 | } 429 | - (NSString*)schemaName { 430 | return @"blood-glucose"; 431 | } 432 | - (NSString*)schemaVersion { 433 | return @"1.0"; 434 | } 435 | - (NSString*)schemaNamespace{ 436 | return @"omh"; 437 | } 438 | @end 439 | 440 | /** 441 | This serializer maps data from HKQuantityTypeIdentifierActiveEnergyBurned samples to JSON that conforms to the Open mHealth [calories-burned schema](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_calories-burned). 442 | */ 443 | @interface OMHSerializerEnergyBurned : OMHSerializer; @end; 444 | @implementation OMHSerializerEnergyBurned 445 | + (BOOL)canSerialize:(HKQuantitySample*)sample error:(NSError**)error { 446 | return YES; 447 | } 448 | - (id)bodyData { 449 | NSString* unitString = @"kcal"; 450 | HKUnit* unit = [HKUnit unitFromString:unitString]; 451 | double value = [[(HKQuantitySample*)self.sample quantity] doubleValueForUnit:unit]; 452 | 453 | return @{ 454 | @"kcal_burned": @{ 455 | @"value": [NSNumber numberWithDouble:value], 456 | @"unit": unitString 457 | }, 458 | @"effective_time_frame": [self populateTimeFrameProperty:self.sample.startDate endDate:self.sample.endDate] 459 | 460 | }; 461 | } 462 | - (NSString*)schemaName { 463 | return @"calories-burned"; 464 | } 465 | - (NSString*)schemaVersion { 466 | return @"1.0"; 467 | } 468 | - (NSString*)schemaNamespace{ 469 | return @"omh"; 470 | } 471 | @end 472 | 473 | /** 474 | This serializer maps data from HKQuantityTypeIdentifierOxygenSaturation samples to JSON that conforms to the Open mHealth [oxygen-saturation schema](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_oxygen-saturation). 475 | */ 476 | @interface OMHSerializerOxygenSaturation : OMHSerializer; @end; 477 | @implementation OMHSerializerOxygenSaturation 478 | + (BOOL)canSerialize:(HKQuantitySample*)sample error:(NSError**)error { 479 | return YES; 480 | } 481 | - (id)bodyData { 482 | NSString* unitString = @"%"; 483 | HKUnit* unit = [HKUnit unitFromString:unitString]; 484 | double value = [[(HKQuantitySample*)self.sample quantity] doubleValueForUnit:unit]; 485 | 486 | return @{ 487 | @"oxygen_saturation": @{ 488 | @"value": [NSNumber numberWithDouble:value*100], 489 | @"unit": unitString 490 | }, 491 | @"effective_time_frame": [self populateTimeFrameProperty:self.sample.startDate endDate:self.sample.endDate] 492 | 493 | }; 494 | } 495 | - (NSString*)schemaName { 496 | return @"oxygen-saturation"; 497 | } 498 | - (NSString*)schemaVersion { 499 | return @"1.0"; 500 | } 501 | - (NSString*)schemaNamespace{ 502 | return @"omh"; 503 | } 504 | @end 505 | 506 | /** 507 | This serializer maps data from HKQuantityTypeIdentifierRespiratoryRate samples to JSON that conforms to the Open mHealth [respiratory-rate schema](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_respiratory-rate). 508 | */ 509 | @interface OMHSerializerRespiratoryRate : OMHSerializer; @end; 510 | @implementation OMHSerializerRespiratoryRate 511 | + (BOOL)canSerialize:(HKQuantitySample*)sample error:(NSError**)error { 512 | return YES; 513 | } 514 | - (id)bodyData { 515 | NSString* unitString = @"count/min"; 516 | HKUnit* unit = [HKUnit unitFromString:unitString]; 517 | float value = [[(HKQuantitySample*)self.sample quantity] doubleValueForUnit:unit]; 518 | return @{ 519 | @"respiratory_rate": @{ 520 | @"value": [NSNumber numberWithDouble:value], 521 | @"unit": @"breaths/min" 522 | }, 523 | @"effective_time_frame": [self populateTimeFrameProperty:self.sample.startDate endDate:self.sample.endDate] 524 | 525 | }; 526 | } 527 | - (NSString*)schemaName { 528 | return @"respiratory-rate"; 529 | } 530 | - (NSString*)schemaVersion { 531 | return @"1.0"; 532 | } 533 | - (NSString*)schemaNamespace{ 534 | return @"omh"; 535 | } 536 | @end 537 | 538 | /** 539 | This serializer maps data from HKQuantityTypeIdentifierBodyTemperature samples to JSON that conforms to the Open mHealth [body-temperature schema](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_body-temperature). 540 | */ 541 | @interface OMHSerializerBodyTemperature : OMHSerializer; @end; 542 | @implementation OMHSerializerBodyTemperature 543 | + (BOOL)canSerialize:(HKQuantitySample*)sample error:(NSError**)error { 544 | return YES; 545 | } 546 | - (id)bodyData { 547 | HKUnit* unit = [HKUnit degreeCelsiusUnit]; 548 | float value = [((HKQuantitySample*)self.sample).quantity doubleValueForUnit:unit]; 549 | 550 | 551 | NSMutableDictionary* serializedValues = [NSMutableDictionary dictionaryWithDictionary:@{ 552 | @"body_temperature": @{ 553 | @"value": [NSNumber numberWithDouble:value], 554 | @"unit": @"C" 555 | }, 556 | @"effective_time_frame": [self populateTimeFrameProperty:self.sample.startDate endDate:self.sample.endDate] 557 | }]; 558 | 559 | if([self.sample.sampleType.description isEqualToString:HKQuantityTypeIdentifierBasalBodyTemperature]) { 560 | BOOL userEntered = (BOOL)[self.sample.metadata objectForKey:HKMetadataKeyWasUserEntered]; 561 | if(userEntered == true){ 562 | /* Basal body temperature should be taken during sleep or immediately upon waking. It is not possible to tell whether a 563 | measurement was taken during sleep, however if the measurement was self-entered by the user then we assume they took that 564 | measurement first thing in the morning, at waking. */ 565 | [serializedValues setObject:@"on waking" forKey:@"temporal_relationship_to_sleep"]; 566 | } 567 | } 568 | 569 | NSNumber* bodyTemperatureLocation = self.sample.metadata[HKMetadataKeyBodyTemperatureSensorLocation]; 570 | if (bodyTemperatureLocation!=nil){ 571 | NSString* measurementLocationString = [self getBodyTemperatureLocationFromConstant:bodyTemperatureLocation]; 572 | if(measurementLocationString!=nil){ 573 | [serializedValues setObject:measurementLocationString forKey:@"measurement_location"]; 574 | } 575 | } 576 | 577 | return serializedValues; 578 | } 579 | - (NSString*) getBodyTemperatureLocationFromConstant:(NSNumber*)temperatureLocationConstant { 580 | 581 | switch ([temperatureLocationConstant intValue]) { 582 | case HKBodyTemperatureSensorLocationArmpit: 583 | return @"axillary"; 584 | case HKBodyTemperatureSensorLocationBody: 585 | return nil; 586 | case HKBodyTemperatureSensorLocationEar: 587 | return @"tympanic"; 588 | case HKBodyTemperatureSensorLocationEarDrum: 589 | return @"tympanic"; 590 | case HKBodyTemperatureSensorLocationFinger: 591 | return @"finger"; 592 | case HKBodyTemperatureSensorLocationForehead: 593 | return @"forehead"; 594 | case HKBodyTemperatureSensorLocationGastroIntestinal: 595 | return nil; 596 | case HKBodyTemperatureSensorLocationMouth: 597 | return @"oral"; 598 | case HKBodyTemperatureSensorLocationOther: 599 | return nil; 600 | case HKBodyTemperatureSensorLocationRectum: 601 | return @"rectal"; 602 | case HKBodyTemperatureSensorLocationTemporalArtery: 603 | return @"temporal artery"; 604 | case HKBodyTemperatureSensorLocationToe: 605 | return @"toe"; 606 | default: 607 | return nil; 608 | } 609 | } 610 | - (NSString*)schemaName { 611 | return @"body-temperature"; 612 | } 613 | - (NSString*)schemaVersion { 614 | return @"2.0"; 615 | } 616 | - (NSString*)schemaNamespace{ 617 | return @"omh"; 618 | } 619 | @end 620 | 621 | /** 622 | This serializer maps data from HKCategoryValueSleepAnalysis samples with the HKCategoryValueSleepAnalysisAsleep value to JSON that conforms to the Open mHealth [sleep-duration schema](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_sleep-duration). 623 | */ 624 | @interface OMHSerializerSleepAnalysis : OMHSerializer; @end; 625 | @implementation OMHSerializerSleepAnalysis 626 | + (BOOL)canSerialize:(HKCategorySample*)sample error:(NSError**)error { 627 | if (sample.value == HKCategoryValueSleepAnalysisAsleep) return YES; 628 | if (error) { 629 | NSString* errorMessage = 630 | @"HKCategoryValueSleepAnalysis value HKCategoryValueSleepAnalysisInBed uses OMHSerializerGenericCategorySample"; 631 | NSDictionary* userInfo = @{ NSLocalizedDescriptionKey : errorMessage }; 632 | *error = [NSError errorWithDomain: OMHErrorDomain 633 | code: OMHErrorCodeUnsupportedValues 634 | userInfo: userInfo]; 635 | } 636 | return NO; 637 | } 638 | - (id)bodyData { 639 | id value = 640 | [NSNumber numberWithFloat: 641 | [self.sample.endDate timeIntervalSinceDate:self.sample.startDate]]; 642 | return @{ 643 | @"sleep_duration": @{ 644 | @"value": value, 645 | @"unit": @"sec" 646 | }, 647 | @"effective_time_frame":[self populateTimeFrameProperty:self.sample.startDate endDate:self.sample.endDate] 648 | }; 649 | } 650 | - (NSString*)schemaName { 651 | return @"sleep-duration"; 652 | } 653 | - (NSString*)schemaVersion { 654 | return @"1.0"; 655 | } 656 | - (NSString*)schemaNamespace{ 657 | return @"omh"; 658 | } 659 | @end 660 | 661 | /** 662 | This serializer maps data from HKCategoryValueSleepAnalysis samples with the HKQuantityTypeIdentifierBodyFatPercentage value to JSON that conforms to the Open mHealth [body-fat-percentage schema](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_body-fat-percentage). 663 | */ 664 | @interface OMHSerializerBodyFatPercentage : OMHSerializer; @end; 665 | @implementation OMHSerializerBodyFatPercentage 666 | + (BOOL)canSerialize:(HKQuantitySample*)sample error:(NSError**)error { 667 | return YES; 668 | } 669 | - (id)bodyData { 670 | NSString* unitString = @"%"; 671 | HKUnit* unit = [HKUnit unitFromString:unitString]; 672 | double value = [[(HKQuantitySample*)self.sample quantity] doubleValueForUnit:unit]; 673 | return @{ 674 | @"body_fat_percentage": @{ 675 | @"value": [NSNumber numberWithDouble:value*100], 676 | @"unit": unitString 677 | }, 678 | @"effective_time_frame": [self populateTimeFrameProperty:self.sample.startDate endDate:self.sample.endDate] 679 | 680 | }; 681 | } 682 | - (NSString*)schemaName { 683 | return @"body-fat-percentage"; 684 | } 685 | - (NSString*)schemaVersion { 686 | return @"1.0"; 687 | } 688 | - (NSString*)schemaNamespace{ 689 | return @"omh"; 690 | } 691 | @end 692 | 693 | 694 | /** 695 | This serializer maps data from HKCorrelationTypeIdentifierBloodPressure samples to JSON that conforms to the Open mHealth [blood-pressure schema](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_blood-pressure). 696 | */ 697 | @interface OMHSerializerBloodPressure : OMHSerializer; @end; 698 | @implementation OMHSerializerBloodPressure 699 | + (BOOL)canSerialize:(HKCorrelation*)sample error:(NSError**)error { 700 | NSSet* samples = sample.objects; 701 | HKSample* (^firstSampleOfType)(NSString* typeIdentifier) = 702 | ^HKSample*(NSString* typeIdentifier){ 703 | return [[samples select:^BOOL(HKSample* sample) { 704 | return [[sample sampleType].identifier isEqual:typeIdentifier]; 705 | }] firstObject]; 706 | }; 707 | HKSample* systolicSample = 708 | firstSampleOfType(HKQuantityTypeIdentifierBloodPressureSystolic); 709 | HKSample* diastolicSample = 710 | firstSampleOfType(HKQuantityTypeIdentifierBloodPressureDiastolic); 711 | if (systolicSample && diastolicSample) return YES; 712 | if (error) { 713 | NSString* errorMessage = @"Missing Diastolic or Systolic sample"; 714 | NSDictionary* userInfo = @{ NSLocalizedDescriptionKey : errorMessage }; 715 | *error = [NSError errorWithDomain: OMHErrorDomain 716 | code: OMHErrorCodeUnsupportedValues 717 | userInfo: userInfo]; 718 | } 719 | return NO; 720 | } 721 | - (id)bodyData { 722 | NSString* unitString = @"mmHg"; 723 | HKUnit* unit = [HKUnit unitFromString:unitString]; 724 | HKCorrelation* sample = (HKCorrelation*)self.sample; 725 | double (^valueForFirstSampleOfType)(NSString* typeIdentifier) = 726 | ^(NSString* typeIdentifier) { 727 | HKQuantitySample* found = 728 | [[sample.objects select:^BOOL(HKSample* sample) { 729 | return [[sample sampleType].identifier isEqual:typeIdentifier]; 730 | }] firstObject]; 731 | return [found.quantity doubleValueForUnit:unit]; 732 | }; 733 | double systolicValue = 734 | valueForFirstSampleOfType(HKQuantityTypeIdentifierBloodPressureSystolic); 735 | double diastolicValue = 736 | valueForFirstSampleOfType(HKQuantityTypeIdentifierBloodPressureDiastolic); 737 | return @{ 738 | @"systolic_blood_pressure": @{ 739 | @"unit": unitString, 740 | @"value": [NSNumber numberWithDouble: systolicValue] 741 | }, 742 | @"diastolic_blood_pressure": @{ 743 | @"unit": unitString, 744 | @"value": [NSNumber numberWithDouble: diastolicValue] 745 | }, 746 | @"effective_time_frame": [self populateTimeFrameProperty:self.sample.startDate endDate:self.sample.endDate] 747 | }; 748 | } 749 | - (NSString*)schemaName { 750 | return @"blood-pressure"; 751 | } 752 | - (NSString*)schemaVersion { 753 | return @"1.0"; 754 | } 755 | - (NSString*)schemaNamespace{ 756 | return @"omh"; 757 | } 758 | @end 759 | 760 | /** 761 | This serializer maps data from HKQuantityTypeIdentifierBodyMassIndex samples to JSON that conforms to the Open mHealth [body-mass-index schema](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/omh_body-mass-index). 762 | */ 763 | @interface OMHSerializerBodyMassIndex : OMHSerializer; @end; 764 | @implementation OMHSerializerBodyMassIndex 765 | 766 | + (BOOL)canSerialize:(HKSample *)sample error:(NSError *__autoreleasing *)error { 767 | if ([sample.sampleType.description isEqualToString:HKQuantityTypeIdentifierBodyMassIndex]){ 768 | return YES; 769 | } 770 | return NO; 771 | } 772 | - (id)bodyData { 773 | HKUnit *unit = [HKUnit unitFromString:@"count"]; 774 | HKQuantitySample *quantitySample = (HKQuantitySample*)self.sample; 775 | double value = [[quantitySample quantity] doubleValueForUnit:unit]; 776 | 777 | return @{ 778 | @"body_mass_index": @{ 779 | @"value":[NSNumber numberWithDouble:value], 780 | @"unit":@"kg/m2" 781 | }, 782 | @"effective_time_frame":[self populateTimeFrameProperty:quantitySample.startDate endDate:quantitySample.endDate] 783 | }; 784 | 785 | } 786 | - (NSString*)schemaName { 787 | return @"body-mass-index"; 788 | } 789 | - (NSString*)schemaVersion { 790 | return @"1.0"; 791 | } 792 | - (NSString*)schemaNamespace{ 793 | return @"omh"; 794 | } 795 | @end 796 | 797 | /** 798 | This serializer maps data from HKQuantitySamples to JSON that conforms to the generic, Granola-specific [hk-quantity-sample schema](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-quantity-sample). 799 | 800 | This serializer is used for all quantity types that are not supported by Open mHealth curated schemas. The [supportedTypeIdentifiersWithOMHSchema]([OMHSerializer supportedTypeIdentifiersWithOMHSchema]) method provides a list of schemas that _are_ supported by Open mHealth curated schemas. 801 | */ 802 | @interface OMHSerializerGenericQuantitySample : OMHSerializer; @end; 803 | @implementation OMHSerializerGenericQuantitySample 804 | 805 | + (BOOL)canSerialize:(HKSample *)sample error:(NSError *__autoreleasing *)error { 806 | @try{ 807 | HKQuantitySample *quantitySample = (HKQuantitySample*)sample; 808 | if([[quantitySample.quantityType description] containsString:@"HKQuantityType"]){ 809 | return YES; 810 | } 811 | if (error) { 812 | NSString* errorMessage = 813 | @"HKQuantitySamples should have a quantity type that begins with 'HKQuantityType'"; 814 | NSDictionary* userInfo = @{ NSLocalizedDescriptionKey : errorMessage }; 815 | *error = [NSError errorWithDomain: OMHErrorDomain 816 | code: OMHErrorCodeIncorrectType 817 | userInfo: userInfo]; 818 | } 819 | return NO; 820 | } 821 | @catch (NSException *exception) { 822 | if (error) { 823 | NSString* errorMessage = 824 | @"OMHSerializerGenericQuantitySample is used for HKQuantitySamples only"; 825 | NSDictionary* userInfo = @{ NSLocalizedDescriptionKey : errorMessage }; 826 | *error = [NSError errorWithDomain: OMHErrorDomain 827 | code: OMHErrorCodeUnsupportedValues 828 | userInfo: userInfo]; 829 | } 830 | return NO; 831 | } 832 | } 833 | - (id)bodyData { 834 | HKQuantitySample *quantitySample = (HKQuantitySample*)self.sample; 835 | HKQuantity *quantity = [quantitySample quantity]; 836 | NSMutableDictionary *serializedUnitValues = [NSMutableDictionary new]; 837 | 838 | if ([[OMHSerializer parseUnitFromQuantity:quantity] isEqualToString:@"%"]) { 839 | 840 | // Types that use "%" units are compatible with the "count" unit (in the next condition), so this condition to pre-empts that. 841 | NSNumber* value = [NSNumber numberWithDouble:[quantity doubleValueForUnit:[HKUnit percentUnit]]]; 842 | 843 | [serializedUnitValues addEntriesFromDictionary:@{ 844 | @"unit_value":@{ 845 | @"value": @([value doubleValue] * 100), 846 | @"unit": @"%" 847 | } 848 | } 849 | ]; 850 | } 851 | else if ([quantity isCompatibleWithUnit:[HKUnit unitFromString:@"count"]]) { 852 | [serializedUnitValues addEntriesFromDictionary:@{ 853 | @"count": [NSNumber numberWithDouble:[quantity doubleValueForUnit:[HKUnit unitFromString:@"count"]]] 854 | } 855 | ]; 856 | } 857 | else{ 858 | NSString *unitString = [OMHSerializer parseUnitFromQuantity:quantity]; 859 | [serializedUnitValues addEntriesFromDictionary:@{ 860 | @"unit_value":@{ 861 | @"value": [NSNumber numberWithDouble:[quantity doubleValueForUnit:[HKUnit unitFromString:unitString]]], 862 | @"unit": unitString 863 | 864 | } 865 | } 866 | ]; 867 | } 868 | 869 | NSDictionary *partialSerializedDictionary = 870 | @{ 871 | @"quantity_type":[quantitySample quantityType].description, 872 | @"effective_time_frame":[self populateTimeFrameProperty:quantitySample.startDate endDate:quantitySample.endDate] 873 | } ; 874 | NSMutableDictionary *fullSerializedDictionary = [partialSerializedDictionary mutableCopy]; 875 | [fullSerializedDictionary addEntriesFromDictionary:serializedUnitValues]; 876 | return fullSerializedDictionary; 877 | } 878 | - (NSString*)schemaName { 879 | return @"hk-quantity-sample"; 880 | } 881 | - (NSString*)schemaVersion { 882 | return @"1.0"; 883 | } 884 | - (NSString*)schemaNamespace{ 885 | return @"granola"; 886 | } 887 | @end 888 | 889 | /** 890 | This serializer maps data from HKCategorySamples to JSON that conforms to the generic, Granola-specific [hk-category-sample schema](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-category-sample). 891 | 892 | This serializer is used for all quantity types that are not supported by Open mHealth curated schemas. The [supportedTypeIdentifiersWithOMHSchema]([OMHSerializer supportedTypeIdentifiersWithOMHSchema]) method provides a list of schemas that _are_ supported by Open mHealth curated schemas. 893 | */ 894 | @interface OMHSerializerGenericCategorySample : OMHSerializer; @end; 895 | @implementation OMHSerializerGenericCategorySample 896 | 897 | + (BOOL)canSerialize:(HKSample *)sample error:(NSError *__autoreleasing *)error { 898 | BOOL canSerialize = YES; 899 | @try{ 900 | HKCategorySample *categorySample = (HKCategorySample*) sample; 901 | NSArray* categoryTypes = [[OMHHealthKitConstantsMapper allSupportedCategoryTypeIdentifiersToClasses] allKeys]; 902 | if(![categoryTypes containsObject:categorySample.categoryType.description]){ 903 | if (error) { 904 | NSString* errorMessage = @"The category type is not currently supported."; 905 | NSDictionary* userInfo = @{ NSLocalizedDescriptionKey : errorMessage }; 906 | *error = [NSError errorWithDomain: OMHErrorDomain 907 | code: OMHErrorCodeUnsupportedType 908 | userInfo: userInfo]; 909 | } 910 | canSerialize = NO; 911 | } 912 | 913 | return canSerialize; 914 | } 915 | @catch(NSException *exception){ 916 | if (error) { 917 | NSString* errorMessage = 918 | @"OMHSerializerGenericCategorySample is used for HKCategorySamples only"; 919 | NSDictionary* userInfo = @{ NSLocalizedDescriptionKey : errorMessage }; 920 | *error = [NSError errorWithDomain: OMHErrorDomain 921 | code: OMHErrorCodeIncorrectType 922 | userInfo: userInfo]; 923 | } 924 | return NO; 925 | } 926 | } 927 | 928 | - (id)bodyData { 929 | HKCategorySample *categorySample = (HKCategorySample*) self.sample; 930 | 931 | //Error checking for correct types is done in the canSerialize method. 932 | NSString *schemaMappedValue = [OMHSerializer getCategoryValueForTypeWithValue:categorySample.categoryType categoryValue:categorySample.value]; 933 | 934 | return @{ 935 | @"effective_time_frame":[self populateTimeFrameProperty:categorySample.startDate endDate:categorySample.endDate], 936 | @"category_type": [categorySample categoryType].description, 937 | @"category_value": schemaMappedValue 938 | }; 939 | } 940 | 941 | - (NSString*)schemaName { 942 | return @"hk-category-sample"; 943 | } 944 | - (NSString*)schemaVersion { 945 | return @"1.0"; 946 | } 947 | - (NSString*)schemaNamespace{ 948 | return @"granola"; 949 | } 950 | @end 951 | 952 | /** 953 | This serializer maps data from HKCorrelation samples to JSON that conforms to the generic, Granola-specific [hk-correlation schema](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-correlation). 954 | 955 | This serializer is used for all quantity types that are not supported by Open mHealth curated schemas. The [supportedTypeIdentifiersWithOMHSchema]([OMHSerializer supportedTypeIdentifiersWithOMHSchema]) method provides a list of schemas that _are_ supported by Open mHealth curated schemas. 956 | */ 957 | @interface OMHSerializerGenericCorrelation : OMHSerializer; @end; 958 | @implementation OMHSerializerGenericCorrelation 959 | 960 | + (BOOL)canSerialize:(HKSample *)sample error:(NSError *__autoreleasing *)error { 961 | @try { 962 | HKCorrelation *correlationSample = (HKCorrelation*)sample; 963 | if(![correlationSample.correlationType.description isEqualToString:HKCorrelationTypeIdentifierBloodPressure] && 964 | ![correlationSample.correlationType.description isEqualToString:HKCorrelationTypeIdentifierFood]){ 965 | 966 | if (error) { 967 | NSString* errorMessage = 968 | [NSString stringWithFormat:@"%@ is not a supported correlation type in HealthKit",correlationSample.correlationType.description]; 969 | NSDictionary* userInfo = @{ NSLocalizedDescriptionKey : errorMessage }; 970 | *error = [NSError errorWithDomain: OMHErrorDomain 971 | code: OMHErrorCodeUnsupportedValues 972 | userInfo: userInfo]; 973 | } 974 | 975 | return NO; 976 | } 977 | } 978 | @catch (NSException *exception){ 979 | if (error) { 980 | NSString* errorMessage = 981 | @"OMHSerializerGenericCorrelation is used for HKCorrelation samples only"; 982 | NSDictionary* userInfo = @{ NSLocalizedDescriptionKey : errorMessage }; 983 | *error = [NSError errorWithDomain: OMHErrorDomain 984 | code: OMHErrorCodeIncorrectType 985 | userInfo: userInfo]; 986 | } 987 | return NO; 988 | } 989 | return YES; 990 | } 991 | 992 | - (id)bodyData { 993 | HKCorrelation *correlationSample = (HKCorrelation*)self.sample; 994 | 995 | NSMutableArray *quantitySampleArray = [NSMutableArray new]; 996 | NSMutableArray *categorySampleArray = [NSMutableArray new]; 997 | for (NSObject *sample in correlationSample.objects) { 998 | if ([sample isKindOfClass:[HKQuantitySample class]]){ 999 | //create serialized with the sample input, then call body 1000 | OMHSerializerGenericQuantitySample *quantitySampleSerializer = [OMHSerializerGenericQuantitySample new]; 1001 | quantitySampleSerializer = [quantitySampleSerializer initWithSample:(HKQuantitySample*)sample]; 1002 | NSError *error = nil; 1003 | if([OMHSerializerGenericQuantitySample canSerialize:(HKSample*)sample error:&error]){ 1004 | NSDictionary *serializedQuantitySample = (NSDictionary*)[quantitySampleSerializer bodyData]; 1005 | [quantitySampleArray addObject:serializedQuantitySample]; 1006 | } 1007 | else{ 1008 | NSLog(@"%@",[error localizedDescription]); 1009 | } 1010 | } 1011 | else if ([sample isKindOfClass:[HKCategorySample class]]){ 1012 | OMHSerializerGenericCategorySample *categorySampleSerializer = [OMHSerializerGenericCategorySample new]; 1013 | categorySampleSerializer = [categorySampleSerializer initWithSample:(HKCategorySample*)sample]; 1014 | NSError *error = nil; 1015 | if([OMHSerializerGenericCategorySample canSerialize:(HKSample*)sample error:&error]){ 1016 | NSDictionary *serializedCategorySample = (NSDictionary*)[categorySampleSerializer bodyData]; 1017 | [quantitySampleArray addObject:serializedCategorySample]; 1018 | } 1019 | else{ 1020 | NSLog(@"%@",[error localizedDescription]); 1021 | } 1022 | } 1023 | else { 1024 | NSException *e = [NSException 1025 | exceptionWithName:@"CorrelationContainsInvalidSample" 1026 | reason:@"HKCorrelation can only contain samples of type HKQuantitySample or HKCategorySample" 1027 | userInfo:nil]; 1028 | @throw e; 1029 | } 1030 | } 1031 | 1032 | return @{@"effective_time_frame":[self populateTimeFrameProperty:correlationSample.startDate endDate:correlationSample.endDate], 1033 | @"correlation_type":correlationSample.correlationType.description, 1034 | @"quantity_samples":quantitySampleArray, 1035 | @"category_samples":categorySampleArray 1036 | }; 1037 | } 1038 | - (NSString*)schemaName { 1039 | return @"hk-correlation"; 1040 | } 1041 | - (NSString*)schemaVersion { 1042 | return @"1.0"; 1043 | } 1044 | - (NSString*)schemaNamespace{ 1045 | return @"granola"; 1046 | } 1047 | @end 1048 | 1049 | /** 1050 | This serializer maps data from HKWorkout samples to JSON that conforms to the generic, Granola-specific [hk-workout schema](http://www.openmhealth.org/documentation/#/schema-docs/schema-library/schemas/granola_hk-workout). 1051 | 1052 | This serializer is used for all quantity types that are not supported by Open mHealth curated schemas. The [supportedTypeIdentifiersWithOMHSchema]([OMHSerializer supportedTypeIdentifiersWithOMHSchema]) method provides a list of schemas that _are_ supported by Open mHealth curated schemas. 1053 | */ 1054 | @interface OMHSerializerGenericWorkout : OMHSerializer; @end 1055 | @implementation OMHSerializerGenericWorkout 1056 | 1057 | + (BOOL)canSerialize:(HKSample *)sample error:(NSError *__autoreleasing *)error { 1058 | if([sample isKindOfClass:[HKWorkout class]]){ 1059 | return YES; 1060 | } 1061 | else{ 1062 | if (error) { 1063 | NSString* errorMessage = 1064 | @"OMHSerializerGenericWorkout is used for HKWorkout samples only"; 1065 | NSDictionary* userInfo = @{ NSLocalizedDescriptionKey : errorMessage }; 1066 | *error = [NSError errorWithDomain: OMHErrorDomain 1067 | code: OMHErrorCodeIncorrectType 1068 | userInfo: userInfo]; 1069 | } 1070 | return NO; 1071 | } 1072 | } 1073 | 1074 | - (id)bodyData { 1075 | HKWorkout *workoutSample = (HKWorkout*)self.sample; 1076 | 1077 | NSMutableDictionary *fullSerializedDictionary = [NSMutableDictionary new]; 1078 | if(workoutSample.totalDistance){ 1079 | NSString *unitString = [OMHSerializer parseUnitFromQuantity:workoutSample.totalDistance]; 1080 | [fullSerializedDictionary setObject:@{@"value":[NSNumber numberWithDouble:[workoutSample.totalDistance doubleValueForUnit:[HKUnit unitFromString:unitString]]],@"unit":unitString} forKey:@"distance"]; 1081 | } 1082 | if(workoutSample.totalEnergyBurned){ 1083 | [fullSerializedDictionary setObject:@{@"value":[NSNumber numberWithDouble:[workoutSample.totalEnergyBurned doubleValueForUnit:[HKUnit unitFromString:@"kcal"]]],@"unit":@"kcal"} forKey:@"kcal_burned"]; 1084 | } 1085 | if(workoutSample.duration){ 1086 | [fullSerializedDictionary setObject:@{@"value":[NSNumber numberWithDouble:workoutSample.duration],@"unit":@"sec"} forKey:@"duration"]; 1087 | } 1088 | 1089 | [fullSerializedDictionary addEntriesFromDictionary:@{ 1090 | @"effective_time_frame":[self populateTimeFrameProperty:workoutSample.startDate endDate:workoutSample.endDate], 1091 | @"activity_name":[OMHHealthKitConstantsMapper stringForHKWorkoutActivityType:workoutSample.workoutActivityType] 1092 | 1093 | }]; 1094 | return fullSerializedDictionary; 1095 | } 1096 | 1097 | - (NSString*)schemaName { 1098 | return @"hk-workout"; 1099 | } 1100 | - (NSString*)schemaVersion { 1101 | return @"1.0"; 1102 | } 1103 | - (NSString*)schemaNamespace{ 1104 | return @"granola"; 1105 | } 1106 | @end 1107 | 1108 | --------------------------------------------------------------------------------