├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── custom.md │ └── feature_request.md ├── pull_request_template.md └── workflows │ ├── lint_pod.yml │ └── swift.yml ├── .gitignore ├── .swiftlint.yml ├── .swiftpm └── xcode │ ├── package.xcworkspace │ └── contents.xcworkspacedata │ └── xcshareddata │ └── xcschemes │ └── HealthKitReporter.xcscheme ├── .travis.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Example ├── HealthKitReporter.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── HealthKitReporter_Example.xcscheme ├── HealthKitReporter.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── HealthKitReporter │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── LaunchScreen.xib │ │ └── Main.storyboard │ ├── HealthKitReporterService.swift │ ├── Images.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Info.plist │ ├── LocalNotification.swift │ ├── LocalNotificationManager.swift │ └── ViewController.swift ├── HealthKitReporter_Example.entitlements ├── Podfile ├── Podfile.lock ├── Pods │ ├── Local Podspecs │ │ └── HealthKitReporter.podspec.json │ ├── Manifest.lock │ ├── Pods.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ ├── HealthKitReporter.xcscheme │ │ │ └── Pods-HealthKitReporter_Tests.xcscheme │ ├── Target Support Files │ │ ├── HealthKitReporter │ │ │ ├── HealthKitReporter-Info.plist │ │ │ ├── HealthKitReporter-Unit-Tests-Info.plist │ │ │ ├── HealthKitReporter-Unit-Tests-frameworks.sh │ │ │ ├── HealthKitReporter-Unit-Tests-prefix.pch │ │ │ ├── HealthKitReporter-dummy.m │ │ │ ├── HealthKitReporter-prefix.pch │ │ │ ├── HealthKitReporter-umbrella.h │ │ │ ├── HealthKitReporter.debug.xcconfig │ │ │ ├── HealthKitReporter.modulemap │ │ │ ├── HealthKitReporter.release.xcconfig │ │ │ ├── HealthKitReporter.unit-tests.debug.xcconfig │ │ │ └── HealthKitReporter.unit-tests.release.xcconfig │ │ ├── Pods-HealthKitReporter_Example │ │ │ ├── Pods-HealthKitReporter_Example-Info.plist │ │ │ ├── Pods-HealthKitReporter_Example-acknowledgements.markdown │ │ │ ├── Pods-HealthKitReporter_Example-acknowledgements.plist │ │ │ ├── Pods-HealthKitReporter_Example-dummy.m │ │ │ ├── Pods-HealthKitReporter_Example-frameworks.sh │ │ │ ├── Pods-HealthKitReporter_Example-umbrella.h │ │ │ ├── Pods-HealthKitReporter_Example.debug.xcconfig │ │ │ ├── Pods-HealthKitReporter_Example.modulemap │ │ │ └── Pods-HealthKitReporter_Example.release.xcconfig │ │ └── Pods-HealthKitReporter_Tests │ │ │ ├── Pods-HealthKitReporter_Tests-Info.plist │ │ │ ├── Pods-HealthKitReporter_Tests-acknowledgements.markdown │ │ │ ├── Pods-HealthKitReporter_Tests-acknowledgements.plist │ │ │ ├── Pods-HealthKitReporter_Tests-dummy.m │ │ │ ├── Pods-HealthKitReporter_Tests-umbrella.h │ │ │ ├── Pods-HealthKitReporter_Tests.debug.xcconfig │ │ │ ├── Pods-HealthKitReporter_Tests.modulemap │ │ │ └── Pods-HealthKitReporter_Tests.release.xcconfig │ └── build_script.sh └── Tests │ ├── Info.plist │ └── Tests.swift ├── FUNDING.yml ├── Gemfile ├── HealthKitReporter.podspec ├── LICENSE ├── Package.swift ├── README.md ├── Sources ├── Decorator │ ├── Extensions+Date.swift │ ├── Extensions+DateComponents.swift │ ├── Extensions+Dictionary.swift │ ├── Extensions+Double.swift │ ├── Extensions+Encodable.swift │ ├── Extensions+HKActivityMoveMode.swift │ ├── Extensions+HKActivitySummary.swift │ ├── Extensions+HKBiologicalSex.swift │ ├── Extensions+HKBloodType.swift │ ├── Extensions+HKCategorySample.swift │ ├── Extensions+HKCategoryType.swift │ ├── Extensions+HKCategoryValue.swift │ ├── Extensions+HKCategoryValueAppetiteChanges.swift │ ├── Extensions+HKCategoryValueAppleStandHour.swift │ ├── Extensions+HKCategoryValueAppleWalkingSteadinessEvent.swift │ ├── Extensions+HKCategoryValueAudioExposureEvent.swift │ ├── Extensions+HKCategoryValueCervicalMucusQuality.swift │ ├── Extensions+HKCategoryValueContraceptive.swift │ ├── Extensions+HKCategoryValueEnvironmentalAudioExposureEvent.swift │ ├── Extensions+HKCategoryValueHeadphoneAudioExposureEvent.swift │ ├── Extensions+HKCategoryValueLowCardioFitnessEvent.swift │ ├── Extensions+HKCategoryValueMenstrualFlow.swift │ ├── Extensions+HKCategoryValueOvulationTestResult.swift │ ├── Extensions+HKCategoryValuePregnancyTestResult.swift │ ├── Extensions+HKCategoryValuePresence.swift │ ├── Extensions+HKCategoryValueProgesteroneTestResult.swift │ ├── Extensions+HKCategoryValueSeverity.swift │ ├── Extensions+HKCategoryValueSleepAnalysis.swift │ ├── Extensions+HKClinicalRecord.swift │ ├── Extensions+HKCorrelation.swift │ ├── Extensions+HKElectrocardiogram.swift │ ├── Extensions+HKFitzpatrickSkinType.swift │ ├── Extensions+HKHeartbeatSeriesSample.swift │ ├── Extensions+HKQuantitiySample.swift │ ├── Extensions+HKQuantityType.swift │ ├── Extensions+HKSample.swift │ ├── Extensions+HKSourceRevision.swift │ ├── Extensions+HKStatistics.swift │ ├── Extensions+HKVisionPrescription.swift │ ├── Extensions+HKVisionPrescriptionType.swift │ ├── Extensions+HKWheelchairUse.swift │ ├── Extensions+HKWorkout.swift │ ├── Extensions+HKWorkoutActivityType.swift │ ├── Extensions+HKWorkoutConfiguration.swift │ ├── Extensions+HKWorkoutEvent.swift │ ├── Extensions+HKWorkoutEventType.swift │ ├── Extensions+HKWorkoutRoute.swift │ ├── Extensions+NSPredicate.swift │ └── Extensions+String.swift ├── HealthKitError.swift ├── HealthKitReporter.swift ├── Model │ ├── Harmonizable.swift │ ├── Metadata.swift │ ├── Original.swift │ ├── Payload.swift │ ├── Payload │ │ ├── ActivitySummary.swift │ │ ├── Category.swift │ │ ├── Characteristic.swift │ │ ├── ClinicalRecord.swift │ │ ├── Correlation.swift │ │ ├── DeletedObject.swift │ │ ├── Device.swift │ │ ├── Electrocardiogram.swift │ │ ├── HeartbeatSeries.swift │ │ ├── Identifiable.swift │ │ ├── Quantity.swift │ │ ├── Sample.swift │ │ ├── Source.swift │ │ ├── SourceRevision.swift │ │ ├── Statistics.swift │ │ ├── VisionPrescription.swift │ │ ├── Workout.swift │ │ ├── WorkoutConfiguration.swift │ │ ├── WorkoutEvent.swift │ │ └── WorkoutRoute.swift │ ├── PreferredUnit.swift │ ├── Type │ │ ├── ActivitySummaryType.swift │ │ ├── CharacteristicType.swift │ │ ├── ObjectType.swift │ │ └── Sample │ │ │ ├── CategoryType.swift │ │ │ ├── ClinicalType.swift │ │ │ ├── CorrelationType.swift │ │ │ ├── DocumentType.swift │ │ │ ├── ElectrocardiogramType.swift │ │ │ ├── QuantityType.swift │ │ │ ├── SampleType.swift │ │ │ ├── SeriesType.swift │ │ │ ├── VisionPrescriptionType.swift │ │ │ └── WorkoutType.swift │ ├── UnitConvertable.swift │ └── UpdateFrequency.swift └── Service │ ├── HealthKitManager.swift │ ├── HealthKitObserver.swift │ ├── HealthKitReader.swift │ ├── HealthKitWriter.swift │ └── Retriever │ ├── ElectrocardiogramRetriever.swift │ └── SeriesSampleRetriever.swift ├── Tests ├── ActivitySummaryTests.swift ├── CategoryTests.swift ├── CharacteristicTests.swift ├── CorrelationTests.swift ├── DeletedObjectTests.swift ├── DeviceTests.swift ├── ElectrocardiogramTests.swift ├── HealthKitReporterTests.swift ├── HeartbeatSeriesTests.swift ├── MetadataTests.swift ├── QuantityTests.swift ├── SourceRevisionTests.swift ├── SourceTests.swift ├── StatisticsTests.swift ├── WorkoutConfigurationTests.swift ├── WorkoutEventTests.swift ├── WorkoutRouteTests.swift └── WorkoutTests.swift └── _Pods.xcodeproj /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Status 2 | READY/IN DEVELOPMENT/HOLD 3 | 4 | ## Migrations 5 | YES | NO 6 | 7 | ## Description 8 | A few sentences describing the overall goals of the pull request's commits. 9 | 10 | ## Related PRs 11 | List related PRs against other branches: 12 | 13 | branch PR 14 | other_pr_production link 15 | other_pr_master link 16 | Todos 17 | Tests 18 | Documentation 19 | 20 | ## Deploy Notes 21 | Notes regarding deployment the contained body of work. These should note any db migrations, etc. 22 | 23 | ## Steps to Test or Reproduce 24 | Outline the steps to test or reproduce the PR here. 25 | 26 | git pull --prune 27 | git checkout 28 | 29 | ## Impacted Areas in Application 30 | Small description about what will be changed in the app if this PR will be merged 31 | 32 | ## List general components of the application that this PR will affect: 33 | The direct changes inside the app made by this PR 34 | -------------------------------------------------------------------------------- /.github/workflows/lint_pod.yml: -------------------------------------------------------------------------------- 1 | name: Lint Pod 2 | 3 | on: 4 | pull_request: 5 | branches: [ master ] 6 | types: [ assigned, opened, synchronize, reopened ] 7 | 8 | jobs: 9 | pod-lint: 10 | name: Lint Pod 11 | runs-on: macos-latest 12 | 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v3 16 | 17 | - name: Load and Install Bundle Cache 18 | uses: ruby/setup-ruby@v1 19 | with: 20 | ruby-version: 2.6 21 | bundler-cache: true 22 | 23 | - name: Lint Pod 24 | run: bundle exec pod lib lint HealthKitReporter.podspec --allow-warnings 25 | -------------------------------------------------------------------------------- /.github/workflows/swift.yml: -------------------------------------------------------------------------------- 1 | name: Swift 2 | 3 | on: 4 | pull_request: 5 | branches: [ master ] 6 | types: [ assigned, opened, synchronize, reopened ] 7 | 8 | jobs: 9 | build: 10 | 11 | runs-on: macos-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | - name: Generate project 16 | run: swift package generate-xcodeproj 17 | - name: Build 18 | run: xcodebuild clean build -project "HealthKitReporter.xcodeproj" -scheme "HealthKitReporter-Package" -destination "platform=iOS Simulator,name=iPhone 12 Pro,OS=latest" CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO ONLY_ACTIVE_ARCH=NO 19 | - name: Run tests 20 | run: xcodebuild clean test -project "HealthKitReporter.xcodeproj" -scheme "HealthKitReporter-Package" -destination "platform=iOS Simulator,name=iPhone 12 Pro,OS=latest" CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO ONLY_ACTIVE_ARCH=NO 21 | 22 | 23 | -------------------------------------------------------------------------------- /.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 | *.framework 22 | 23 | # Bundler 24 | .bundle 25 | 26 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 27 | # Carthage/Checkouts 28 | 29 | Carthage/Build 30 | 31 | # We recommend against adding the Pods directory to your .gitignore. However 32 | # you should judge for yourself, the pros and cons are mentioned at: 33 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 34 | # 35 | # Note: if you ignore the Pods directory, make sure to uncomment 36 | # `pod install` in .travis.yml 37 | # 38 | # Pods/ 39 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: # rule identifiers to exclude from running 2 | - colon 3 | - comma 4 | - control_statement 5 | opt_in_rules: # some rules are only opt-in 6 | - empty_count 7 | # Find all the available rules by running: 8 | # swiftlint rules 9 | included: # paths to include during linting. `--path` is ignored if present. 10 | - HealthKitReporter 11 | excluded: # paths to ignore during linting. Takes precedence over `included`. 12 | - Carthage 13 | analyzer_rules: # Rules run by `swiftlint analyze` (experimental) 14 | - explicit_self 15 | 16 | # configurable rules can be customized from this configuration file 17 | # binary rules can set their severity level 18 | force_cast: warning # implicitly 19 | force_try: 20 | severity: warning # explicitly 21 | # rules that have both warning and error levels, can set just the warning level 22 | # implicitly 23 | line_length: 110 24 | # they can set both implicitly with an array 25 | type_body_length: 26 | - 300 # warning 27 | - 400 # error 28 | # or they can set both explicitly 29 | file_length: 30 | warning: 500 31 | error: 1200 32 | # naming rules can set warnings/errors for min_length and max_length 33 | # additionally they can set excluded names 34 | type_name: 35 | min_length: 2 # only warning 36 | max_length: # warning and error 37 | warning: 40 38 | error: 50 39 | excluded: iPhone # excluded via string 40 | allowed_symbols: ["_"] # these are allowed in type names 41 | identifier_name: 42 | min_length: 2 # only min_length 43 | max_length: # warning and error 44 | warning: 40 45 | error: 50 # only error 46 | excluded: # excluded via string array 47 | - id 48 | - URL 49 | - GlobalAPIKey 50 | reporter: "xcode" # reporter type (xcode, json, csv, checkstyle, junit, html, emoji, sonarqube, markdown) 51 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.swiftpm/xcode/xcshareddata/xcschemes/HealthKitReporter.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 51 | 52 | 53 | 54 | 57 | 58 | 59 | 60 | 63 | 69 | 70 | 71 | 72 | 73 | 83 | 84 | 90 | 91 | 97 | 98 | 99 | 100 | 102 | 103 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # references: 2 | # * https://www.objc.io/issues/6-build-tools/travis-ci/ 3 | # * https://github.com/supermarin/xcpretty#usage 4 | 5 | osx_image: xcode7.3 6 | language: objective-c 7 | # cache: cocoapods 8 | # podfile: Example/Podfile 9 | # before_install: 10 | # - gem install cocoapods # Since Travis is not always on latest version 11 | # - pod install --project-directory=Example 12 | script: 13 | - set -o pipefail && xcodebuild test -enableCodeCoverage YES -workspace Example/HealthKitReporter.xcworkspace -scheme HealthKitReporter-Example -sdk iphonesimulator9.3 ONLY_ACTIVE_ARCH=NO | xcpretty 14 | - pod lib lint 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [3.1.0] - 08.01.2024. 2 | 3 | * Added support for HKClinicalRecord 4 | 5 | ## [3.0.0] - 12.03.2023. 6 | 7 | * Check for Health data availability 8 | * CHanged the way of creating HKR instance 9 | 10 | ## [2.0.0] - 29.10.2022. 11 | 12 | * Revamp metadata 13 | 14 | ## [1.7.0] - 29.10.2022. 15 | 16 | * Add new iOS 16 types (also missing iOS 15 types) 17 | 18 | ## [1.6.9] - 27.05.2022. 19 | 20 | * Add copyWith methods to Correlation 21 | 22 | ## [1.6.8] - 27.05.2022. 23 | 24 | * Small fix with Correlation save in health repository 25 | 26 | ## [1.6.7] - 27.05.2022. 27 | 28 | * Small fix with Correlation copyWith method 29 | 30 | ## [1.6.6] - 27.05.2022. 31 | 32 | * Add saving Correlation samples 33 | 34 | ## [1.6.5] - 18.04.2022. 35 | 36 | * ECG with Voltage measurements in one query on demand 37 | 38 | ## [1.6.4] - 17.04.2022. 39 | 40 | * ECG with Voltage measurements in one query 41 | 42 | ## [1.6.3] - 16.04.2022. 43 | 44 | * add from dictionary static factory for WorkoutRoute 45 | * minor fixes in the code 46 | 47 | ## [1.6.2] - 23.10.2021. 48 | 49 | * Fix timestamps bug 50 | * add from dictionary static factory for HeartbeatSeries 51 | 52 | ## [1.6.1] - 15.10.2021. 53 | 54 | * return HeartbeatSeries as a collection of samples, each has own collection of measurements 55 | 56 | ## [1.6.0] - 12.10.2021. 57 | 58 | * heartbeatSeriesQuery changed as HeartbeatSeries now is a valid sample with a set of beat by beat measurements 59 | 60 | ## [1.5.3] - 05.09.2021. 61 | 62 | * Sample app adjustments 63 | 64 | ## [1.5.2] - 05.09.2021. 65 | 66 | * Extension of Category types, add detail key-value to DTO 67 | 68 | ## [1.5.1] - 04.09.2021. 69 | 70 | * CustomStringConvertable for HK enum Types 71 | * Workout and WorkoutEvent restructuring, add harmonized description instead of name and type property respectively 72 | 73 | ## [1.5.0] - 04.09.2021. 74 | 75 | * Unit Tests for most of DTO Models 76 | * WorkoutConfiguration bug fix for parsing 77 | * WorkoutEventType name 78 | * Parsing number values from incoming JSON dictionaries as NSNumber 79 | * Fix the typos (HeartbeatSeries) 80 | 81 | ## [1.4.5] - 28.06.2021. 82 | 83 | * Fix with minimum Operation System for iOS 10 84 | 85 | ## [1.4.4] - 27.05.2021. 86 | 87 | * Swift package fix 88 | 89 | ## [1.4.3] - 02.04.2021. 90 | 91 | * Add Workout names 92 | 93 | ## [1.4.2] - 12.03.2021. 94 | 95 | * Fix Statistics sources 96 | 97 | ## [1.4.0] - 25.02.2021. 98 | 99 | * iOS 9.0 support 100 | * Carthage and Swift Package Manager support 101 | * FIx with workout values 102 | * Fix with UUID of Samples 103 | 104 | ## [1.3.6] - 10.02.2021. 105 | 106 | * Remove redundant UUID paramater 107 | 108 | ## [1.3.5] - 10.02.2021. 109 | 110 | * Fix issue with saving Workout 111 | 112 | ## [1.3.4] - 01.02.2021. 113 | 114 | * Added UUID property for Wrappers of original HKObjectTypes 115 | 116 | ## [1.3.3] - 27.01.2021. 117 | 118 | * Added more CategoryType enum cases (iOS 14) 119 | 120 | ## [1.3.2] - 19.01.2021. 121 | 122 | * Deprecate SampleQuery, use specific queries instead (Quantity, Category etc.) 123 | * Added more QuantityType enum cases (iOS 14) 124 | 125 | ## [1.3.1] - 23.12.2020. 126 | 127 | * Fix with HKActivitySummaryType identifier 128 | 129 | ## [1.3.0] - 08.12.2020. 130 | 131 | * All reading and observer queries are returned as objects in order to let to stop them running 132 | * Manager is now responsible for executing queries 133 | * Most of the reading queries will throw an Error if provided type is not recognized by HK 134 | * Electrocardiograms voltage measurement 135 | 136 | ## [1.2.6] - 24.11.2020. 137 | 138 | * Workout Route series query 139 | * CLLocation usage in Workout Route 140 | 141 | ## [1.2.5] - 23.11.2020. 142 | 143 | * Dietery water add 144 | * Fix with Source Revision cinstructor from map 145 | 146 | ## [1.2.4] - 23.11.2020. 147 | 148 | * Timestamps wide usage for payload (Flutter) 149 | 150 | 151 | ## [1.2.3] - 18.11.2020. 152 | 153 | * SampleTypes in appropriate requests 154 | * Add Correlation 155 | 156 | 157 | ## [1.2.2] - 18.11.2020. 158 | 159 | * Correlation fix 160 | * Fix with SampleType 161 | * Refactoring 162 | 163 | ## [1.2.1] - 15.11.2020. 164 | 165 | * More public extensions for Flutter support 166 | 167 | ## [1.2.0] - 12.11.2020. 168 | 169 | * Cross-platform support for Flutter 170 | * ObjectType as an aggregation type for different types 171 | * Public extensions 172 | 173 | ## [1.1.2] - 11.11.2020. 174 | 175 | * Making error enum public 176 | 177 | ## [1.1.1] - 21.10.2020. 178 | 179 | * Documentation 180 | 181 | ## [1.1.0] - 21.10.2020. 182 | 183 | * Distinguish queries between different kind of types 184 | * Making usage of preferred units as main strategy to write or read data 185 | 186 | ## [1.0.1] - 02.10.2020. 187 | 188 | * Add change log 189 | 190 | ## [1.0.0] - 02.10.2020. 191 | 192 | * Initial release. The HealthKitReporter library to make easy data reading and writing for HealthKit 193 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at victorkachalov@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue, 4 | email, or any other method with the owners of this repository before making a change. 5 | 6 | Please note we have a code of conduct, please follow it in all your interactions with the project. 7 | 8 | ## Pull Request Process 9 | 10 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a 11 | build. 12 | 2. Update the README.md with details of changes to the interface, this includes new environment 13 | variables, exposed ports, useful file locations and container parameters. 14 | 3. Increase the version numbers in any examples files and the README.md to the new version that this 15 | Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/). 16 | 4. You may merge the Pull Request in once you have the sign-off of one of other developers, or if you 17 | do not have permission to do that, you may request the second reviewer to merge it for you. 18 | 19 | ## Swift 20 | 21 | We use https://swift.org/documentation/api-design-guidelines/ to provide a good quality and easy readyble code. 22 | -------------------------------------------------------------------------------- /Example/HealthKitReporter.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/HealthKitReporter.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/HealthKitReporter.xcodeproj/xcshareddata/xcschemes/HealthKitReporter_Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /Example/HealthKitReporter.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/HealthKitReporter.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/HealthKitReporter/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor Kachalov on 09/14/2020. 6 | // Copyright (c) 2020 Victor Kachalov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import HealthKitReporter 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | var window: UIWindow? 15 | 16 | var observerUpdateHandler: ((Query?, String?, Error?) -> Void)? 17 | 18 | func application( 19 | _ application: UIApplication, 20 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 21 | ) -> Bool { 22 | let localNotificationManager = LocalNotificationManager() 23 | localNotificationManager.requestPermission { (_, _) in } 24 | let reporter = HealthKitReporter() 25 | let types: [SampleType] = [ 26 | QuantityType.stepCount, 27 | QuantityType.heartRate, 28 | QuantityType.distanceCycling, 29 | CategoryType.sleepAnalysis 30 | ] 31 | reporter.manager.requestAuthorization( 32 | toRead: types, 33 | toWrite: types 34 | ) { (success, error) in 35 | if success && error == nil { 36 | for type in types { 37 | do { 38 | let query = try reporter.observer.observerQuery( 39 | type: type 40 | ) { (_, identifier, _) in 41 | if let identifier = identifier { 42 | let notification = LocalNotification( 43 | title: "Observed", 44 | subtitle: identifier 45 | ) 46 | localNotificationManager.scheduleNotification(notification) 47 | } 48 | } 49 | reporter.observer.enableBackgroundDelivery( 50 | type: type, 51 | frequency: .immediate 52 | ) { (_, error) in 53 | if error == nil { 54 | print("enabled") 55 | } 56 | } 57 | reporter.manager.executeQuery(query) 58 | } catch { 59 | print(error) 60 | } 61 | } 62 | } 63 | } 64 | return true 65 | } 66 | } 67 | 68 | extension AppDelegate: UNUserNotificationCenterDelegate { 69 | func userNotificationCenter( 70 | _ center: UNUserNotificationCenter, 71 | willPresent notification: UNNotification, 72 | withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void 73 | ) { 74 | completionHandler( 75 | [ 76 | .alert, 77 | .badge, 78 | .sound 79 | ] 80 | ) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Example/HealthKitReporter/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /Example/HealthKitReporter/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ios-marketing", 45 | "size" : "1024x1024", 46 | "scale" : "1x" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Example/HealthKitReporter/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | NSHealthShareUsageDescription 26 | This app needs permission to share your health data 27 | NSHealthUpdateUsageDescription 28 | This app needs permission to update your health data 29 | NSLocationAlwaysAndWhenInUseUsageDescription 30 | This app needs permission to update your location data always 31 | NSLocationWhenInUseUsageDescription 32 | This app needs permission to update your location data during usage 33 | UIBackgroundModes 34 | 35 | fetch 36 | 37 | UILaunchStoryboardName 38 | LaunchScreen 39 | UIMainStoryboardFile 40 | Main 41 | UIRequiredDeviceCapabilities 42 | 43 | armv7 44 | 45 | UISupportedInterfaceOrientations 46 | 47 | UIInterfaceOrientationPortrait 48 | UIInterfaceOrientationLandscapeLeft 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /Example/HealthKitReporter/LocalNotification.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LocalNotification.swift 3 | // HealthKitReporter_Example 4 | // 5 | // Created by Florian on 09.12.20. 6 | // Copyright © 2020 CocoaPods. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct LocalNotification { 12 | let title: String 13 | let subtitle: String 14 | } 15 | -------------------------------------------------------------------------------- /Example/HealthKitReporter/LocalNotificationManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LocalNotificationManager.swift 3 | // HealthKitReporter_Example 4 | // 5 | // Created by Victor Kachalov on 09.12.20. 6 | // Copyright © 2020 Victor Kachalov. All rights reserved. 7 | // 8 | 9 | import UserNotifications 10 | 11 | class LocalNotificationManager { 12 | func requestPermission(completionHandler: @escaping (Bool, Error?) -> Void) { 13 | UNUserNotificationCenter.current().requestAuthorization( 14 | options: [ 15 | .alert, 16 | .badge, 17 | .sound 18 | ], 19 | completionHandler: completionHandler 20 | ) 21 | } 22 | func scheduleNotification(_ notification: LocalNotification) { 23 | let content = UNMutableNotificationContent() 24 | content.title = notification.title 25 | content.subtitle = notification.subtitle 26 | content.sound = UNNotificationSound.default 27 | let trigger = UNTimeIntervalNotificationTrigger( 28 | timeInterval: 1, 29 | repeats: false 30 | ) 31 | let request = UNNotificationRequest( 32 | identifier: UUID().uuidString, 33 | content: content, 34 | trigger: trigger 35 | ) 36 | UNUserNotificationCenter.current().add(request) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Example/HealthKitReporter/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor Kachalov on 09/14/2020. 6 | // Copyright (c) 2020 Victor Kachalov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import HealthKitReporter 11 | 12 | class ViewController: UIViewController { 13 | @IBOutlet weak var readButton: UIButton! 14 | @IBOutlet weak var writeButton: UIButton! 15 | 16 | private let healthKitReporterSerivce = HealthKitReporterService() 17 | 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | readButton.isEnabled = false 21 | writeButton.isEnabled = false 22 | } 23 | 24 | @IBAction func authorizeButtonTapped(_ sender: UIButton) { 25 | healthKitReporterSerivce.requestAuthorization { success, error in 26 | if success && error == nil { 27 | DispatchQueue.main.async { [unowned self] in 28 | let alert = UIAlertController( 29 | title: "HK", 30 | message: "HK Authorized", 31 | preferredStyle: .alert 32 | ) 33 | alert.addAction( 34 | UIAlertAction( 35 | title: "OK", 36 | style: .default 37 | ) { [unowned self] (_) in 38 | self.readButton.isEnabled = true 39 | self.writeButton.isEnabled = true 40 | } 41 | ) 42 | self.present(alert, animated: true) 43 | } 44 | } else { 45 | print(error ?? "error") 46 | } 47 | } 48 | } 49 | @IBAction func readButtonTapped(_ sender: UIButton) { 50 | healthKitReporterSerivce.readCategories() 51 | healthKitReporterSerivce.readElectrocardiogram() 52 | healthKitReporterSerivce.readQuantitiesAndStatistics() 53 | } 54 | @IBAction func writeButtonTapped(_ sender: UIButton) { 55 | healthKitReporterSerivce.writeSteps() 56 | healthKitReporterSerivce.writeBloodPressureCorrelation() 57 | } 58 | @IBAction func seriesButtonTapped(_ sender: UIButton) { 59 | healthKitReporterSerivce.readWorkoutRoutes() 60 | healthKitReporterSerivce.readHearbeatSeries() 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Example/HealthKitReporter_Example.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.developer.healthkit 6 | 7 | com.apple.developer.healthkit.access 8 | 9 | com.apple.developer.healthkit.background-delivery 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '9.0' 2 | use_frameworks! 3 | 4 | target 'HealthKitReporter_Example' do 5 | pod 'HealthKitReporter', :path => '../', :testspecs => ['Tests'] 6 | end 7 | 8 | target 'HealthKitReporter_Tests' do 9 | inherit! :search_paths 10 | end 11 | 12 | -------------------------------------------------------------------------------- /Example/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - HealthKitReporter (1.7.0) 3 | - HealthKitReporter/Tests (1.7.0) 4 | 5 | DEPENDENCIES: 6 | - HealthKitReporter (from `../`) 7 | - HealthKitReporter/Tests (from `../`) 8 | 9 | EXTERNAL SOURCES: 10 | HealthKitReporter: 11 | :path: "../" 12 | 13 | SPEC CHECKSUMS: 14 | HealthKitReporter: c47cb09099b218c2717b8c3b884ee683edf57d95 15 | 16 | PODFILE CHECKSUM: 42dc208a4788fd9b7d8eebf0c62dd84606fddf4d 17 | 18 | COCOAPODS: 1.11.2 19 | -------------------------------------------------------------------------------- /Example/Pods/Local Podspecs/HealthKitReporter.podspec.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "HealthKitReporter", 3 | "version": "1.7.0", 4 | "summary": "HealthKitReporter. A wrapper for HealthKit framework.", 5 | "swift_versions": "5.3", 6 | "description": "Helps to write or read data from Apple Health via HealthKit framework.", 7 | "homepage": "https://github.com/VictorKachalov/HealthKitReporter", 8 | "license": { 9 | "type": "MIT", 10 | "file": "LICENSE" 11 | }, 12 | "authors": { 13 | "Victor Kachalov": "victorkachalov@gmail.com" 14 | }, 15 | "source": { 16 | "git": "https://github.com/VictorKachalov/HealthKitReporter.git", 17 | "tag": "1.7.0" 18 | }, 19 | "social_media_url": "https://www.facebook.com/profile.php?id=1700091944", 20 | "platforms": { 21 | "ios": "9.0" 22 | }, 23 | "source_files": "Sources/**/*", 24 | "testspecs": [ 25 | { 26 | "name": "Tests", 27 | "test_type": "unit", 28 | "source_files": "Tests/*.swift" 29 | } 30 | ], 31 | "swift_version": "5.3" 32 | } 33 | -------------------------------------------------------------------------------- /Example/Pods/Manifest.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - HealthKitReporter (1.7.0) 3 | - HealthKitReporter/Tests (1.7.0) 4 | 5 | DEPENDENCIES: 6 | - HealthKitReporter (from `../`) 7 | - HealthKitReporter/Tests (from `../`) 8 | 9 | EXTERNAL SOURCES: 10 | HealthKitReporter: 11 | :path: "../" 12 | 13 | SPEC CHECKSUMS: 14 | HealthKitReporter: c47cb09099b218c2717b8c3b884ee683edf57d95 15 | 16 | PODFILE CHECKSUM: 42dc208a4788fd9b7d8eebf0c62dd84606fddf4d 17 | 18 | COCOAPODS: 1.11.2 19 | -------------------------------------------------------------------------------- /Example/Pods/Pods.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/Pods/Pods.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/Pods/Pods.xcodeproj/xcshareddata/xcschemes/HealthKitReporter.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /Example/Pods/Pods.xcodeproj/xcshareddata/xcschemes/Pods-HealthKitReporter_Tests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/HealthKitReporter/HealthKitReporter-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 3.1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/HealthKitReporter/HealthKitReporter-Unit-Tests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleIdentifier 8 | ${PRODUCT_BUNDLE_IDENTIFIER} 9 | CFBundleInfoDictionaryVersion 10 | 6.0 11 | CFBundleName 12 | ${PRODUCT_NAME} 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/HealthKitReporter/HealthKitReporter-Unit-Tests-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/HealthKitReporter/HealthKitReporter-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_HealthKitReporter : NSObject 3 | @end 4 | @implementation PodsDummy_HealthKitReporter 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/HealthKitReporter/HealthKitReporter-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/HealthKitReporter/HealthKitReporter-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double HealthKitReporterVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char HealthKitReporterVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/HealthKitReporter/HealthKitReporter.debug.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/HealthKitReporter 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 5 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 6 | PODS_BUILD_DIR = ${BUILD_DIR} 7 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 8 | PODS_ROOT = ${SRCROOT} 9 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../.. 10 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 11 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 12 | SKIP_INSTALL = YES 13 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 14 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/HealthKitReporter/HealthKitReporter.modulemap: -------------------------------------------------------------------------------- 1 | framework module HealthKitReporter { 2 | umbrella header "HealthKitReporter-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/HealthKitReporter/HealthKitReporter.release.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/HealthKitReporter 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 5 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 6 | PODS_BUILD_DIR = ${BUILD_DIR} 7 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 8 | PODS_ROOT = ${SRCROOT} 9 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../.. 10 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 11 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 12 | SKIP_INSTALL = YES 13 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 14 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/HealthKitReporter/HealthKitReporter.unit-tests.debug.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/HealthKitReporter" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift "$(PLATFORM_DIR)/Developer/Library/Frameworks" '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | LIBRARY_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks" "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 6 | OTHER_LDFLAGS = $(inherited) -ObjC -framework "HealthKitReporter" 7 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_ROOT = ${SRCROOT} 11 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../.. 12 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 13 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 14 | SKIP_INSTALL = YES 15 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 16 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/HealthKitReporter/HealthKitReporter.unit-tests.release.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/HealthKitReporter" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift "$(PLATFORM_DIR)/Developer/Library/Frameworks" '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | LIBRARY_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks" "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 6 | OTHER_LDFLAGS = $(inherited) -ObjC -framework "HealthKitReporter" 7 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_ROOT = ${SRCROOT} 11 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../.. 12 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 13 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 14 | SKIP_INSTALL = YES 15 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 16 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-HealthKitReporter_Example/Pods-HealthKitReporter_Example-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-HealthKitReporter_Example/Pods-HealthKitReporter_Example-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## HealthKitReporter 5 | 6 | Copyright (c) 2020 Victor Kachalov 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | 26 | Generated by CocoaPods - https://cocoapods.org 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-HealthKitReporter_Example/Pods-HealthKitReporter_Example-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | Copyright (c) 2020 Victor Kachalov <victorkachalov@gmail.com> 18 | 19 | Permission is hereby granted, free of charge, to any person obtaining a copy 20 | of this software and associated documentation files (the "Software"), to deal 21 | in the Software without restriction, including without limitation the rights 22 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 23 | copies of the Software, and to permit persons to whom the Software is 24 | furnished to do so, subject to the following conditions: 25 | 26 | The above copyright notice and this permission notice shall be included in 27 | all copies or substantial portions of the Software. 28 | 29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 30 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 31 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 32 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 33 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 34 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 35 | THE SOFTWARE. 36 | 37 | License 38 | MIT 39 | Title 40 | HealthKitReporter 41 | Type 42 | PSGroupSpecifier 43 | 44 | 45 | FooterText 46 | Generated by CocoaPods - https://cocoapods.org 47 | Title 48 | 49 | Type 50 | PSGroupSpecifier 51 | 52 | 53 | StringsTable 54 | Acknowledgements 55 | Title 56 | Acknowledgements 57 | 58 | 59 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-HealthKitReporter_Example/Pods-HealthKitReporter_Example-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_HealthKitReporter_Example : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_HealthKitReporter_Example 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-HealthKitReporter_Example/Pods-HealthKitReporter_Example-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_HealthKitReporter_ExampleVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_HealthKitReporter_ExampleVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-HealthKitReporter_Example/Pods-HealthKitReporter_Example.debug.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 3 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/HealthKitReporter" 4 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 5 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/HealthKitReporter/HealthKitReporter.framework/Headers" 6 | LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/Frameworks' '@loader_path/Frameworks' 7 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 8 | OTHER_LDFLAGS = $(inherited) -framework "HealthKitReporter" 9 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 10 | PODS_BUILD_DIR = ${BUILD_DIR} 11 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 12 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 13 | PODS_ROOT = ${SRCROOT}/Pods 14 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 15 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 16 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-HealthKitReporter_Example/Pods-HealthKitReporter_Example.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_HealthKitReporter_Example { 2 | umbrella header "Pods-HealthKitReporter_Example-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-HealthKitReporter_Example/Pods-HealthKitReporter_Example.release.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 3 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/HealthKitReporter" 4 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 5 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/HealthKitReporter/HealthKitReporter.framework/Headers" 6 | LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/Frameworks' '@loader_path/Frameworks' 7 | LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift 8 | OTHER_LDFLAGS = $(inherited) -framework "HealthKitReporter" 9 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 10 | PODS_BUILD_DIR = ${BUILD_DIR} 11 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 12 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 13 | PODS_ROOT = ${SRCROOT}/Pods 14 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 15 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 16 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-HealthKitReporter_Tests/Pods-HealthKitReporter_Tests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-HealthKitReporter_Tests/Pods-HealthKitReporter_Tests-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | Generated by CocoaPods - https://cocoapods.org 4 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-HealthKitReporter_Tests/Pods-HealthKitReporter_Tests-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | Generated by CocoaPods - https://cocoapods.org 18 | Title 19 | 20 | Type 21 | PSGroupSpecifier 22 | 23 | 24 | StringsTable 25 | Acknowledgements 26 | Title 27 | Acknowledgements 28 | 29 | 30 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-HealthKitReporter_Tests/Pods-HealthKitReporter_Tests-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_HealthKitReporter_Tests : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_HealthKitReporter_Tests 5 | @end 6 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-HealthKitReporter_Tests/Pods-HealthKitReporter_Tests-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_HealthKitReporter_TestsVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_HealthKitReporter_TestsVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-HealthKitReporter_Tests/Pods-HealthKitReporter_Tests.debug.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | PODS_BUILD_DIR = ${BUILD_DIR} 4 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 5 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 6 | PODS_ROOT = ${SRCROOT}/Pods 7 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 8 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 9 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-HealthKitReporter_Tests/Pods-HealthKitReporter_Tests.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_HealthKitReporter_Tests { 2 | umbrella header "Pods-HealthKitReporter_Tests-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Pods/Target Support Files/Pods-HealthKitReporter_Tests/Pods-HealthKitReporter_Tests.release.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | PODS_BUILD_DIR = ${BUILD_DIR} 4 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 5 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 6 | PODS_ROOT = ${SRCROOT}/Pods 7 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 8 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 9 | -------------------------------------------------------------------------------- /Example/Pods/build_script.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | UNIVERSAL_OUTPUTFOLDER=${BUILD_DIR}/${CONFIGURATION}-universal 3 | 4 | # make sure the output directory exists 5 | if [ ! -d "$UNIVERSAL_OUTPUTFOLDER" ]; then 6 | mkdir -p "${UNIVERSAL_OUTPUTFOLDER}" 7 | fi 8 | 9 | # passed parameter is the name of target 10 | PROJECT_NAME="$1" 11 | 12 | # Step 1. Build Device and Simulator versions 13 | xcodebuild BITCODE_GENERATION_MODE=bitcode -target "${PROJECT_NAME}" ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} -sdk iphoneos BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build 14 | 15 | xcodebuild -target "${PROJECT_NAME}" -configuration ${CONFIGURATION} -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" EXCLUDED_ARCHS=arm64 clean build 16 | 17 | # Step 2. Copy the framework structure (from iphoneos build) to the universal folder 18 | cp -R "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}/${PROJECT_NAME}.framework" "${UNIVERSAL_OUTPUTFOLDER}/" 19 | 20 | # Step 3. Copy Swift modules from iphonesimulator build (if it exists) to the copied framework directory 21 | SIMULATOR_SWIFT_MODULES_DIR="${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}/${PROJECT_NAME}.framework/Modules/${PROJECT_NAME}.swiftmodule/." 22 | 23 | if [ -d "${SIMULATOR_SWIFT_MODULES_DIR}" ]; then 24 | cp -R "${SIMULATOR_SWIFT_MODULES_DIR}" "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/Modules/${PROJECT_NAME}.swiftmodule" 25 | fi 26 | 27 | # Step 4. Create universal binary file using lipo and place the combined executable in the copied framework directory 28 | lipo -create -output "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework/${PROJECT_NAME}" 29 | 30 | # Step 5. Convenience step to copy the framework to the project's directory 31 | EXECUTABLES_DIR="${PROJECT_DIR}/Executables" 32 | if [ ! -d "$EXECUTABLES_DIR" ]; then 33 | mkdir -p "${EXECUTABLES_DIR}" 34 | fi 35 | 36 | # Step 6. Copy everything to Executables 37 | cp -R "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework" "${EXECUTABLES_DIR}" 38 | -------------------------------------------------------------------------------- /Example/Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Example/Tests/Tests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | class Tests: XCTestCase { 4 | override func setUp() { 5 | super.setUp() 6 | // Put setup code here. This method is called before the invocation of each test method in the class. 7 | } 8 | override func tearDown() { 9 | // Put teardown code here. This method is called after the invocation of each test method in the class. 10 | super.tearDown() 11 | } 12 | 13 | func testExample() { 14 | XCTAssert(true, "Pass") 15 | } 16 | func testPerformanceExample() { 17 | // This is an example of a performance test case. 18 | self.measure { 19 | // Put the code you want to measure the time of here. 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [VictorKachalov] 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | ruby '~> 2.6.0' 6 | 7 | gem 'cocoapods', '~> 1.11.2' 8 | gem 'fastlane', '~> 2.178' 9 | gem 'xcode-install', '~> 2.6.8' 10 | gem 'xcpretty-actions-formatter', '~> 0.1.2' 11 | -------------------------------------------------------------------------------- /HealthKitReporter.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod lib lint HealthKitReporter.podspec' to ensure this is a 3 | # valid spec before submitting. 4 | # 5 | # Any lines starting with a # are optional, but their use is encouraged 6 | # To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | s.name = 'HealthKitReporter' 11 | s.version = '3.1.0' 12 | s.summary = 'HealthKitReporter. A wrapper for HealthKit framework.' 13 | s.swift_versions = '5.3' 14 | s.description = 'Helps to write or read data from Apple Health via HealthKit framework.' 15 | s.homepage = 'https://github.com/VictorKachalov/HealthKitReporter' 16 | s.license = { :type => 'MIT', :file => 'LICENSE' } 17 | s.author = { 'Victor Kachalov' => 'victorkachalov@gmail.com' } 18 | s.source = { :git => 'https://github.com/VictorKachalov/HealthKitReporter.git', :tag => s.version.to_s } 19 | s.social_media_url = 'https://www.facebook.com/profile.php?id=1700091944' 20 | s.platform = :ios, '9.0' 21 | s.ios.deployment_target = '9.0' 22 | s.source_files = 'Sources/**/*' 23 | 24 | s.test_spec 'Tests' do |t| 25 | t.source_files = 'Tests/*.swift' 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Victor Kachalov 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.2 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "HealthKitReporter", 8 | platforms: [ .iOS(.v9), .watchOS(.v2) ], 9 | products: [ 10 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 11 | .library( 12 | name: "HealthKitReporter", 13 | targets: ["HealthKitReporter"] 14 | ), 15 | ], 16 | dependencies: [ 17 | // Dependencies declare other packages that this package depends on. 18 | ], 19 | targets: [ 20 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 21 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 22 | .target( 23 | name: "HealthKitReporter", 24 | path: "Sources" 25 | ), 26 | .testTarget( 27 | name: "HealthKitReporterTests", 28 | dependencies: ["HealthKitReporter"], 29 | path: "Tests" 30 | ) 31 | ] 32 | ) 33 | 34 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+Date.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+Date.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 14.09.20. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension Date { 11 | static var iso8601: String { 12 | return "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ" 13 | } 14 | 15 | var millisecondsSince1970: Double { 16 | return timeIntervalSince1970 * 1000 17 | } 18 | 19 | func formatted( 20 | with format: String, 21 | timezone: TimeZone? = TimeZone.current 22 | ) -> String { 23 | let dateFormatter = DateFormatter() 24 | dateFormatter.dateFormat = format 25 | dateFormatter.timeZone = timezone 26 | let date = dateFormatter.string(from: self) 27 | return date 28 | } 29 | func distance(to other: Date) -> TimeInterval { 30 | return other.timeIntervalSinceReferenceDate - self.timeIntervalSinceReferenceDate 31 | } 32 | func advanced(by n: TimeInterval) -> Date { 33 | return self + n 34 | } 35 | 36 | static func make(from millisecondsSince1970: Double) -> Date { 37 | return Date(timeIntervalSince1970: millisecondsSince1970.secondsSince1970) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+DateComponents.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+DateComponents.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 15.11.20. 6 | // 7 | 8 | import Foundation 9 | 10 | // MARK: - Payload 11 | extension DateComponents: Payload { 12 | public static func make( 13 | from dictionary: [String: Any] 14 | ) -> DateComponents { 15 | return DateComponents( 16 | calendar: Calendar.current, 17 | timeZone: TimeZone.current, 18 | era: dictionary["era"] as? Int, 19 | year: dictionary["year"] as? Int, 20 | month: dictionary["month"] as? Int, 21 | day: dictionary["day"] as? Int, 22 | hour: dictionary["hour"] as? Int, 23 | minute: dictionary["minute"] as? Int, 24 | second: dictionary["second"] as? Int, 25 | nanosecond: dictionary["nanosecond"] as? Int, 26 | weekday: dictionary["weekday"] as? Int, 27 | weekdayOrdinal: dictionary["weekdayOrdinal"] as? Int, 28 | quarter: dictionary["quarter"] as? Int, 29 | weekOfMonth: dictionary["weekOfMonth"] as? Int, 30 | weekOfYear: dictionary["weekOfYear"] as? Int, 31 | yearForWeekOfYear: dictionary["yearForWeekOfYear"] as? Int 32 | ) 33 | } 34 | } 35 | 36 | 37 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+Dictionary.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+Dictionary.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 13.11.20. 6 | // 7 | 8 | import HealthKit 9 | 10 | public extension Dictionary where Key == String, Value == NSPredicate { 11 | var sampleTypePredicates: [HKSampleType: NSPredicate] { 12 | var samplePredicates = [HKSampleType: NSPredicate]() 13 | for (key, value) in self { 14 | if let type = try? QuantityType.make(from: key) 15 | .original as? HKSampleType { 16 | samplePredicates[type] = value 17 | } 18 | if let type = try? CategoryType.make(from: key) 19 | .original as? HKSampleType { 20 | samplePredicates[type] = value 21 | } 22 | if let type = try? CharacteristicType.make(from: key) 23 | .original as? HKSampleType { 24 | samplePredicates[type] = value 25 | } 26 | if let type = try? SeriesType.make(from: key) 27 | .original as? HKSampleType { 28 | samplePredicates[type] = value 29 | } 30 | if let type = try? CorrelationType.make(from: key) 31 | .original as? HKSampleType { 32 | samplePredicates[type] = value 33 | } 34 | if let type = try? DocumentType.make(from: key) 35 | .original as? HKSampleType { 36 | samplePredicates[type] = value 37 | } 38 | if let type = try? ActivitySummaryType.make(from: key) 39 | .original as? HKSampleType { 40 | samplePredicates[type] = value 41 | } 42 | if let type = try? WorkoutType.make(from: key) 43 | .original as? HKSampleType { 44 | samplePredicates[type] = value 45 | } 46 | if #available(iOS 14.0, *) { 47 | if let type = try? ElectrocardiogramType.make(from: key) 48 | .original as? HKSampleType { 49 | samplePredicates[type] = value 50 | } 51 | } 52 | } 53 | return samplePredicates 54 | } 55 | } 56 | 57 | public extension Dictionary where Key == String, Value == Any { 58 | var asMetadata: Metadata? { 59 | try? Metadata.make(from: self) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+Double.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+Double.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 30.09.20. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension Double { 11 | var asDate: Date { 12 | return Date(timeIntervalSince1970: self) 13 | } 14 | var secondsSince1970: Double { 15 | return (self / 1000) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+Encodable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+Encodable.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 01.10.20. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension Encodable { 11 | func encoded() throws -> String { 12 | let encoder = JSONEncoder() 13 | encoder.outputFormatting = .prettyPrinted 14 | encoder.nonConformingFloatEncodingStrategy = .convertToString(positiveInfinity: Double.infinity.description, negativeInfinity: Double.infinity.description, nan: "-500.0") 15 | let data = try encoder.encode(self) 16 | guard let string = String(data: data, encoding: .utf8) else { 17 | throw HealthKitError.badEncoding("Impossible to encode: \(self)") 18 | } 19 | return string 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+HKActivityMoveMode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+HKActivityMoveMode.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor Kachalov on 27.01.21. 6 | // 7 | 8 | import HealthKit 9 | 10 | @available(iOS 14.0, *) 11 | extension HKActivityMoveMode: @retroactive CustomStringConvertible { 12 | public var description: String { 13 | switch self { 14 | case .activeEnergy: 15 | return "Active energy" 16 | case .appleMoveTime: 17 | return "Apple move time" 18 | @unknown default: 19 | fatalError() 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+HKActivitySummary.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+HKActivitySummary.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 24.09.20. 6 | // 7 | 8 | import HealthKit 9 | 10 | @available(iOS 9.3, *) 11 | extension HKActivitySummary: Harmonizable { 12 | typealias Harmonized = ActivitySummary.Harmonized 13 | 14 | func harmonize() throws -> Harmonized { 15 | let activeEnergyBurnedUnit: HKUnit 16 | if #available(iOS 11.0, *) { 17 | activeEnergyBurnedUnit = HKUnit.largeCalorie() 18 | } else { 19 | activeEnergyBurnedUnit = HKUnit.kilocalorie() 20 | } 21 | let activeEnergyBurned = self.activeEnergyBurned.doubleValue(for: activeEnergyBurnedUnit) 22 | let activeEnergyBurnedGoal = self.activeEnergyBurnedGoal.doubleValue(for: activeEnergyBurnedUnit) 23 | let appleExerciseTimeUnit = HKUnit.minute() 24 | let appleExerciseTime = self.appleExerciseTime.doubleValue(for: appleExerciseTimeUnit) 25 | let appleExerciseTimeGoal = self.appleExerciseTimeGoal.doubleValue(for: appleExerciseTimeUnit) 26 | let appleStandHoursUnit = HKUnit.count() 27 | let appleStandHours = self.appleStandHours.doubleValue(for: appleStandHoursUnit) 28 | let appleStandHoursGoal = self.appleStandHoursGoal.doubleValue(for: appleStandHoursUnit) 29 | return Harmonized( 30 | activeEnergyBurned: activeEnergyBurned, 31 | activeEnergyBurnedGoal: activeEnergyBurnedGoal, 32 | activeEnergyBurnedUnit: activeEnergyBurnedUnit.unitString, 33 | appleExerciseTime: appleExerciseTime, 34 | appleExerciseTimeGoal: appleExerciseTimeGoal, 35 | appleExerciseTimeUnit: appleExerciseTimeUnit.unitString, 36 | appleStandHours: appleStandHours, 37 | appleStandHoursGoal: appleStandHoursGoal, 38 | appleStandHoursUnit: appleStandHoursUnit.unitString 39 | ) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+HKBiologicalSex.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+HKBiologicalSex.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 15.09.20. 6 | // 7 | 8 | import HealthKit 9 | 10 | extension HKBiologicalSex: @retroactive CustomStringConvertible { 11 | public var description: String { 12 | switch self { 13 | case .notSet: 14 | return "na" 15 | case .female: 16 | return "Female" 17 | case .male: 18 | return "Male" 19 | case .other: 20 | return "Other" 21 | @unknown default: 22 | fatalError() 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+HKBloodType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+HKBloodType.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 15.09.20. 6 | // 7 | 8 | import HealthKit 9 | 10 | extension HKBloodType: @retroactive CustomStringConvertible { 11 | public var description: String { 12 | switch self { 13 | case .notSet: 14 | return "na" 15 | case .aPositive: 16 | return "A+" 17 | case .aNegative: 18 | return "A-" 19 | case .bPositive: 20 | return "B+" 21 | case .bNegative: 22 | return "B-" 23 | case .abPositive: 24 | return "AB+" 25 | case .abNegative: 26 | return "AB-" 27 | case .oPositive: 28 | return "O+" 29 | case .oNegative: 30 | return "O-" 31 | @unknown default: 32 | fatalError() 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+HKCategoryType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+HKCategoryType.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor Kachalov on 27.01.21. 6 | // 7 | 8 | import HealthKit 9 | 10 | extension HKCategoryType { 11 | func parsed() throws -> CategoryType { 12 | for type in CategoryType.allCases { 13 | if type.identifier == identifier { 14 | return type 15 | } 16 | } 17 | throw HealthKitError.invalidType( 18 | "Unknown HKCategoryType with identifier:\(identifier)" 19 | ) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+HKCategoryValue.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+HKCategoryValue.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Kachalov, Victor on 05.09.21. 6 | // 7 | 8 | import HealthKit 9 | 10 | extension HKCategoryValue: @retroactive CustomStringConvertible { 11 | public var description: String { 12 | "HKCategoryValue" 13 | } 14 | public var detail: String { 15 | switch self { 16 | case .notApplicable: 17 | return "Not Applicable" 18 | @unknown default: 19 | return "Unknown" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+HKCategoryValueAppetiteChanges.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+HKCategoryValueAppetiteChanges.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Kachalov, Victor on 05.09.21. 6 | // 7 | 8 | import HealthKit 9 | 10 | @available(iOS 13.6, *) 11 | extension HKCategoryValueAppetiteChanges: @retroactive CustomStringConvertible { 12 | public var description: String { 13 | "HKCategoryValueAppetiteChanges" 14 | } 15 | public var detail: String { 16 | switch self { 17 | case .unspecified: 18 | return "Unspecified" 19 | case .noChange: 20 | return "No Change" 21 | case .decreased: 22 | return "Decreased" 23 | case .increased: 24 | return "Increased" 25 | @unknown default: 26 | return "Unknown" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+HKCategoryValueAppleStandHour.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+HKCategoryValueAppleStandHour.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Kachalov, Victor on 05.09.21. 6 | // 7 | 8 | import HealthKit 9 | 10 | extension HKCategoryValueAppleStandHour: @retroactive CustomStringConvertible { 11 | public var description: String { 12 | "HKCategoryValueAppleStandHour" 13 | } 14 | public var detail: String { 15 | switch self { 16 | case .stood: 17 | return "Stood" 18 | case .idle: 19 | return "Idle" 20 | @unknown default: 21 | return "Unknown" 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+HKCategoryValueAppleWalkingSteadinessEvent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+HKCategoryValueAppleWalkingSteadinessEvent.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor Kachalov on 04.10.22. 6 | // 7 | 8 | import HealthKit 9 | 10 | @available(iOS 15.0, *) 11 | extension HKCategoryValueAppleWalkingSteadinessEvent: @retroactive CustomStringConvertible { 12 | public var description: String { 13 | "HKCategoryValueAppleWalkingSteadinessEvent" 14 | } 15 | public var detail: String { 16 | switch self { 17 | case .initialLow: 18 | return "Initial low" 19 | case .initialVeryLow: 20 | return "Initial very low" 21 | case .repeatLow: 22 | return "Repeat low" 23 | case .repeatVeryLow: 24 | return "Repeat very low" 25 | @unknown default: 26 | return "Unknown" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+HKCategoryValueAudioExposureEvent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+HKCategoryValueAudioExposureEvent.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Kachalov, Victor on 05.09.21. 6 | // 7 | 8 | import HealthKit 9 | 10 | @available(iOS 13.0, *) 11 | extension HKCategoryValueAudioExposureEvent: @retroactive CustomStringConvertible { 12 | public var description: String { 13 | "HKCategoryValueAudioExposureEvent" 14 | } 15 | public var detail: String { 16 | switch self { 17 | case .loudEnvironment: 18 | return "Load Environment" 19 | @unknown default: 20 | return "Unknown" 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+HKCategoryValueCervicalMucusQuality.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+HKCategoryValueCervicalMucusQuality.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Kachalov, Victor on 05.09.21. 6 | // 7 | 8 | import HealthKit 9 | 10 | extension HKCategoryValueCervicalMucusQuality: @retroactive CustomStringConvertible { 11 | public var description: String { 12 | "HKCategoryValueCervicalMucusQuality" 13 | } 14 | public var detail: String { 15 | switch self { 16 | case .dry: 17 | return "Dry" 18 | case .sticky: 19 | return "Sticky" 20 | case .creamy: 21 | return "Creamy" 22 | case .watery: 23 | return "Watery" 24 | case .eggWhite: 25 | return "Egg White" 26 | @unknown default: 27 | return "Unknown" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+HKCategoryValueContraceptive.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+HKCategoryValueContraceptive.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Kachalov, Victor on 05.09.21. 6 | // 7 | 8 | import HealthKit 9 | 10 | @available(iOS 14.3, *) 11 | extension HKCategoryValueContraceptive: @retroactive CustomStringConvertible { 12 | public var description: String { 13 | "HKCategoryValueContraceptive" 14 | } 15 | public var detail: String { 16 | switch self { 17 | case .unspecified: 18 | return "Unspecified" 19 | case .implant: 20 | return "Implant" 21 | case .injection: 22 | return "Injection" 23 | case .intrauterineDevice: 24 | return "Intrauterine Device" 25 | case .intravaginalRing: 26 | return "Intravaginal Ring" 27 | case .oral: 28 | return "Oral" 29 | case .patch: 30 | return "Patch" 31 | @unknown default: 32 | return "Unknown" 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+HKCategoryValueEnvironmentalAudioExposureEvent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+HKCategoryValueEnvironmentalAudioExposureEvent.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Kachalov, Victor on 05.09.21. 6 | // 7 | 8 | import HealthKit 9 | 10 | @available(iOS 14.0, *) 11 | extension HKCategoryValueEnvironmentalAudioExposureEvent: @retroactive CustomStringConvertible { 12 | public var description: String { 13 | "HKCategoryValueEnvironmentalAudioExposureEvent" 14 | } 15 | public var detail: String { 16 | switch self { 17 | case .momentaryLimit: 18 | return "Momentary Limit" 19 | @unknown default: 20 | return "Unknown" 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+HKCategoryValueHeadphoneAudioExposureEvent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+HKCategoryValueHeadphoneAudioExposureEvent.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Kachalov, Victor on 05.09.21. 6 | // 7 | 8 | import HealthKit 9 | 10 | @available(iOS 14.2, *) 11 | extension HKCategoryValueHeadphoneAudioExposureEvent: @retroactive CustomStringConvertible { 12 | public var description: String { 13 | "HKCategoryValueHeadphoneAudioExposureEvent" 14 | } 15 | public var detail: String { 16 | switch self { 17 | case .sevenDayLimit: 18 | return "Seven Day Limit" 19 | @unknown default: 20 | return "Unknown" 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+HKCategoryValueLowCardioFitnessEvent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+HKCategoryValueLowCardioFitnessEvent.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Kachalov, Victor on 05.09.21. 6 | // 7 | 8 | import HealthKit 9 | 10 | @available(iOS 14.3, *) 11 | extension HKCategoryValueLowCardioFitnessEvent: @retroactive CustomStringConvertible { 12 | public var description: String { 13 | String(describing: self) 14 | } 15 | public var detail: String { 16 | switch self { 17 | case .lowFitness: 18 | return "Low Fitness" 19 | @unknown default: 20 | return "Unknown" 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+HKCategoryValueMenstrualFlow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+HKCategoryValueMenstrualFlow.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Kachalov, Victor on 05.09.21. 6 | // 7 | 8 | import HealthKit 9 | 10 | extension HKCategoryValueMenstrualFlow: @retroactive CustomStringConvertible { 11 | public var description: String { 12 | "HKCategoryValueMenstrualFlow" 13 | } 14 | public var detail: String { 15 | switch self { 16 | case .unspecified: 17 | return "Unspecified" 18 | case .light: 19 | return "Light" 20 | case .medium: 21 | return "Medium" 22 | case .heavy: 23 | return "Heavy" 24 | case .none: 25 | return "None" 26 | @unknown default: 27 | return "Unknown" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+HKCategoryValueOvulationTestResult.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+HKCategoryValueOvulationTestResult.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Kachalov, Victor on 05.09.21. 6 | // 7 | 8 | import HealthKit 9 | 10 | extension HKCategoryValueOvulationTestResult: @retroactive CustomStringConvertible { 11 | public var description: String { 12 | "HKCategoryValueOvulationTestResult" 13 | } 14 | public var detail: String { 15 | switch self { 16 | case .negative: 17 | return "Negative" 18 | case .luteinizingHormoneSurge: 19 | return "Luteinizing Hormone Surge" 20 | case .indeterminate: 21 | return "Indeterminate" 22 | case .estrogenSurge: 23 | return "Estrogen Surge" 24 | @unknown default: 25 | return "Unknown" 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+HKCategoryValuePregnancyTestResult.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+HKCategoryValuePregnancyTestResult.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor Kachalov on 04.10.22. 6 | // 7 | 8 | import HealthKit 9 | 10 | @available(iOS 15.0, *) 11 | extension HKCategoryValuePregnancyTestResult: @retroactive CustomStringConvertible { 12 | public var description: String { 13 | "HKCategoryValuePregnancyTestResult" 14 | } 15 | public var detail: String { 16 | switch self { 17 | case .negative: 18 | return "Negative" 19 | case .positive: 20 | return "Positive" 21 | case .indeterminate: 22 | return "Indeterminate" 23 | @unknown default: 24 | return "Unknown" 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+HKCategoryValuePresence.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+HKCategoryValuePresence.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Kachalov, Victor on 05.09.21. 6 | // 7 | 8 | import HealthKit 9 | 10 | @available(iOS 13.6, *) 11 | extension HKCategoryValuePresence: @retroactive CustomStringConvertible { 12 | public var description: String { 13 | String(describing: self) 14 | } 15 | public var detail: String { 16 | switch self { 17 | case .present: 18 | return "Present" 19 | case .notPresent: 20 | return "Not Present" 21 | @unknown default: 22 | return "Unknown" 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+HKCategoryValueProgesteroneTestResult.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+HKCategoryValueProgesteroneTestResult.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor Kachalov on 04.10.22. 6 | // 7 | 8 | import HealthKit 9 | 10 | @available(iOS 15.0, *) 11 | extension HKCategoryValueProgesteroneTestResult: @retroactive CustomStringConvertible { 12 | public var description: String { 13 | "HKCategoryValueProgesteroneTestResult" 14 | } 15 | public var detail: String { 16 | switch self { 17 | case .negative: 18 | return "Negative" 19 | case .positive: 20 | return "Positive" 21 | case .indeterminate: 22 | return "Indeterminate" 23 | @unknown default: 24 | return "Unknown" 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+HKCategoryValueSeverity.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+HKCategoryValueSeverity.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Kachalov, Victor on 05.09.21. 6 | // 7 | 8 | import HealthKit 9 | 10 | @available(iOS 13.6, *) 11 | extension HKCategoryValueSeverity: @retroactive CustomStringConvertible { 12 | public var description: String { 13 | "HKCategoryValueSeverity" 14 | } 15 | public var detail: String { 16 | switch self { 17 | case .unspecified: 18 | return "Unspecified" 19 | case .notPresent: 20 | return "Not Present" 21 | case .mild: 22 | return "Mild" 23 | case .moderate: 24 | return "Moderate" 25 | case .severe: 26 | return "Severe" 27 | @unknown default: 28 | return "Unknown" 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+HKCategoryValueSleepAnalysis.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+HKCategoryValueSleepAnalysis.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Kachalov, Victor on 05.09.21. 6 | // 7 | 8 | import HealthKit 9 | 10 | extension HKCategoryValueSleepAnalysis: @retroactive CustomStringConvertible { 11 | public var description: String { 12 | "HKCategoryValueSleepAnalysis" 13 | } 14 | public var detail: String { 15 | switch self { 16 | case .inBed: 17 | return "In Bed" 18 | case .asleepUnspecified: 19 | return "Asleep unspecified" 20 | case .awake: 21 | return "Awake" 22 | case .asleepCore: 23 | return "Asleep core" 24 | case .asleepDeep: 25 | return "Asleep deep" 26 | case .asleepREM: 27 | return "Asleep REM" 28 | @unknown default: 29 | return "Unknown" 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+HKClinicalRecord.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+HKClinicalRecord.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Quentin on 01.08.24. 6 | // 7 | 8 | import HealthKit 9 | 10 | @available(iOS 12.0, *) 11 | extension HKClinicalRecord: Harmonizable { 12 | typealias Harmonized = ClinicalRecord.Harmonized 13 | 14 | func harmonize() throws -> Harmonized { 15 | let fhirVersion: String? = if #available(iOS 14.0, *) { 16 | fhirResource?.fhirVersion.stringRepresentation 17 | } else { 18 | nil 19 | } 20 | var fhirData: String? { 21 | guard let data: Data = fhirResource?.data, 22 | let jsonString = String(data: data, encoding: .utf8) else { 23 | return nil 24 | } 25 | return jsonString 26 | } 27 | 28 | return Harmonized( 29 | displayName: displayName, 30 | fhirSourceUrl: fhirResource?.sourceURL?.absoluteString, 31 | fhirVersion: fhirVersion, 32 | fhirData: fhirData, 33 | metadata: metadata?.asMetadata 34 | ) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+HKCorrelation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+HKCorrelation.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 25.09.20. 6 | // 7 | 8 | import HealthKit 9 | 10 | extension HKCorrelation: Harmonizable { 11 | typealias Harmonized = Correlation.Harmonized 12 | 13 | func harmonize() throws -> Harmonized { 14 | var quantityArray = [Quantity]() 15 | if let quantitySamples = objects as? Set { 16 | for element in quantitySamples { 17 | let quantity = try Quantity(quantitySample: element) 18 | quantityArray.append(quantity) 19 | } 20 | } 21 | var categoryArray = [Category]() 22 | if let categorySamples = objects as? Set { 23 | for element in categorySamples { 24 | let category = try Category(categorySample: element) 25 | categoryArray.append(category) 26 | } 27 | } 28 | return Harmonized( 29 | quantitySamples: quantityArray, 30 | categorySamples: categoryArray, 31 | metadata: metadata?.asMetadata 32 | ) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+HKElectrocardiogram.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+HKElectrocardiogram.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 24.09.20. 6 | // 7 | 8 | import HealthKit 9 | 10 | @available(iOS 14.0, *) 11 | extension HKElectrocardiogram { 12 | typealias Harmonized = Electrocardiogram.Harmonized 13 | 14 | func harmonize(voltageMeasurements: [Electrocardiogram.VoltageMeasurement]) throws -> Harmonized { 15 | let averageHeartRateUnit = HKUnit.count().unitDivided(by: HKUnit.minute()) 16 | let averageHeartRate = averageHeartRate?.doubleValue(for: averageHeartRateUnit) 17 | let samplingFrequencyUnit = HKUnit.hertz() 18 | guard 19 | let samplingFrequency = samplingFrequency?.doubleValue(for: samplingFrequencyUnit) 20 | else { 21 | throw HealthKitError.invalidValue( 22 | "Invalid samplingFrequency value for HKElectrocardiogram" 23 | ) 24 | } 25 | return Harmonized( 26 | averageHeartRate: averageHeartRate, 27 | averageHeartRateUnit: averageHeartRateUnit.unitString, 28 | samplingFrequency: samplingFrequency, 29 | samplingFrequencyUnit: samplingFrequencyUnit.unitString, 30 | classification: classification.description, 31 | symptomsStatus: symptomsStatus.description, 32 | count: numberOfVoltageMeasurements, 33 | voltageMeasurements: voltageMeasurements, 34 | metadata: metadata?.asMetadata 35 | ) 36 | } 37 | } 38 | 39 | @available(iOS 14.0, *) 40 | extension HKElectrocardiogram.VoltageMeasurement: Harmonizable { 41 | typealias Harmonized = Electrocardiogram.VoltageMeasurement.Harmonized 42 | 43 | func harmonize() throws -> Harmonized { 44 | guard 45 | let quantitiy = quantity(for: .appleWatchSimilarToLeadI) 46 | else { 47 | throw HealthKitError.invalidValue( 48 | "Invalid averageHeartRate value for HKElectrocardiogram" 49 | ) 50 | } 51 | let unit = HKUnit.volt() 52 | let voltage = quantitiy.doubleValue(for: unit) 53 | return Harmonized(value: voltage, unit: unit.unitString) 54 | } 55 | } 56 | // MARK: - CustomStringConvertible 57 | @available(iOS 14.0, *) 58 | extension HKElectrocardiogram.Classification: @retroactive CustomStringConvertible { 59 | public var description: String { 60 | switch self { 61 | case .notSet: 62 | return "na" 63 | case .sinusRhythm: 64 | return "Sinus rhytm" 65 | case .atrialFibrillation: 66 | return "Atrial fibrillation" 67 | case .inconclusiveLowHeartRate: 68 | return "Inconclusive low heart rate" 69 | case .inconclusiveHighHeartRate: 70 | return "Inconclusive high heart rate" 71 | case .inconclusivePoorReading: 72 | return "Inconclusive poor reading" 73 | case .inconclusiveOther: 74 | return "Inconclusive other" 75 | case .unrecognized: 76 | return "Unrecognized" 77 | @unknown default: 78 | fatalError() 79 | } 80 | } 81 | } 82 | // MARK: - CustomStringConvertible 83 | @available(iOS 14.0, *) 84 | extension HKElectrocardiogram.SymptomsStatus: @retroactive CustomStringConvertible { 85 | public var description: String { 86 | switch self { 87 | case .notSet: 88 | return "na" 89 | case .none: 90 | return "None" 91 | case .present: 92 | return "Present" 93 | @unknown default: 94 | fatalError() 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+HKFitzpatrickSkinType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+HKFitzpatrickSkinType.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 15.09.20. 6 | // 7 | 8 | import HealthKit 9 | 10 | extension HKFitzpatrickSkinType: @retroactive CustomStringConvertible { 11 | public var description: String { 12 | switch self { 13 | case .notSet: 14 | return "na" 15 | case .I: 16 | return "I" 17 | case .II: 18 | return "II" 19 | case .III: 20 | return "III" 21 | case .IV: 22 | return "IV" 23 | case .V: 24 | return "V" 25 | case .VI: 26 | return "VI" 27 | @unknown default: 28 | fatalError() 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+HKHeartbeatSeriesSample.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+HKHeartbeatSeriesSample.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Kachalov, Victor on 12.10.21. 6 | // 7 | 8 | import HealthKit 9 | 10 | @available(iOS 13.0, *) 11 | extension HKHeartbeatSeriesSample { 12 | typealias Harmonized = HeartbeatSeries.Harmonized 13 | 14 | func harmonize(measurements: [HeartbeatSeries.Measurement]) -> Harmonized { 15 | Harmonized( 16 | count: count, 17 | measurements: measurements, 18 | metadata: metadata?.asMetadata 19 | ) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+HKQuantityType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+HKQuantityType.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 15.09.20. 6 | // 7 | 8 | import HealthKit 9 | 10 | extension HKQuantityType { 11 | func parsed() throws -> QuantityType { 12 | for type in QuantityType.allCases { 13 | if type.identifier == identifier { 14 | return type 15 | } 16 | } 17 | throw HealthKitError.invalidType("Unknown HKObjectType") 18 | } 19 | 20 | var statisticsOptions: HKStatisticsOptions { 21 | switch aggregationStyle { 22 | case .cumulative: 23 | return .cumulativeSum 24 | case .discreteArithmetic, 25 | .discreteTemporallyWeighted, 26 | .discreteEquivalentContinuousLevel: 27 | return .discreteAverage 28 | @unknown default: 29 | fatalError() 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+HKSample.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+HKSample.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 25.09.20. 6 | // 7 | 8 | import HealthKit 9 | 10 | extension HKSample { 11 | func parsed() throws -> Sample { 12 | if let quantitiy = self as? HKQuantitySample { 13 | return try Quantity(quantitySample: quantitiy) 14 | } 15 | if let category = self as? HKCategorySample { 16 | return try Category(categorySample: category) 17 | } 18 | if let workout = self as? HKWorkout { 19 | return try Workout(workout: workout) 20 | } 21 | if let correlation = self as? HKCorrelation { 22 | return try Correlation(correlation: correlation) 23 | } 24 | if #available(iOS 14.0, *) { 25 | if let electrocardiogram = self as? HKElectrocardiogram { 26 | return try Electrocardiogram(electrocardiogram: electrocardiogram, voltageMeasurements: []) 27 | } 28 | } 29 | if #available(iOS 12.0, *) { 30 | if let clinicalRecord = self as? HKClinicalRecord { 31 | return try ClinicalRecord(clinicalRecord: clinicalRecord) 32 | } 33 | } 34 | throw HealthKitError.parsingFailed("HKSample could not be parsed") 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+HKSourceRevision.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+HKSourceRevision.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 24.09.20. 6 | // 7 | 8 | import HealthKit 9 | 10 | extension HKSourceRevision { 11 | @available(iOS 11.0, *) 12 | var systemVersion: String { 13 | let major = operatingSystemVersion.majorVersion 14 | let minor = operatingSystemVersion.minorVersion 15 | let patch = operatingSystemVersion.patchVersion 16 | return "\(major).\(minor).\(patch)" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+HKVisionPrescription.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+HKVisionPrescription.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor Kachalov on 04.10.22. 6 | // 7 | 8 | import HealthKit 9 | 10 | @available(iOS 16.0, *) 11 | extension HKVisionPrescription: Harmonizable { 12 | typealias Harmonized = VisionPrescription.Harmonized 13 | 14 | func harmonize() throws -> Harmonized { 15 | return Harmonized( 16 | dateIssuedTimestamp: dateIssued.millisecondsSince1970, 17 | expirationDateTimestamp: expirationDate?.millisecondsSince1970, 18 | prescriptionType: VisionPrescription.PrescriptionType(prescriptionType: prescriptionType), 19 | metadata: metadata?.asMetadata 20 | ) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+HKVisionPrescriptionType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+HKVisionPrescriptionType.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor Kachalov on 04.10.22. 6 | // 7 | 8 | import HealthKit 9 | 10 | @available(iOS 16.0, *) 11 | extension HKVisionPrescriptionType: @retroactive CustomStringConvertible { 12 | public var description: String { 13 | "HKVisionPrescriptionType" 14 | } 15 | public var detail: String { 16 | switch self { 17 | case .glasses: 18 | return "Glasses" 19 | case .contacts: 20 | return "Contacts" 21 | @unknown default: 22 | return "Unknown" 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+HKWheelchairUse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+HKWheelchairUse.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor Kachalov on 27.01.21. 6 | // 7 | 8 | import HealthKit 9 | 10 | @available(iOS 10.0, *) 11 | extension HKWheelchairUse { 12 | var string: String { 13 | switch self { 14 | case .notSet: 15 | return "na" 16 | case .no: 17 | return "No" 18 | case .yes: 19 | return "Yes" 20 | @unknown default: 21 | fatalError() 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+HKWorkout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+HKWorkout.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 25.09.20. 6 | // 7 | 8 | import HealthKit 9 | 10 | extension HKWorkout: Harmonizable { 11 | typealias Harmonized = Workout.Harmonized 12 | 13 | func harmonize() throws -> Harmonized { 14 | let totalEnergyBurnedUnit: HKUnit 15 | if #available(iOS 11.0, *) { 16 | totalEnergyBurnedUnit = HKUnit.largeCalorie() 17 | } else { 18 | totalEnergyBurnedUnit = HKUnit.kilocalorie() 19 | } 20 | let totalEnergyBurned = self.totalEnergyBurned?.doubleValue(for: totalEnergyBurnedUnit) 21 | 22 | let totalDistanceUnit = HKUnit.meter() 23 | let totalDistance = self.totalDistance?.doubleValue(for: totalDistanceUnit) 24 | 25 | let countUnit = HKUnit.count() 26 | var totalSwimmingStrokeCount: Double? 27 | if #available(iOS 10.0, *) { 28 | totalSwimmingStrokeCount = self.totalSwimmingStrokeCount?.doubleValue(for: countUnit) 29 | } 30 | var totalFlightsClimbed: Double? 31 | if #available(iOS 11.0, *) { 32 | totalFlightsClimbed = self.totalFlightsClimbed?.doubleValue(for: countUnit) 33 | } 34 | return Harmonized( 35 | value: Int(workoutActivityType.rawValue), 36 | description: workoutActivityType.description, 37 | totalEnergyBurned: totalEnergyBurned, 38 | totalEnergyBurnedUnit: totalEnergyBurnedUnit.unitString, 39 | totalDistance: totalDistance, 40 | totalDistanceUnit: totalDistanceUnit.unitString, 41 | totalSwimmingStrokeCount: totalSwimmingStrokeCount, 42 | totalSwimmingStrokeCountUnit: countUnit.unitString, 43 | totalFlightsClimbed: totalFlightsClimbed, 44 | totalFlightsClimbedUnit: countUnit.unitString, 45 | metadata: metadata?.asMetadata 46 | ) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+HKWorkoutConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+HKWorkoutConfiguration.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 25.09.20. 6 | // 7 | 8 | import HealthKit 9 | 10 | @available(iOS 10.0, *) 11 | extension HKWorkoutConfiguration: Harmonizable { 12 | typealias Harmonized = WorkoutConfiguration.Harmonized 13 | 14 | func harmonize() throws -> Harmonized { 15 | let unit = HKUnit.meter() 16 | guard let value = lapLength?.doubleValue(for: unit) else { 17 | throw HealthKitError.invalidValue("Value for HKWorkoutConfiguration is invalid") 18 | } 19 | return Harmonized( 20 | value: value, 21 | unit: unit.unitString 22 | ) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+HKWorkoutEvent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+HKWorkoutEvent.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 25.09.20. 6 | // 7 | 8 | import HealthKit 9 | 10 | extension HKWorkoutEvent: Harmonizable { 11 | typealias Harmonized = WorkoutEvent.Harmonized 12 | 13 | func harmonize() throws -> Harmonized { 14 | if #available(iOS 10.0, *) { 15 | return Harmonized( 16 | value: type.rawValue, 17 | description: type.description, 18 | metadata: metadata?.asMetadata 19 | ) 20 | } else { 21 | throw HealthKitError.notAvailable( 22 | "Metadata is not available for the current iOS" 23 | ) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+HKWorkoutEventType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Kachalov, Victor on 04.09.21. 6 | // 7 | 8 | import HealthKit 9 | 10 | extension HKWorkoutEventType: @retroactive CustomStringConvertible { 11 | public var description: String { 12 | switch self { 13 | case .pause: 14 | return "Pause" 15 | case .resume: 16 | return "Resume" 17 | case .lap: 18 | return "Lap" 19 | case .marker: 20 | return "Marker" 21 | case .motionPaused: 22 | return "Motion paused" 23 | case .motionResumed: 24 | return "Motion Resumed" 25 | case .segment: 26 | return "Segment" 27 | case .pauseOrResumeRequest: 28 | return "Pause on resume request" 29 | @unknown default: 30 | fatalError() 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+HKWorkoutRoute.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+HKWorkoutRoute.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor Kachalov on 16.04.22. 6 | // 7 | 8 | import HealthKit 9 | 10 | @available(iOS 11.0, *) 11 | extension HKWorkoutRoute { 12 | typealias Harmonized = WorkoutRoute.Harmonized 13 | 14 | func harmonize(routes: [WorkoutRoute.Route]) -> Harmonized { 15 | Harmonized( 16 | count: count, 17 | routes: routes, 18 | metadata: metadata?.asMetadata 19 | ) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+NSPredicate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+NSPredicate.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 14.09.20. 6 | // 7 | 8 | import HealthKit 9 | 10 | public extension NSPredicate { 11 | static var allSamples: NSPredicate { 12 | return HKQuery.predicateForSamples( 13 | withStart: .distantPast, 14 | end: .distantFuture, 15 | options: [] 16 | ) 17 | } 18 | static func samplesPredicate( 19 | startDate: Date, 20 | endDate: Date, 21 | options: HKQueryOptions = [.strictStartDate, .strictEndDate] 22 | ) -> NSPredicate { 23 | return HKQuery.predicateForSamples( 24 | withStart: startDate, 25 | end: endDate, 26 | options: options 27 | ) 28 | } 29 | @available(iOS 9.3, *) 30 | static func activitySummaryPredicate( 31 | dateComponents: DateComponents 32 | ) -> NSPredicate { 33 | return HKQuery.predicateForActivitySummary(with: dateComponents) 34 | } 35 | @available(iOS 9.3, *) 36 | static func activitySummaryPredicateBetween( 37 | start: DateComponents, 38 | end: DateComponents 39 | ) -> NSPredicate { 40 | return HKQuery.predicate(forActivitySummariesBetweenStart: start, end: end) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Sources/Decorator/Extensions+String.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions+String.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 25.09.20. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension String { 11 | var integer: Int? { 12 | return Int(self) 13 | } 14 | var double: Double? { 15 | return Double(self) 16 | } 17 | var boolean: Bool { 18 | return (self as NSString).boolValue 19 | } 20 | var objectType: ObjectType? { 21 | if let type = try? QuantityType.make(from: self) { 22 | return type 23 | } 24 | if let type = try? CategoryType.make(from: self) { 25 | return type 26 | } 27 | if let type = try? CharacteristicType.make(from: self) { 28 | return type 29 | } 30 | if let type = try? SeriesType.make(from: self) { 31 | return type 32 | } 33 | if let type = try? CorrelationType.make(from: self) { 34 | return type 35 | } 36 | if let type = try? DocumentType.make(from: self) { 37 | return type 38 | } 39 | if let type = try? ActivitySummaryType.make(from: self) { 40 | return type 41 | } 42 | if let type = try? WorkoutType.make(from: self) { 43 | return type 44 | } 45 | if #available(iOS 14.0, *) { 46 | if let type = try? ElectrocardiogramType.make(from: self) { 47 | return type 48 | } 49 | } 50 | if #available(iOS 12.0, *) { 51 | if let type = try? ClinicalType.make(from: self) { 52 | return type 53 | } 54 | } 55 | return nil 56 | } 57 | 58 | func asDate( 59 | format: String, 60 | timezone: TimeZone = TimeZone.current 61 | ) -> Date? { 62 | let dateFormatter = DateFormatter() 63 | dateFormatter.dateFormat = format 64 | dateFormatter.timeZone = timezone 65 | let date = dateFormatter.date(from: self) 66 | return date 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Sources/HealthKitError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HealthKitError.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 24.09.20. 6 | // 7 | 8 | import Foundation 9 | 10 | public enum HealthKitError: Error { 11 | case notAvailable(String = "HealthKit data is not available") 12 | case unknown(String = "Unknown") 13 | case invalidType(String = "Invalid type") 14 | case invalidIdentifier(String = "Invalid identifier") 15 | case invalidOption(String = "Invalid option") 16 | case invalidValue(String = "Invalid value") 17 | case parsingFailed(String = "Parsing failed") 18 | case badEncoding(String) 19 | case notImplementable(String) 20 | } 21 | -------------------------------------------------------------------------------- /Sources/Model/Harmonizable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Harmonizable.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 25.09.20. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol Harmonizable { 11 | associatedtype Harmonized: Codable 12 | 13 | func harmonize() throws -> Harmonized 14 | } 15 | -------------------------------------------------------------------------------- /Sources/Model/Metadata.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Metadata.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor Kachalov on 29.10.22. 6 | // 7 | 8 | import Foundation 9 | 10 | public enum Metadata: Codable { 11 | case string(dictionary: [String: String]?) 12 | case date(dictionary: [String: Date]?) 13 | case double(dictionary: [String: Double]?) 14 | 15 | public var original: [String: Any]? { 16 | switch self { 17 | case .string(dictionary: let dictionary): 18 | return dictionary 19 | case .date(dictionary: let dictionary): 20 | return dictionary 21 | case .double(dictionary: let dictionary): 22 | return dictionary 23 | } 24 | } 25 | } 26 | // MARK: - Metadata: ExpressibleByDictionaryLiteral, Equatable 27 | extension Metadata: ExpressibleByDictionaryLiteral, Equatable { 28 | public typealias Key = String 29 | public typealias Value = Any 30 | 31 | public init(dictionaryLiteral elements: (Key, Value)...) { 32 | var dictionary = [String: Any]() 33 | for pair in elements { 34 | dictionary[pair.0] = pair.1 35 | } 36 | do { 37 | self = try Metadata.make(from: dictionary) 38 | } catch { 39 | self = [:] 40 | } 41 | } 42 | } 43 | // MARK: - Metadata: Payload 44 | extension Metadata: Payload { 45 | public static func make(from dictionary: [String : Any]) throws -> Metadata { 46 | if let stringDictionary = dictionary as? [String: String] { 47 | return Metadata.string(dictionary: stringDictionary) 48 | } 49 | if let dateDictionary = dictionary as? [String: Date] { 50 | return Metadata.date(dictionary: dateDictionary) 51 | } 52 | if let doubleDictionary = dictionary as? [String: Double] { 53 | return Metadata.double(dictionary: doubleDictionary) 54 | } 55 | throw HealthKitError.invalidValue("Invalid dictionary: \(dictionary)") 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Sources/Model/Original.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Writable.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 25.09.20. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol Original { 11 | associatedtype Object: NSObject 12 | 13 | func asOriginal() throws -> Object 14 | } 15 | -------------------------------------------------------------------------------- /Sources/Model/Payload.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Payload.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 15.11.20. 6 | // 7 | 8 | import Foundation 9 | 10 | public protocol Payload { 11 | static func make(from dictionary: [String: Any]) throws -> Self 12 | } 13 | -------------------------------------------------------------------------------- /Sources/Model/Payload/ActivitySummary.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ActivitySummary.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 25.09.20. 6 | // 7 | 8 | import HealthKit 9 | 10 | @available(iOS 9.3, *) 11 | public struct ActivitySummary: Identifiable { 12 | public struct Harmonized: Codable { 13 | public let activeEnergyBurned: Double 14 | public let activeEnergyBurnedGoal: Double 15 | public let activeEnergyBurnedUnit: String 16 | public let appleExerciseTime: Double 17 | public let appleExerciseTimeGoal: Double 18 | public let appleExerciseTimeUnit: String 19 | public let appleStandHours: Double 20 | public let appleStandHoursGoal: Double 21 | public let appleStandHoursUnit: String 22 | } 23 | 24 | public let identifier: String 25 | public let date: String? 26 | public let harmonized: Harmonized 27 | 28 | init(activitySummary: HKActivitySummary) throws { 29 | self.identifier = ActivitySummaryType 30 | .activitySummaryType 31 | .original? 32 | .identifier ?? "HKActivitySummaryTypeIdentifier" 33 | self.date = activitySummary 34 | .dateComponents(for: Calendar.current) 35 | .date? 36 | .formatted(with: Date.iso8601) 37 | self.harmonized = try activitySummary.harmonize() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/Model/Payload/Characteristic.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Characteristic.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 25.09.20. 6 | // 7 | 8 | import HealthKit 9 | 10 | public struct Characteristic: Codable { 11 | public let biologicalSex: String? 12 | public let birthday: String? 13 | public let bloodType: String? 14 | public let fitzpatrickSkinType: String? 15 | public let wheelchairUse: String? 16 | public let activityMoveMode: String? 17 | 18 | init( 19 | biologicalSex: HKBiologicalSexObject?, 20 | bloodType: HKBloodTypeObject?, 21 | fitzpatrickSkinType: HKFitzpatrickSkinTypeObject? 22 | ) { 23 | self.biologicalSex = biologicalSex?.biologicalSex.description 24 | self.bloodType = bloodType?.bloodType.description 25 | self.fitzpatrickSkinType = fitzpatrickSkinType?.skinType.description 26 | self.birthday = nil 27 | self.wheelchairUse = nil 28 | self.activityMoveMode = nil 29 | } 30 | 31 | @available(iOS 10.0, *) 32 | init( 33 | biologicalSex: HKBiologicalSexObject?, 34 | birthday: DateComponents?, 35 | bloodType: HKBloodTypeObject?, 36 | fitzpatrickSkinType: HKFitzpatrickSkinTypeObject?, 37 | wheelchairUse: HKWheelchairUseObject? 38 | ) { 39 | self.biologicalSex = biologicalSex?.biologicalSex.description 40 | self.birthday = birthday?.date?.formatted(with: Date.iso8601) 41 | self.bloodType = bloodType?.bloodType.description 42 | self.fitzpatrickSkinType = fitzpatrickSkinType?.skinType.description 43 | self.wheelchairUse = wheelchairUse?.wheelchairUse.string 44 | self.activityMoveMode = nil 45 | } 46 | 47 | @available(iOS 14.0, *) 48 | init( 49 | biologicalSex: HKBiologicalSexObject?, 50 | birthday: DateComponents?, 51 | bloodType: HKBloodTypeObject?, 52 | fitzpatrickSkinType: HKFitzpatrickSkinTypeObject?, 53 | wheelchairUse: HKWheelchairUseObject?, 54 | activityMoveMode: HKActivityMoveModeObject? 55 | ) { 56 | self.biologicalSex = biologicalSex?.biologicalSex.description 57 | self.birthday = birthday?.date?.formatted(with: Date.iso8601) 58 | self.bloodType = bloodType?.bloodType.description 59 | self.fitzpatrickSkinType = fitzpatrickSkinType?.skinType.description 60 | self.wheelchairUse = wheelchairUse?.wheelchairUse.string 61 | self.activityMoveMode = activityMoveMode?.activityMoveMode.description 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Sources/Model/Payload/DeletedObject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeletedObject.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Kachalov, Victor on 16.02.21. 6 | // 7 | 8 | import HealthKit 9 | 10 | public struct DeletedObject: Codable { 11 | public let uuid: String 12 | public let metadata: Metadata? 13 | 14 | init(deletedObject: HKDeletedObject) { 15 | self.uuid = deletedObject.uuid.uuidString 16 | if #available(iOS 11.0, *) { 17 | self.metadata = deletedObject.metadata?.asMetadata 18 | } else { 19 | self.metadata = nil 20 | } 21 | } 22 | } 23 | // MARK: - Factory 24 | extension DeletedObject { 25 | public static func collect( 26 | deletedObjects: [HKDeletedObject]? 27 | ) -> [DeletedObject] { 28 | return deletedObjects?.compactMap { 29 | DeletedObject(deletedObject: $0) 30 | } ?? [] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/Model/Payload/Device.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Device.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 25.09.20. 6 | // 7 | 8 | import HealthKit 9 | 10 | public struct Device: Codable { 11 | public let name: String? 12 | public let manufacturer: String? 13 | public let model: String? 14 | public let hardwareVersion: String? 15 | public let firmwareVersion: String? 16 | public let softwareVersion: String? 17 | public let localIdentifier: String? 18 | public let udiDeviceIdentifier: String? 19 | 20 | init(device: HKDevice?) { 21 | self.name = device?.name 22 | self.manufacturer = device?.manufacturer 23 | self.model = device?.model 24 | self.hardwareVersion = device?.hardwareVersion 25 | self.firmwareVersion = device?.firmwareVersion 26 | self.softwareVersion = device?.softwareVersion 27 | self.localIdentifier = device?.localIdentifier 28 | self.udiDeviceIdentifier = device?.udiDeviceIdentifier 29 | } 30 | 31 | public init( 32 | name: String?, 33 | manufacturer: String?, 34 | model: String?, 35 | hardwareVersion: String?, 36 | firmwareVersion: String?, 37 | softwareVersion: String?, 38 | localIdentifier: String?, 39 | udiDeviceIdentifier: String? 40 | ) { 41 | self.name = name 42 | self.manufacturer = manufacturer 43 | self.model = model 44 | self.hardwareVersion = hardwareVersion 45 | self.firmwareVersion = firmwareVersion 46 | self.softwareVersion = softwareVersion 47 | self.localIdentifier = localIdentifier 48 | self.udiDeviceIdentifier = udiDeviceIdentifier 49 | } 50 | 51 | public static func local() -> Device { 52 | let local = HKDevice.local() 53 | return Device(device: local) 54 | } 55 | 56 | public func copyWith( 57 | name: String? = nil, 58 | manufacturer: String? = nil, 59 | model: String? = nil, 60 | hardwareVersion: String? = nil, 61 | firmwareVersion: String? = nil, 62 | softwareVersion: String? = nil, 63 | localIdentifier: String? = nil, 64 | udiDeviceIdentifier: String? = nil 65 | ) -> Device { 66 | return Device( 67 | name: name ?? self.name, 68 | manufacturer: manufacturer ?? self.manufacturer, 69 | model: model ?? self.model, 70 | hardwareVersion: hardwareVersion ?? self.hardwareVersion, 71 | firmwareVersion: firmwareVersion ?? self.firmwareVersion, 72 | softwareVersion: softwareVersion ?? self.softwareVersion, 73 | localIdentifier: localIdentifier ?? self.localIdentifier, 74 | udiDeviceIdentifier: udiDeviceIdentifier ?? self.udiDeviceIdentifier 75 | ) 76 | } 77 | } 78 | // MARK: - Original 79 | extension Device: Original { 80 | func asOriginal() -> HKDevice { 81 | return HKDevice( 82 | name: name, 83 | manufacturer: manufacturer, 84 | model: model, 85 | hardwareVersion: hardwareVersion, 86 | firmwareVersion: firmwareVersion, 87 | softwareVersion: softwareVersion, 88 | localIdentifier: localIdentifier, 89 | udiDeviceIdentifier: udiDeviceIdentifier 90 | ) 91 | } 92 | } 93 | // MARK: - Payload 94 | extension Device: Payload { 95 | public static func make( 96 | from dictionary: [String: Any] 97 | ) throws -> Device { 98 | return Device( 99 | name: dictionary["name"] as? String, 100 | manufacturer: dictionary["manufacturer"] as? String, 101 | model: dictionary["model"] as? String, 102 | hardwareVersion: dictionary["hardwareVersion"] as? String, 103 | firmwareVersion: dictionary["firmwareVersion"] as? String, 104 | softwareVersion: dictionary["softwareVersion"] as? String, 105 | localIdentifier: dictionary["localIdentifier"] as? String, 106 | udiDeviceIdentifier: dictionary["udiDeviceIdentifier"] as? String 107 | ) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Sources/Model/Payload/Identifiable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Identifiable.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 25.09.20. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol Identifiable: Codable { 11 | var identifier: String { get } 12 | } 13 | 14 | 15 | -------------------------------------------------------------------------------- /Sources/Model/Payload/Sample.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Sample.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 14.09.20. 6 | // 7 | 8 | import Foundation 9 | 10 | public protocol Sample: Codable { 11 | var startTimestamp: Double { get } 12 | var endTimestamp: Double { get } 13 | } 14 | -------------------------------------------------------------------------------- /Sources/Model/Payload/Source.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Source.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 25.09.20. 6 | // 7 | 8 | import HealthKit 9 | 10 | public struct Source: Codable { 11 | public let name: String 12 | public let bundleIdentifier: String 13 | 14 | init(source: HKSource) { 15 | self.name = source.name 16 | self.bundleIdentifier = source.bundleIdentifier 17 | } 18 | 19 | public init(name: String, bundleIdentifier: String) { 20 | self.name = name 21 | self.bundleIdentifier = bundleIdentifier 22 | } 23 | 24 | public func copyWith( 25 | name: String? = nil, 26 | bundleIdentifier: String? = nil 27 | ) -> Source { 28 | return Source( 29 | name: name ?? self.name, 30 | bundleIdentifier: bundleIdentifier ?? self.bundleIdentifier 31 | ) 32 | } 33 | } 34 | // MARK: - Original 35 | extension Source: Original { 36 | func asOriginal() throws -> HKSource { 37 | return HKSource.default() 38 | } 39 | } 40 | // MARK: - Payload 41 | extension Source: Payload { 42 | public static func make(from dictionary: [String: Any]) throws -> Source { 43 | guard 44 | let name = dictionary["name"] as? String, 45 | let bundleIdentifier = dictionary["bundleIdentifier"] as? String 46 | else { 47 | throw HealthKitError.invalidValue("Invalid dictionary: \(dictionary)") 48 | } 49 | return Source(name: name, bundleIdentifier: bundleIdentifier) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Sources/Model/Payload/SourceRevision.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SourceRevision.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 25.09.20. 6 | // 7 | 8 | import HealthKit 9 | 10 | public struct SourceRevision: Codable { 11 | public struct OperatingSystem: Codable { 12 | public let majorVersion: Int 13 | public let minorVersion: Int 14 | public let patchVersion: Int 15 | 16 | var original: OperatingSystemVersion { 17 | return OperatingSystemVersion( 18 | majorVersion: majorVersion, 19 | minorVersion: minorVersion, 20 | patchVersion: patchVersion 21 | ) 22 | } 23 | 24 | init(version: OperatingSystemVersion) { 25 | self.majorVersion = version.majorVersion 26 | self.minorVersion = version.minorVersion 27 | self.patchVersion = version.patchVersion 28 | } 29 | 30 | public init( 31 | majorVersion: Int, 32 | minorVersion: Int, 33 | patchVersion: Int 34 | ) { 35 | self.majorVersion = majorVersion 36 | self.minorVersion = minorVersion 37 | self.patchVersion = patchVersion 38 | } 39 | 40 | public func copyWith( 41 | majorVersion: Int? = nil, 42 | minorVersion: Int? = nil, 43 | patchVersion: Int? = nil 44 | ) -> OperatingSystem { 45 | return OperatingSystem( 46 | majorVersion: majorVersion ?? self.majorVersion, 47 | minorVersion: minorVersion ?? self.minorVersion, 48 | patchVersion: patchVersion ?? self.patchVersion 49 | ) 50 | } 51 | } 52 | 53 | public let source: Source 54 | public let version: String? 55 | public let productType: String? 56 | public let systemVersion: String 57 | public let operatingSystem: OperatingSystem 58 | 59 | init(sourceRevision: HKSourceRevision) { 60 | self.source = Source(source: sourceRevision.source) 61 | self.version = sourceRevision.version 62 | if #available(iOS 11.0, *) { 63 | self.productType = sourceRevision.productType 64 | self.systemVersion = sourceRevision.systemVersion 65 | self.operatingSystem = OperatingSystem( 66 | version: sourceRevision.operatingSystemVersion 67 | ) 68 | } else { 69 | self.productType = nil 70 | self.systemVersion = "10.0.0" 71 | self.operatingSystem = OperatingSystem( 72 | majorVersion: 10, 73 | minorVersion: 0, 74 | patchVersion: 0 75 | ) 76 | } 77 | } 78 | 79 | public init( 80 | source: Source, 81 | version: String?, 82 | productType: String?, 83 | systemVersion: String, 84 | operatingSystem: OperatingSystem 85 | ) { 86 | self.source = source 87 | self.version = version 88 | self.productType = productType 89 | self.systemVersion = systemVersion 90 | self.operatingSystem = operatingSystem 91 | } 92 | 93 | public func copyWith( 94 | source: Source? = nil, 95 | version: String? = nil, 96 | productType: String? = nil, 97 | systemVersion: String? = nil, 98 | operatingSystem: OperatingSystem? = nil 99 | ) -> SourceRevision { 100 | return SourceRevision( 101 | source: source ?? self.source, 102 | version: version ?? self.version, 103 | productType: productType ?? self.productType, 104 | systemVersion: systemVersion ?? self.systemVersion, 105 | operatingSystem: operatingSystem ?? self.operatingSystem 106 | ) 107 | } 108 | } 109 | // MARK: - Original 110 | extension SourceRevision: Original { 111 | func asOriginal() throws -> HKSourceRevision { 112 | if #available(iOS 11.0, *) { 113 | return HKSourceRevision( 114 | source: try source.asOriginal(), 115 | version: version, 116 | productType: productType, 117 | operatingSystemVersion: operatingSystem.original 118 | ) 119 | } else { 120 | throw HealthKitError.notAvailable( 121 | "HKSourceRevision is not available for the current iOS" 122 | ) 123 | } 124 | } 125 | } 126 | // MARK: - Payload 127 | extension SourceRevision.OperatingSystem: Payload { 128 | public static func make( 129 | from dictionary: [String: Any] 130 | ) throws -> SourceRevision.OperatingSystem { 131 | guard 132 | let majorVersion = dictionary["majorVersion"] as? Int, 133 | let minorVersion = dictionary["minorVersion"] as? Int, 134 | let patchVersion = dictionary["patchVersion"] as? Int 135 | else { 136 | throw HealthKitError.invalidValue("Invalid dictionary: \(dictionary)") 137 | } 138 | return SourceRevision.OperatingSystem( 139 | majorVersion: majorVersion, 140 | minorVersion: minorVersion, 141 | patchVersion: patchVersion 142 | ) 143 | } 144 | } 145 | // MARK: - Payload 146 | extension SourceRevision: Payload { 147 | public static func make( 148 | from dictionary: [String: Any] 149 | ) throws -> SourceRevision { 150 | guard 151 | let systemVersion = dictionary["systemVersion"] as? String, 152 | let operatingSystem = dictionary["operatingSystem"] as? [String: Any], 153 | let source = dictionary["source"] as? [String: Any] 154 | else { 155 | throw HealthKitError.invalidValue("Invalid dictionary: \(dictionary)") 156 | } 157 | let version = dictionary["version"] as? String 158 | let productType = dictionary["productType"] as? String 159 | return SourceRevision( 160 | source: try Source.make(from: source), 161 | version: version, 162 | productType: productType, 163 | systemVersion: systemVersion, 164 | operatingSystem: try OperatingSystem.make(from: operatingSystem) 165 | ) 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /Sources/Model/Payload/Statistics.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Statistics.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 25.09.20. 6 | // 7 | 8 | import HealthKit 9 | 10 | public struct Statistics: Identifiable, Sample { 11 | public struct Harmonized: Codable { 12 | public let summary: Double? 13 | public let average: Double? 14 | public let recent: Double? 15 | public let min: Double? 16 | public let max: Double? 17 | public let unit: String 18 | 19 | public init( 20 | summary: Double?, 21 | average: Double?, 22 | recent: Double?, 23 | min: Double?, 24 | max: Double?, 25 | unit: String 26 | ) { 27 | self.summary = summary 28 | self.average = average 29 | self.recent = recent 30 | self.min = min 31 | self.max = max 32 | self.unit = unit 33 | } 34 | 35 | public func copyWith( 36 | summary: Double? = nil, 37 | average: Double? = nil, 38 | recent: Double? = nil, 39 | min: Double? = nil, 40 | max: Double? = nil, 41 | unit: String? = nil 42 | ) -> Harmonized { 43 | return Harmonized( 44 | summary: summary ?? self.summary, 45 | average: average ?? self.average, 46 | recent: recent ?? self.recent, 47 | min: min ?? self.min, 48 | max: max ?? self.max, 49 | unit: unit ?? self.unit 50 | ) 51 | } 52 | } 53 | 54 | public let identifier: String 55 | public let startTimestamp: Double 56 | public let endTimestamp: Double 57 | public let harmonized: Harmonized 58 | public let sources: [Source] 59 | 60 | init(statistics: HKStatistics, unit: HKUnit) throws { 61 | self.identifier = statistics.quantityType.identifier 62 | self.startTimestamp = statistics.startDate.timeIntervalSince1970 63 | self.endTimestamp = statistics.endDate.timeIntervalSince1970 64 | self.sources = statistics.sources?.map { Source(source: $0) } ?? [] 65 | if #available(iOS 12.0, *) { 66 | self.harmonized = Harmonized( 67 | summary: statistics.sumQuantity()?.doubleValue(for: unit), 68 | average: statistics.averageQuantity()?.doubleValue(for: unit), 69 | recent: statistics.mostRecentQuantity()?.doubleValue(for: unit), 70 | min: statistics.minimumQuantity()?.doubleValue(for: unit), 71 | max: statistics.maximumQuantity()?.doubleValue(for: unit), 72 | unit: unit.unitString 73 | ) 74 | } else { 75 | self.harmonized = Harmonized( 76 | summary: statistics.sumQuantity()?.doubleValue(for: unit), 77 | average: statistics.averageQuantity()?.doubleValue(for: unit), 78 | recent: nil, 79 | min: statistics.minimumQuantity()?.doubleValue(for: unit), 80 | max: statistics.maximumQuantity()?.doubleValue(for: unit), 81 | unit: unit.unitString 82 | ) 83 | } 84 | } 85 | init(statistics: HKStatistics) throws { 86 | self.identifier = statistics.quantityType.identifier 87 | self.startTimestamp = statistics.startDate.timeIntervalSince1970 88 | self.endTimestamp = statistics.endDate.timeIntervalSince1970 89 | self.sources = statistics.sources?.map { Source(source: $0) } ?? [] 90 | self.harmonized = try statistics.harmonize() 91 | } 92 | 93 | private init( 94 | identifier: String, 95 | startTimestamp: Double, 96 | endTimestamp: Double, 97 | harmonized: Harmonized, 98 | sources: [Source] 99 | ) { 100 | self.identifier = identifier 101 | self.startTimestamp = startTimestamp 102 | self.endTimestamp = endTimestamp 103 | self.harmonized = harmonized 104 | self.sources = sources 105 | } 106 | 107 | public func copyWith( 108 | identifier: String? = nil, 109 | startTimestamp: Double? = nil, 110 | endTimestamp: Double? = nil, 111 | harmonized: Harmonized? = nil, 112 | sources: [Source]? = nil 113 | ) -> Statistics { 114 | return Statistics( 115 | identifier: identifier ?? self.identifier, 116 | startTimestamp: startTimestamp ?? self.startTimestamp, 117 | endTimestamp: endTimestamp ?? self.endTimestamp, 118 | harmonized: harmonized ?? self.harmonized, 119 | sources: sources ?? self.sources 120 | ) 121 | } 122 | } 123 | // MARK: - UnitConvertable 124 | extension Statistics: UnitConvertable { 125 | public func converted(to unit: String) throws -> Statistics { 126 | guard harmonized.unit != unit else { 127 | return self 128 | } 129 | return copyWith(harmonized: harmonized.copyWith(unit: unit)) 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /Sources/Model/Payload/VisionPrescription.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VisionPrescription.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor Kachalov on 04.10.22. 6 | // 7 | 8 | import HealthKit 9 | 10 | @available(iOS 16.0, *) 11 | public struct VisionPrescription: Identifiable, Sample { 12 | public struct PrescriptionType: Codable { 13 | public let id: Int 14 | public let detail: String 15 | 16 | init(id: Int, detail: String) { 17 | self.id = id 18 | self.detail = detail 19 | } 20 | 21 | init(prescriptionType: HKVisionPrescriptionType) { 22 | self.id = Int(prescriptionType.rawValue) 23 | self.detail = prescriptionType.detail 24 | } 25 | } 26 | 27 | public struct Harmonized: Codable { 28 | public let dateIssuedTimestamp: Double 29 | public let expirationDateTimestamp: Double? 30 | public let prescriptionType: PrescriptionType 31 | public let metadata: Metadata? 32 | 33 | init( 34 | dateIssuedTimestamp: Double, 35 | expirationDateTimestamp: Double?, 36 | prescriptionType: PrescriptionType, 37 | metadata: Metadata? 38 | ) { 39 | self.dateIssuedTimestamp = dateIssuedTimestamp 40 | self.expirationDateTimestamp = expirationDateTimestamp 41 | self.prescriptionType = prescriptionType 42 | self.metadata = metadata 43 | } 44 | } 45 | 46 | public let uuid: String 47 | public let identifier: String 48 | public let startTimestamp: Double 49 | public let endTimestamp: Double 50 | public let device: Device? 51 | public let sourceRevision: SourceRevision 52 | public let harmonized: Harmonized 53 | 54 | init( 55 | identifier: String, 56 | startTimestamp: Double, 57 | endTimestamp: Double, 58 | device: Device?, 59 | sourceRevision: SourceRevision, 60 | harmonized: Harmonized 61 | ) { 62 | self.uuid = UUID().uuidString 63 | self.identifier = identifier 64 | self.startTimestamp = startTimestamp 65 | self.endTimestamp = endTimestamp 66 | self.device = device 67 | self.sourceRevision = sourceRevision 68 | self.harmonized = harmonized 69 | } 70 | 71 | init(visionPrescription: HKVisionPrescription) throws { 72 | self.uuid = visionPrescription.uuid.uuidString 73 | self.identifier = VisionPrescriptionType 74 | .visionPrescription 75 | .original? 76 | .identifier ?? "HKVisionPrescriptionTypeIdentifier" 77 | self.startTimestamp = visionPrescription.startDate.timeIntervalSince1970 78 | self.endTimestamp = visionPrescription.endDate.timeIntervalSince1970 79 | self.device = Device(device: visionPrescription.device) 80 | self.sourceRevision = SourceRevision(sourceRevision: visionPrescription.sourceRevision) 81 | self.harmonized = try visionPrescription.harmonize() 82 | } 83 | } 84 | // MARK: - Payload 85 | @available(iOS 16.0, *) 86 | extension VisionPrescription.Harmonized: Payload { 87 | public static func make(from dictionary: [String: Any]) throws -> VisionPrescription.Harmonized { 88 | guard 89 | let dateIssuedTimestamp = dictionary["dateIssuedTimestamp"] as? NSNumber, 90 | let prescriptionType = dictionary["prescriptionType"] as? [String: Any] 91 | else { 92 | throw HealthKitError.invalidValue("Invalid dictionary: \(dictionary)") 93 | } 94 | let expirationDateTimestamp = dictionary["expirationDateTimestamp"] as? NSNumber 95 | let metadata = dictionary["metadata"] as? [String: Any] 96 | return VisionPrescription.Harmonized( 97 | dateIssuedTimestamp: Double(truncating: dateIssuedTimestamp), 98 | expirationDateTimestamp: expirationDateTimestamp != nil 99 | ? Double(truncating: expirationDateTimestamp!) 100 | : nil, 101 | prescriptionType: try VisionPrescription.PrescriptionType.make(from: prescriptionType), 102 | metadata: metadata?.asMetadata 103 | ) 104 | } 105 | } 106 | // MARK: - Payload 107 | @available(iOS 16.0, *) 108 | extension VisionPrescription.PrescriptionType: Payload { 109 | public static func make(from dictionary: [String: Any]) throws -> VisionPrescription.PrescriptionType { 110 | guard 111 | let id = dictionary["id"] as? NSNumber, 112 | let detail = dictionary["detail"] as? String 113 | else { 114 | throw HealthKitError.invalidValue("Invalid dictionary: \(dictionary)") 115 | } 116 | return VisionPrescription.PrescriptionType(id: id.intValue, detail: detail) 117 | } 118 | } 119 | // MARK: - Payload 120 | @available(iOS 16.0, *) 121 | extension VisionPrescription: Payload { 122 | public static func make(from dictionary: [String: Any]) throws -> VisionPrescription { 123 | guard 124 | let identifier = dictionary["identifier"] as? String, 125 | let startTimestamp = dictionary["startTimestamp"] as? NSNumber, 126 | let endTimestamp = dictionary["endTimestamp"] as? NSNumber, 127 | let sourceRevision = dictionary["sourceRevision"] as? [String: Any], 128 | let harmonized = dictionary["harmonized"] as? [String: Any] 129 | else { 130 | throw HealthKitError.invalidValue("Invalid dictionary: \(dictionary)") 131 | } 132 | let device = dictionary["device"] as? [String: Any] 133 | return VisionPrescription( 134 | identifier: identifier, 135 | startTimestamp: Double(truncating: startTimestamp), 136 | endTimestamp: Double(truncating: endTimestamp), 137 | device: device != nil 138 | ? try Device.make(from: device!) 139 | : nil, 140 | sourceRevision: try SourceRevision.make(from: sourceRevision), 141 | harmonized: try Harmonized.make(from: harmonized) 142 | ) 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /Sources/Model/Payload/WorkoutConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WorkoutConfiguration.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 25.09.20. 6 | // 7 | 8 | import HealthKit 9 | 10 | @available(iOS 10.0, *) 11 | public struct WorkoutConfiguration: Codable { 12 | public struct Harmonized: Codable { 13 | public let value: Double 14 | public let unit: String 15 | 16 | public init( 17 | value: Double, 18 | unit: String 19 | ) { 20 | self.value = value 21 | self.unit = unit 22 | } 23 | } 24 | 25 | public let activityValue: Int 26 | public let locationValue: Int 27 | public let swimmingValue: Int 28 | public let harmonized: Harmonized 29 | 30 | public static func make( 31 | from dictionary: [String: Any] 32 | ) throws -> WorkoutConfiguration { 33 | guard 34 | let activityValue = dictionary["activityValue"] as? Int, 35 | let locationValue = dictionary["locationValue"] as? Int, 36 | let swimmingValue = dictionary["swimmingValue"] as? Int, 37 | let harmonized = dictionary["harmonized"] as? [String: Any] 38 | else { 39 | throw HealthKitError.invalidValue( 40 | "Invalid dictionary: \(dictionary)" 41 | ) 42 | } 43 | return WorkoutConfiguration( 44 | activityValue: activityValue, 45 | locationValue: locationValue, 46 | swimmingValue: swimmingValue, 47 | harmonized: try Harmonized.make(from: harmonized) 48 | ) 49 | } 50 | 51 | public init( 52 | activityValue: Int, 53 | locationValue: Int, 54 | swimmingValue: Int, 55 | harmonized: Harmonized 56 | ) { 57 | self.activityValue = activityValue 58 | self.locationValue = locationValue 59 | self.swimmingValue = swimmingValue 60 | self.harmonized = harmonized 61 | } 62 | 63 | init(workoutConfiguration: HKWorkoutConfiguration) throws { 64 | self.activityValue = Int(workoutConfiguration.activityType.rawValue) 65 | self.locationValue = workoutConfiguration.locationType.rawValue 66 | self.swimmingValue = workoutConfiguration.swimmingLocationType.rawValue 67 | self.harmonized = try workoutConfiguration.harmonize() 68 | } 69 | } 70 | // MARK: - Original 71 | @available(iOS 10.0, *) 72 | extension WorkoutConfiguration: Original { 73 | func asOriginal() throws -> HKWorkoutConfiguration { 74 | let configuration = HKWorkoutConfiguration() 75 | if let activityType = HKWorkoutActivityType(rawValue: UInt(activityValue)) { 76 | configuration.activityType = activityType 77 | } 78 | if let locationType = HKWorkoutSessionLocationType(rawValue: locationValue) { 79 | configuration.locationType = locationType 80 | } 81 | if let swimmingLocationType = HKWorkoutSwimmingLocationType(rawValue: swimmingValue) { 82 | configuration.swimmingLocationType = swimmingLocationType 83 | } 84 | configuration.lapLength = HKQuantity( 85 | unit: HKUnit.init(from: harmonized.unit), 86 | doubleValue: harmonized.value 87 | ) 88 | return HKWorkoutConfiguration() 89 | } 90 | } 91 | // MARK: - Payload 92 | @available(iOS 10.0, *) 93 | extension WorkoutConfiguration.Harmonized: Payload { 94 | public static func make( 95 | from dictionary: [String: Any] 96 | ) throws -> WorkoutConfiguration.Harmonized { 97 | guard 98 | let value = dictionary["value"] as? NSNumber, 99 | let unit = dictionary["unit"] as? String 100 | else { 101 | throw HealthKitError.invalidValue("Invalid dictionary: \(dictionary)") 102 | } 103 | return WorkoutConfiguration.Harmonized( 104 | value: Double(truncating: value), 105 | unit: unit 106 | ) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /Sources/Model/Payload/WorkoutEvent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WorkoutEvent.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 25.09.20. 6 | // 7 | 8 | import HealthKit 9 | 10 | public struct WorkoutEvent: Sample { 11 | public struct Harmonized: Codable { 12 | public let value: Int 13 | public let description: String 14 | public let metadata: Metadata? 15 | 16 | public init(value: Int, description: String, metadata: Metadata?) { 17 | self.value = value 18 | self.description = description 19 | self.metadata = metadata 20 | } 21 | 22 | public func copyWith( 23 | value: Int? = nil, 24 | description: String? = nil, 25 | metadata: Metadata? = nil 26 | ) -> Harmonized { 27 | return Harmonized( 28 | value: value ?? self.value, 29 | description: description ?? self.description, 30 | metadata: metadata ?? self.metadata 31 | ) 32 | } 33 | } 34 | 35 | public let startTimestamp: Double 36 | public let endTimestamp: Double 37 | public let duration: Double 38 | public let harmonized: Harmonized 39 | 40 | @available(iOS 11.0, *) 41 | init(workoutEvent: HKWorkoutEvent) throws { 42 | self.startTimestamp = workoutEvent 43 | .dateInterval 44 | .start 45 | .timeIntervalSince1970 46 | self.endTimestamp = workoutEvent 47 | .dateInterval 48 | .end 49 | .timeIntervalSince1970 50 | self.duration = workoutEvent.dateInterval.duration 51 | self.harmonized = try workoutEvent.harmonize() 52 | } 53 | 54 | public init( 55 | startTimestamp: Double, 56 | endTimestamp: Double, 57 | duration: Double, 58 | harmonized: Harmonized 59 | ) { 60 | self.startTimestamp = startTimestamp 61 | self.endTimestamp = endTimestamp 62 | self.duration = duration 63 | self.harmonized = harmonized 64 | } 65 | 66 | public func copyWith( 67 | startTimestamp: Double? = nil, 68 | endTimestamp: Double? = nil, 69 | duration: Double? = nil, 70 | harmonized: Harmonized? = nil 71 | ) -> WorkoutEvent { 72 | return WorkoutEvent( 73 | startTimestamp: startTimestamp ?? self.startTimestamp, 74 | endTimestamp: endTimestamp ?? self.endTimestamp, 75 | duration: duration ?? self.duration, 76 | harmonized: harmonized ?? self.harmonized 77 | ) 78 | } 79 | } 80 | // MARK: - Original 81 | extension WorkoutEvent: Original { 82 | func asOriginal() throws -> HKWorkoutEvent { 83 | guard #available(iOS 11.0, *) else { 84 | throw HealthKitError.notAvailable( 85 | "HKWorkoutEvent DateInterval is not available for the current iOS" 86 | ) 87 | } 88 | guard let type = HKWorkoutEventType(rawValue: harmonized.value) else { 89 | throw HealthKitError.invalidType( 90 | "WorkoutEvent type: \(harmonized.value) could not be formatted" 91 | ) 92 | } 93 | return HKWorkoutEvent( 94 | type: type, 95 | dateInterval: DateInterval( 96 | start: startTimestamp.asDate, 97 | end: endTimestamp.asDate 98 | ), 99 | metadata: harmonized.metadata?.original 100 | ) 101 | } 102 | } 103 | // MARK: - Payload 104 | extension WorkoutEvent.Harmonized: Payload { 105 | public static func make( 106 | from dictionary: [String: Any] 107 | ) throws -> WorkoutEvent.Harmonized { 108 | guard 109 | let value = dictionary["value"] as? Int, 110 | let description = dictionary["description"] as? String 111 | else { 112 | throw HealthKitError.invalidValue("Invalid dictionary: \(dictionary)") 113 | } 114 | let metadata = dictionary["metadata"] as? [String: Any] 115 | return WorkoutEvent.Harmonized( 116 | value: value, 117 | description: description, 118 | metadata: metadata?.asMetadata 119 | ) 120 | } 121 | } 122 | // MARK: - Payload 123 | extension WorkoutEvent: Payload { 124 | public static func make( 125 | from dictionary: [String: Any] 126 | ) throws -> WorkoutEvent { 127 | guard 128 | let startTimestamp = dictionary["startTimestamp"] as? NSNumber, 129 | let endTimestamp = dictionary["endTimestamp"] as? NSNumber, 130 | let duration = dictionary["duration"] as? NSNumber, 131 | let harmonized = dictionary["harmonized"] as? [String: Any] 132 | else { 133 | throw HealthKitError.invalidValue("Invalid dictionary: \(dictionary)") 134 | } 135 | return WorkoutEvent( 136 | startTimestamp: Double(truncating: startTimestamp), 137 | endTimestamp: Double(truncating: endTimestamp), 138 | duration: Double(truncating: duration), 139 | harmonized: try WorkoutEvent.Harmonized.make(from: harmonized) 140 | ) 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /Sources/Model/PreferredUnit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PreferredUnit.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 16.11.20. 6 | // 7 | 8 | import HealthKit 9 | 10 | public struct PreferredUnit: Codable { 11 | public let identifier: String 12 | public let unit: String 13 | 14 | init(type: HKQuantityType, unit: HKUnit) { 15 | self.identifier = type.identifier 16 | self.unit = unit.unitString 17 | } 18 | 19 | public init(identifier: String, unit: String) { 20 | self.identifier = identifier 21 | self.unit = unit 22 | } 23 | } 24 | // MARK: - Payload 25 | public extension PreferredUnit { 26 | static func collect( 27 | from dictionary: [HKQuantityType : HKUnit] 28 | ) -> [PreferredUnit] { 29 | var preferredUnits: [PreferredUnit] = [] 30 | for (key, value) in dictionary { 31 | let preferredUnit = PreferredUnit( 32 | type: key, 33 | unit: value 34 | ) 35 | preferredUnits.append(preferredUnit) 36 | } 37 | return preferredUnits 38 | } 39 | static func collect( 40 | from dictionary: [QuantityType: String] 41 | ) -> [PreferredUnit] { 42 | var preferredUnits: [PreferredUnit] = [] 43 | for (key, value) in dictionary { 44 | if let identifier = key.identifier { 45 | let preferredUnit = PreferredUnit( 46 | identifier: identifier, 47 | unit: value 48 | ) 49 | preferredUnits.append(preferredUnit) 50 | } 51 | } 52 | return preferredUnits 53 | } 54 | } 55 | // MARK: - Payload 56 | extension PreferredUnit: Payload { 57 | public static func make(from dictionary: [String: Any]) throws -> PreferredUnit { 58 | guard 59 | let identifier = dictionary["identifier"] as? String, 60 | let unit = dictionary["unit"] as? String 61 | else { 62 | throw HealthKitError.invalidValue("Invalid dictionary: \(dictionary)") 63 | } 64 | return PreferredUnit(identifier: identifier, unit: unit) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Sources/Model/Type/ActivitySummaryType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ActivitySummaryType.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 05.10.20. 6 | // 7 | 8 | import HealthKit 9 | 10 | /** 11 | All HealthKit activity summary types 12 | */ 13 | public enum ActivitySummaryType: Int, CaseIterable, ObjectType { 14 | case activitySummaryType 15 | 16 | public var original: HKObjectType? { 17 | switch self { 18 | case .activitySummaryType: 19 | if #available(iOS 9.3, *) { 20 | return HKObjectType.activitySummaryType() 21 | } else { 22 | return nil 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/Model/Type/CharacteristicType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CharacteristicType.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 05.10.20. 6 | // 7 | 8 | import HealthKit 9 | 10 | /** 11 | All HealthKit characteristic types 12 | */ 13 | public enum CharacteristicType: Int, CaseIterable, ObjectType { 14 | case fitzpatrickSkinType 15 | case dateOfBirth 16 | case bloodType 17 | case biologicalSex 18 | case wheelchairUse 19 | case activityMoveMode 20 | 21 | public var original: HKObjectType? { 22 | switch self { 23 | case .fitzpatrickSkinType: 24 | return HKObjectType.characteristicType(forIdentifier: .fitzpatrickSkinType) 25 | case .dateOfBirth: 26 | return HKObjectType.characteristicType(forIdentifier: .dateOfBirth) 27 | case .bloodType: 28 | return HKObjectType.characteristicType(forIdentifier: .bloodType) 29 | case .biologicalSex: 30 | return HKObjectType.characteristicType(forIdentifier: .biologicalSex) 31 | case .wheelchairUse: 32 | if #available(iOS 10.0, *) { 33 | return HKObjectType.characteristicType(forIdentifier: .wheelchairUse) 34 | } else { 35 | return nil 36 | } 37 | case .activityMoveMode: 38 | if #available(iOS 14.0, *) { 39 | return HKObjectType.characteristicType(forIdentifier: .activityMoveMode) 40 | } else { 41 | return nil 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Sources/Model/Type/ObjectType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ObjectType.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 05.10.20. 6 | // 7 | 8 | import HealthKit 9 | 10 | public protocol ObjectType { 11 | /** 12 | Represents type as an original **HKObjectType** 13 | */ 14 | var original: HKObjectType? { get } 15 | } 16 | 17 | public extension ObjectType { 18 | /** 19 | Makes an **ObjectType** based on it's identifier. 20 | - Parameter identifier: **String** identifier of the **ObjectType** 21 | */ 22 | static func make( 23 | from identifier: String 24 | ) throws -> Self where Self: CaseIterable { 25 | let first = Self.allCases.first { identifier == $0.original?.identifier } 26 | guard let result = first else { 27 | throw HealthKitError.invalidIdentifier("Invalid identifier: \(identifier)") 28 | } 29 | return result 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/Model/Type/Sample/ClinicalType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SampleType.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Quentin on 01.08.24. 6 | // 7 | 8 | import HealthKit 9 | 10 | public enum ClinicalType: Int, CaseIterable, SampleType { 11 | case allergyRecord 12 | case conditionRecord 13 | case immunizationRecord 14 | case labResultRecord 15 | case medicationRecord 16 | case procedureRecord 17 | case vitalSignRecord 18 | 19 | public var identifier: String? { 20 | return original?.identifier 21 | } 22 | 23 | public var original: HKObjectType? { 24 | if #available(iOS 12.0, *) { 25 | switch self { 26 | case .allergyRecord: 27 | return HKObjectType.clinicalType(forIdentifier: .allergyRecord) 28 | case .conditionRecord: 29 | return HKObjectType.clinicalType(forIdentifier: .conditionRecord) 30 | case .immunizationRecord: 31 | return HKObjectType.clinicalType(forIdentifier: .immunizationRecord) 32 | case .labResultRecord: 33 | return HKObjectType.clinicalType(forIdentifier: .labResultRecord) 34 | case .medicationRecord: 35 | return HKObjectType.clinicalType(forIdentifier: .medicationRecord) 36 | case .procedureRecord: 37 | return HKObjectType.clinicalType(forIdentifier: .procedureRecord) 38 | case .vitalSignRecord: 39 | return HKObjectType.clinicalType(forIdentifier: .vitalSignRecord) 40 | } 41 | } 42 | return nil 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/Model/Type/Sample/CorrelationType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CorrelationType.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 05.10.20. 6 | // 7 | 8 | import HealthKit 9 | 10 | /** 11 | All HealthKit correlation types 12 | */ 13 | public enum CorrelationType: Int, CaseIterable, SampleType { 14 | case bloodPressure 15 | case food 16 | 17 | public var identifier: String? { 18 | return original?.identifier 19 | } 20 | 21 | public var original: HKObjectType? { 22 | switch self { 23 | case .food: 24 | return HKObjectType.correlationType(forIdentifier: .food) 25 | case .bloodPressure: 26 | return HKObjectType.correlationType(forIdentifier: .bloodPressure) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/Model/Type/Sample/DocumentType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DocumentType.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 05.10.20. 6 | // 7 | 8 | import HealthKit 9 | 10 | /** 11 | All HealthKit document types 12 | */ 13 | public enum DocumentType: Int, CaseIterable, SampleType { 14 | case cda 15 | 16 | public var identifier: String? { 17 | return original?.identifier 18 | } 19 | 20 | public var original: HKObjectType? { 21 | switch self { 22 | case .cda: 23 | if #available(iOS 10.0, *) { 24 | return HKObjectType.documentType(forIdentifier: .CDA) 25 | } else { 26 | return nil 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/Model/Type/Sample/ElectrocardiogramType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ElectrocardiogramType.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 05.10.20. 6 | // 7 | 8 | import HealthKit 9 | 10 | /** 11 | All HealthKit electrocardiogram types 12 | */ 13 | public enum ElectrocardiogramType: Int, CaseIterable, SampleType { 14 | case electrocardiogramType 15 | 16 | public var identifier: String? { 17 | return original?.identifier 18 | } 19 | 20 | public var original: HKObjectType? { 21 | switch self { 22 | case .electrocardiogramType: 23 | if #available(iOS 14.0, *) { 24 | return HKObjectType.electrocardiogramType() 25 | } 26 | } 27 | return nil 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/Model/Type/Sample/SampleType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SampleType.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 18.11.20. 6 | // 7 | 8 | import HealthKit 9 | 10 | public protocol SampleType: ObjectType { 11 | /** 12 | Extracts an original identifier 13 | */ 14 | var identifier: String? { get } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/Model/Type/Sample/SeriesType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SeriesType.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 05.10.20. 6 | // 7 | 8 | import HealthKit 9 | 10 | /** 11 | All HealthKit series types 12 | */ 13 | public enum SeriesType: Int, CaseIterable, SampleType { 14 | case heartbeatSeries 15 | case workoutRoute 16 | 17 | public var identifier: String? { 18 | return original?.identifier 19 | } 20 | 21 | public var original: HKObjectType? { 22 | switch self { 23 | case .heartbeatSeries: 24 | if #available(iOS 13.0, *) { 25 | let heartbeatSeries = HKObjectType.seriesType( 26 | forIdentifier: HKDataTypeIdentifierHeartbeatSeries 27 | ) 28 | return heartbeatSeries ?? HKSeriesType.heartbeat() 29 | } else { 30 | return nil 31 | } 32 | case .workoutRoute: 33 | if #available(iOS 11.0, *) { 34 | let workoutRoute = HKObjectType.seriesType( 35 | forIdentifier: HKWorkoutRouteTypeIdentifier 36 | ) 37 | return workoutRoute ?? HKSeriesType.workoutRoute() 38 | } else { 39 | return nil 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/Model/Type/Sample/VisionPrescriptionType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VisionPrescriptionType.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor Kachalov on 04.10.22. 6 | // 7 | 8 | import HealthKit 9 | 10 | /** 11 | All HealthKit vision prescription types 12 | */ 13 | public enum VisionPrescriptionType: Int, CaseIterable, SampleType { 14 | case visionPrescription 15 | 16 | public var identifier: String? { 17 | return original?.identifier 18 | } 19 | 20 | public var original: HKObjectType? { 21 | switch self { 22 | case .visionPrescription: 23 | if #available(iOS 16.0, *) { 24 | return HKObjectType.visionPrescriptionType() 25 | } 26 | } 27 | return nil 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/Model/Type/Sample/WorkoutType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WorkoutType.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 05.10.20. 6 | // 7 | 8 | import HealthKit 9 | 10 | /** 11 | All HealthKit workout types 12 | */ 13 | public enum WorkoutType: Int, CaseIterable, SampleType { 14 | case workoutType 15 | 16 | public var identifier: String? { 17 | return original?.identifier 18 | } 19 | 20 | public var original: HKObjectType? { 21 | switch self { 22 | case .workoutType: 23 | return HKObjectType.workoutType() 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/Model/UnitConvertable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UnitConvertable.swift 3 | // 4 | // 5 | // Created by Vignesh J on 16/02/21. 6 | // 7 | 8 | import Foundation 9 | 10 | public protocol UnitConvertable { 11 | func converted(to unit: String) throws -> Self 12 | } 13 | -------------------------------------------------------------------------------- /Sources/Model/UpdateFrequency.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UpdateFrequency.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 13.11.20. 6 | // 7 | 8 | import HealthKit 9 | 10 | public enum UpdateFrequency: Int { 11 | case immediate = 1 12 | case hourly = 2 13 | case daily = 3 14 | case weekly = 4 15 | 16 | var original: HKUpdateFrequency { 17 | switch self { 18 | case .immediate: 19 | return .immediate 20 | case .hourly: 21 | return .hourly 22 | case .daily: 23 | return .daily 24 | case .weekly: 25 | return .weekly 26 | } 27 | } 28 | 29 | public static func make(from integer: Int) throws -> Self { 30 | switch integer { 31 | case 1: 32 | return .immediate 33 | case 2: 34 | return .hourly 35 | case 3: 36 | return .daily 37 | case 4: 38 | return .weekly 39 | default: 40 | throw HealthKitError.invalidValue( 41 | "Integer: \(integer) can not be represented as UpdateFrequency" 42 | ) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Sources/Service/HealthKitManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HealthKitManager.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 25.09.20. 6 | // 7 | 8 | import HealthKit 9 | 10 | /// **HealthKitManager** class for HK managing operations 11 | public class HealthKitManager { 12 | private let healthStore: HKHealthStore 13 | 14 | init(healthStore: HKHealthStore) { 15 | self.healthStore = healthStore 16 | } 17 | /** 18 | Requests authorization for reading/writing Objects in HK. 19 | - Parameter toRead: an array of **ObjectType** types to read 20 | - Parameter toWrite: an array of **ObjectType** types to write 21 | - Parameter completion: returns a block with information about authorization window being displayed 22 | */ 23 | public func requestAuthorization( 24 | toRead: [ObjectType], 25 | toWrite: [SampleType], 26 | completion: @escaping StatusCompletionBlock 27 | ) { 28 | var setOfReadTypes = Set() 29 | for type in toRead { 30 | guard let objectType = type.original else { 31 | completion( 32 | false, 33 | HealthKitError.invalidType( 34 | "Type \(type) has not HKObjectType representation" 35 | ) 36 | ) 37 | return 38 | } 39 | setOfReadTypes.insert(objectType) 40 | } 41 | var setOfWriteTypes = Set() 42 | for type in toWrite { 43 | guard let objectType = type.original as? HKSampleType else { 44 | completion( 45 | false, HealthKitError.invalidType( 46 | "Type \(type) has not HKObjectType representation" 47 | ) 48 | ) 49 | return 50 | } 51 | setOfWriteTypes.insert(objectType) 52 | } 53 | healthStore.requestAuthorization( 54 | toShare: setOfWriteTypes, 55 | read: setOfReadTypes, 56 | completion: completion 57 | ) 58 | } 59 | /** 60 | Queries preferred units. 61 | - Parameter quantityTypes: an array of **QuantityType** types 62 | - Parameter completion: returns a block with information preferred units 63 | */ 64 | public func preferredUnits( 65 | for quantityTypes: [QuantityType], 66 | completion: @escaping PreferredUnitsCompeltion 67 | ) { 68 | var setOfTypes = Set() 69 | for type in quantityTypes { 70 | guard let objectType = type.original as? HKQuantityType else { 71 | completion( 72 | [], 73 | HealthKitError.invalidType( 74 | "Type \(type) has not HKQuantityType representation" 75 | ) 76 | ) 77 | return 78 | } 79 | setOfTypes.insert(objectType) 80 | } 81 | healthStore.preferredUnits(for: setOfTypes) { (result, error) in 82 | guard error == nil else { 83 | completion([], error) 84 | return 85 | } 86 | let preferredUnits = PreferredUnit.collect(from: result) 87 | completion(preferredUnits, nil) 88 | } 89 | } 90 | /** 91 | Stops executing the query. 92 | - Parameter query: **Query** 93 | */ 94 | public func stopQuery(_ query: Query) { 95 | healthStore.stop(query) 96 | } 97 | /** 98 | Executs query 99 | - Parameter query: **Query** 100 | */ 101 | public func executeQuery(_ query: Query) { 102 | healthStore.execute(query) 103 | } 104 | /** 105 | Starts Watch App. 106 | - Parameter workoutConfiguration: **WorkoutConfiguration** workout configuration 107 | - Parameter completion: returns a block with samples 108 | */ 109 | @available(iOS 10.0, *) 110 | public func startWatchApp( 111 | with workoutConfiguration: WorkoutConfiguration, 112 | completion: @escaping StatusCompletionBlock 113 | ) { 114 | do { 115 | healthStore.startWatchApp( 116 | with: try workoutConfiguration.asOriginal(), 117 | completion: completion 118 | ) 119 | } catch { 120 | completion(false, error) 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /Sources/Service/HealthKitObserver.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HealthKitObserver.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 23.09.20. 6 | // 7 | 8 | import HealthKit 9 | 10 | /// **HealthKitObserver** class for HK observing operations 11 | public class HealthKitObserver { 12 | private let healthStore: HKHealthStore 13 | 14 | init(healthStore: HKHealthStore) { 15 | self.healthStore = healthStore 16 | } 17 | /** 18 | Sets observer query for type 19 | - Parameter type: **SampleType** type 20 | - Parameter predicate: **NSPredicate** predicate (optional). Nil by default 21 | - Parameter updateHandler: is called as soon any change happened in AppleHealth App 22 | */ 23 | public func observerQuery( 24 | type: SampleType, 25 | predicate: NSPredicate? = nil, 26 | updateHandler: @escaping ObserverUpdateHandler 27 | ) throws -> ObserverQuery { 28 | guard let sampleType = type.original as? HKSampleType else { 29 | throw HealthKitError.invalidType("Invalid HKQuantityType: \(type)") 30 | } 31 | let query = HKObserverQuery( 32 | sampleType: sampleType, 33 | predicate: predicate 34 | ) { (query, completion, error) in 35 | guard error == nil else { 36 | updateHandler(query, nil, error) 37 | return 38 | } 39 | guard #available(iOS 9.3, *) else { 40 | updateHandler(query, nil, HealthKitError.notAvailable( 41 | "Query objectType is not available for the current iOS" 42 | )) 43 | return 44 | } 45 | guard let id = query.objectType?.identifier else { 46 | updateHandler( 47 | query, 48 | nil, 49 | HealthKitError.unknown("Unknown object type for query: \(query)") 50 | ) 51 | return 52 | } 53 | updateHandler(query, id, nil) 54 | completion() 55 | } 56 | return query 57 | } 58 | /** 59 | Enables background notifications about changes in AppleHealth 60 | - Parameter type: **ObjectType** type 61 | - Parameter frequency: **HKUpdateFrequency** frequency. Hourly by default 62 | - Parameter completionHandler: is called as soon any change happened in AppleHealth App 63 | */ 64 | public func enableBackgroundDelivery( 65 | type: ObjectType, 66 | frequency: UpdateFrequency = .hourly, 67 | completionHandler: @escaping StatusCompletionBlock 68 | ) { 69 | guard let objectType = type.original else { 70 | completionHandler( 71 | false, 72 | HealthKitError.invalidType("Unknown type: \(type)") 73 | ) 74 | return 75 | } 76 | healthStore.enableBackgroundDelivery( 77 | for: objectType, 78 | frequency: frequency.original, 79 | withCompletion: completionHandler 80 | ) 81 | } 82 | /** 83 | Disables All background notifications about changes in AppleHealth 84 | - Parameter completionHandler: is called as soon any change happened in AppleHealth App 85 | */ 86 | public func disableAllBackgroundDelivery( 87 | completionHandler: @escaping StatusCompletionBlock 88 | ) { 89 | healthStore.disableAllBackgroundDelivery(completion: completionHandler) 90 | } 91 | /** 92 | Disables All background notifications about changes in AppleHealth 93 | - Parameter type: **ObjectType** type 94 | - Parameter completionHandler: is called as soon any change happened in AppleHealth App 95 | */ 96 | public func disableBackgroundDelivery( 97 | type: ObjectType, 98 | completionHandler: @escaping StatusCompletionBlock 99 | ) { 100 | guard let objectType = type.original else { 101 | completionHandler( 102 | false, 103 | HealthKitError.invalidType("Unknown type: \(type)") 104 | ) 105 | return 106 | } 107 | healthStore.disableBackgroundDelivery( 108 | for: objectType, 109 | withCompletion: completionHandler 110 | ) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Sources/Service/HealthKitWriter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HealthKitWriter.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 24.09.20. 6 | // 7 | 8 | import HealthKit 9 | 10 | /// **HealthKitWriter** class for HK writing operations 11 | public class HealthKitWriter { 12 | private let healthStore: HKHealthStore 13 | 14 | init(healthStore: HKHealthStore) { 15 | self.healthStore = healthStore 16 | } 17 | /** 18 | Checks authorization for writing Objects in HK. 19 | - Parameter type: **ObjectType** type to check 20 | - Throws: `HealthKitError.notAvailable` `HealthKitError.invalidType` 21 | - Returns: true if allowed to write and false if not 22 | */ 23 | public func isAuthorizedToWrite(type: ObjectType) throws -> Bool { 24 | guard let objectType = type.original else { 25 | throw HealthKitError.invalidType("Invalid type: \(type)") 26 | } 27 | let status = healthStore.authorizationStatus(for: objectType) 28 | switch status { 29 | case .notDetermined, .sharingDenied: 30 | return false 31 | case .sharingAuthorized: 32 | return true 33 | @unknown default: 34 | throw HealthKitError.notAvailable("Invalid status") 35 | } 36 | } 37 | /** 38 | Adds category samples from device to a workout 39 | - Parameter samples: **Category** samples 40 | - Parameter from: **Device** device (optional) 41 | - Parameter workout: **Workout** workout 42 | - Parameter completion: block notifies about operation status 43 | */ 44 | public func addCategory( 45 | _ samples: [Category], 46 | from: Device?, 47 | to workout: Workout, 48 | completion: @escaping StatusCompletionBlock 49 | ) { 50 | do { 51 | let categorySamples = try samples.map { try $0.asOriginal() } 52 | healthStore.add( 53 | categorySamples, 54 | to: try workout.asOriginal(), 55 | completion: completion 56 | ) 57 | } catch { 58 | completion(false, error) 59 | } 60 | } 61 | /** 62 | Adds quantitiy samples from device to a workout 63 | - Parameter samples: **Quantitiy** samples 64 | - Parameter from: **Device** device (optional) 65 | - Parameter workout: **Workout** workout 66 | - Parameter completion: block notifies about operation status 67 | */ 68 | public func addQuantitiy( 69 | _ samples: [Quantity], 70 | from: Device?, 71 | to workout: Workout, 72 | completion: @escaping StatusCompletionBlock 73 | ) { 74 | do { 75 | let quantitySamples = try samples.map { try $0.asOriginal() } 76 | healthStore.add( 77 | quantitySamples, 78 | to: try workout.asOriginal(), 79 | completion: completion 80 | ) 81 | } catch { 82 | completion(false, error) 83 | } 84 | } 85 | /** 86 | Deletes the previosly created sample 87 | - Parameter sample: **Sample** sample 88 | - Parameter completion: block notifies about operation status 89 | */ 90 | public func delete( 91 | sample: Sample, 92 | completion: @escaping StatusCompletionBlock 93 | ) { 94 | do { 95 | if let quantity = sample as? Quantity { 96 | healthStore.delete(try quantity.asOriginal(), withCompletion: completion) 97 | } 98 | if let category = sample as? Category { 99 | healthStore.delete(try category.asOriginal(), withCompletion: completion) 100 | } 101 | if let workout = sample as? Workout { 102 | healthStore.delete(try workout.asOriginal(), withCompletion: completion) 103 | } 104 | } catch { 105 | completion(false, error) 106 | } 107 | } 108 | /** 109 | Deletes objects of type with predicate 110 | - Parameter objectType: **ObjectType** type 111 | - Parameter predicate: **NSPredicate** predicate for deletion 112 | - Parameter completion: block notifies about deletion operation status 113 | */ 114 | public func deleteObjects( 115 | of objectType: ObjectType, 116 | predicate: NSPredicate, 117 | completion: @escaping DeletionCompletionBlock 118 | ) { 119 | guard let type = objectType.original else { 120 | completion( 121 | false, 122 | -1, 123 | HealthKitError.invalidType("Object type was invalid: \(objectType)") 124 | ) 125 | return 126 | } 127 | healthStore.deleteObjects(of: type, predicate: predicate, withCompletion: completion) 128 | } 129 | /** 130 | Saves the created sample 131 | - Parameter sample: **Sample** sample 132 | - Parameter completion: block notifies about operation status 133 | */ 134 | public func save( 135 | sample: Sample, 136 | completion: @escaping StatusCompletionBlock 137 | ) { 138 | do { 139 | if let quantity = sample as? Quantity { 140 | healthStore.save(try quantity.asOriginal(), withCompletion: completion) 141 | } 142 | if let category = sample as? Category { 143 | healthStore.save(try category.asOriginal(), withCompletion: completion) 144 | } 145 | if let workout = sample as? Workout { 146 | healthStore.save(try workout.asOriginal(), withCompletion: completion) 147 | } 148 | if let correlation = sample as? Correlation { 149 | healthStore.save(try correlation.asOriginal(), withCompletion: completion) 150 | } 151 | } catch { 152 | completion(false, error) 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /Sources/Service/Retriever/ElectrocardiogramRetriever.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ElectrocardiogramRetriever.swift 3 | // HealthKitReporter 4 | // 5 | // Created by Victor on 21.10.20. 6 | // 7 | 8 | import HealthKit 9 | 10 | @available(iOS 14.0, *) 11 | class ElectrocardiogramRetriever { 12 | func makeElectrocardiogramQuery( 13 | healthStore: HKHealthStore, 14 | predicate: NSPredicate?, 15 | sortDescriptors: [NSSortDescriptor], 16 | limit: Int, 17 | withVoltageMeasurements: Bool, 18 | resultsHandler: @escaping ElectrocardiogramResultsHandler 19 | ) throws -> HKSampleQuery { 20 | let electrocardiogramType = ElectrocardiogramType.electrocardiogramType 21 | guard 22 | let type = electrocardiogramType.original as? HKElectrocardiogramType 23 | else { 24 | throw HealthKitError.invalidType( 25 | "\(electrocardiogramType) can not be represented as HKElectrocardiogramType" 26 | ) 27 | } 28 | let query = HKSampleQuery( 29 | sampleType: type, 30 | predicate: predicate, 31 | limit: limit, 32 | sortDescriptors: sortDescriptors 33 | ) { (query, data, error) in 34 | guard 35 | error == nil, 36 | let results = data as? [HKElectrocardiogram] 37 | else { 38 | resultsHandler([], error) 39 | return 40 | } 41 | var ecgs: [Electrocardiogram] 42 | switch withVoltageMeasurements { 43 | case true: 44 | ecgs = [] 45 | var ecgsError: Error? 46 | let group = DispatchGroup() 47 | for ecgSample in results { 48 | var measurments = [Electrocardiogram.VoltageMeasurement]() 49 | group.enter() 50 | let voltageQuery = HKElectrocardiogramQuery(ecgSample) { (query, result) in 51 | switch(result) { 52 | case .measurement(let voltageMeasurement): 53 | if let measurment = try? Electrocardiogram.VoltageMeasurement(voltageMeasurement: voltageMeasurement) { 54 | measurments.append(measurment) 55 | } 56 | case .done: 57 | if let ecg = try? Electrocardiogram(electrocardiogram: ecgSample, voltageMeasurements: measurments) { 58 | ecgs.append(ecg) 59 | } 60 | group.leave() 61 | case .error(let error): 62 | ecgsError = error 63 | group.leave() 64 | @unknown default: 65 | ecgsError = HealthKitError.notAvailable("Unknown case of Electrocardiogram.VoltageMeasurement result") 66 | group.leave() 67 | } 68 | } 69 | healthStore.execute(voltageQuery) 70 | } 71 | group.notify(queue: .global()) { 72 | resultsHandler(ecgs, ecgsError) 73 | } 74 | case false: 75 | ecgs = Electrocardiogram.collect(results: results) 76 | resultsHandler(ecgs, nil) 77 | } 78 | } 79 | return query 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Tests/ActivitySummaryTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ActivitySummaryTests.swift 3 | // 4 | // 5 | // Created by Kachalov, Victor on 03.09.21. 6 | // 7 | 8 | import XCTest 9 | import HealthKitReporter 10 | 11 | class ActivitySummaryTests: XCTestCase { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /Tests/CharacteristicTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CharacteristicTests.swift 3 | // 4 | // 5 | // Created by Kachalov, Victor on 03.09.21. 6 | // 7 | 8 | import XCTest 9 | import HealthKitReporter 10 | 11 | class CharacteristicTests: XCTestCase { 12 | 13 | } 14 | 15 | -------------------------------------------------------------------------------- /Tests/DeletedObjectTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeletedObjectTests.swift 3 | // 4 | // 5 | // Created by Kachalov, Victor on 03.09.21. 6 | // 7 | 8 | import XCTest 9 | import HealthKitReporter 10 | 11 | class DeletedObjectTests: XCTestCase { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /Tests/DeviceTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeviceTests.swift 3 | // 4 | // 5 | // Created by Kachalov, Victor on 03.09.21. 6 | // 7 | 8 | import XCTest 9 | import HealthKitReporter 10 | 11 | class DeviceTests: XCTestCase { 12 | func testCreateThenEncodeThenDecode() throws { 13 | let sut = Device( 14 | name: "Guy's iPhone", 15 | manufacturer: "Guy", 16 | model: "6.1.1", 17 | hardwareVersion: "some_0", 18 | firmwareVersion: "some_1", 19 | softwareVersion: "some_2", 20 | localIdentifier: "some_3", 21 | udiDeviceIdentifier: "some_4" 22 | ) 23 | let encoded = try sut.encoded() 24 | let decoded = try JSONDecoder().decode( 25 | Device.self, 26 | from: encoded.data(using: .utf8)! 27 | ) 28 | XCTAssertEqual(decoded.name, "Guy's iPhone") 29 | XCTAssertEqual(decoded.manufacturer, "Guy") 30 | XCTAssertEqual(decoded.model, "6.1.1") 31 | XCTAssertEqual(decoded.hardwareVersion, "some_0") 32 | XCTAssertEqual(decoded.firmwareVersion, "some_1") 33 | XCTAssertEqual(decoded.softwareVersion, "some_2") 34 | XCTAssertEqual(decoded.localIdentifier, "some_3") 35 | XCTAssertEqual(decoded.udiDeviceIdentifier, "some_4") 36 | } 37 | func testCreateFromDictionary() throws { 38 | let dictionary: [String: Any] = [ 39 | "name": "FlutterTracker", 40 | "manufacturer": "kvs", 41 | "model": "T-800", 42 | "hardwareVersion": "3", 43 | "firmwareVersion": "3.0", 44 | "softwareVersion": "1.1.1", 45 | "localIdentifier": "kvs.sample.app", 46 | "udiDeviceIdentifier": "444-888-555" 47 | ] 48 | let sut = try Device.make(from: dictionary) 49 | XCTAssertEqual(sut.name, "FlutterTracker") 50 | XCTAssertEqual(sut.manufacturer, "kvs") 51 | XCTAssertEqual(sut.model, "T-800") 52 | XCTAssertEqual(sut.hardwareVersion, "3") 53 | XCTAssertEqual(sut.firmwareVersion, "3.0") 54 | XCTAssertEqual(sut.softwareVersion, "1.1.1") 55 | XCTAssertEqual(sut.localIdentifier, "kvs.sample.app") 56 | XCTAssertEqual(sut.udiDeviceIdentifier, "444-888-555") 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Tests/HealthKitReporterTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HealthKitReporterTests.swift 3 | // 4 | // 5 | // Created by Kachalov, Victor on 21.07.21. 6 | // 7 | 8 | import XCTest 9 | import HealthKitReporter 10 | 11 | class HealthKitReporterTests: XCTestCase { 12 | var healthKitReporter: HealthKitReporter! 13 | 14 | override func setUp() { 15 | healthKitReporter = HealthKitReporter() 16 | } 17 | override func tearDown() { 18 | healthKitReporter = nil 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Tests/MetadataTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MetadataTests.swift 3 | // 4 | // 5 | // Created by Victor Kachalov on 29.10.22. 6 | // 7 | 8 | import XCTest 9 | import HealthKitReporter 10 | 11 | class MetadataTests: XCTestCase { 12 | func testMetadataString() { 13 | let metadataExpressible: Metadata = ["HKWasUserEntered": "1"] 14 | XCTAssertEqual(metadataExpressible, ["HKWasUserEntered": "1"]) 15 | let metadataStringDictionary = Metadata.string(dictionary: ["HKWasUserEntered": "1"]) 16 | XCTAssertEqual(metadataStringDictionary, ["HKWasUserEntered": "1"]) 17 | } 18 | func testMetadataDate() { 19 | let date = Date() 20 | let metadataExpressible: Metadata = ["HKWasUserEnteredOn": date] 21 | XCTAssertEqual(metadataExpressible, ["HKWasUserEnteredOn": date]) 22 | let metadataStringDictionary = Metadata.date(dictionary: ["HKWasUserEnteredOn": date]) 23 | XCTAssertEqual(metadataStringDictionary, ["HKWasUserEnteredOn": date]) 24 | } 25 | func testMetadataDouble() { 26 | let metadataExpressible: Metadata = ["HKWasUserEnteredValue": 10.0] 27 | XCTAssertEqual(metadataExpressible, ["HKWasUserEnteredValue": 10.0]) 28 | let metadataStringDictionary = Metadata.double(dictionary: ["HKWasUserEnteredValue": 10.0]) 29 | XCTAssertEqual(metadataStringDictionary, ["HKWasUserEnteredValue": 10.0]) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Tests/SourceRevisionTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SourceRevisionTests.swift 3 | // 4 | // 5 | // Created by Kachalov, Victor on 03.09.21. 6 | // 7 | 8 | import XCTest 9 | import HealthKitReporter 10 | 11 | class SourceRevisionTests: XCTestCase { 12 | func testCreateThenEncodeThenDecode() throws { 13 | let sut = SourceRevision( 14 | source: Source( 15 | name: "mySource", 16 | bundleIdentifier: "com.kvs.hkreporter" 17 | ), 18 | version: "1.0.0", 19 | productType: "CocoaPod", 20 | systemVersion: "1.0.0.0", 21 | operatingSystem: SourceRevision.OperatingSystem( 22 | majorVersion: 1, 23 | minorVersion: 1, 24 | patchVersion: 1 25 | ) 26 | ) 27 | let encoded = try sut.encoded() 28 | let decoded = try JSONDecoder().decode( 29 | SourceRevision.self, 30 | from: encoded.data(using: .utf8)! 31 | ) 32 | XCTAssertEqual(decoded.source.name, "mySource") 33 | XCTAssertEqual(decoded.source.bundleIdentifier, "com.kvs.hkreporter") 34 | XCTAssertEqual(decoded.version, "1.0.0") 35 | XCTAssertEqual(decoded.productType, "CocoaPod") 36 | XCTAssertEqual(decoded.systemVersion, "1.0.0.0") 37 | XCTAssertEqual(decoded.operatingSystem.majorVersion, 1) 38 | XCTAssertEqual(decoded.operatingSystem.minorVersion, 1) 39 | XCTAssertEqual(decoded.operatingSystem.patchVersion, 1) 40 | } 41 | func testCreateFromDictionary() throws { 42 | let dictionary: [String: Any] = [ 43 | "source": [ 44 | "name": "health_kit_reporter_example", 45 | "bundleIdentifier": "com.kvs.healthKitReporterExample" 46 | ], 47 | "version": "1", 48 | "productType": "iPhone13,3", 49 | "systemVersion": "14.5.0", 50 | "operatingSystem": [ 51 | "majorVersion": 14, 52 | "minorVersion": 5, 53 | "patchVersion": 0 54 | ] 55 | ] 56 | let sut = try SourceRevision.make(from: dictionary) 57 | XCTAssertEqual(sut.source.name, "health_kit_reporter_example") 58 | XCTAssertEqual(sut.source.bundleIdentifier, "com.kvs.healthKitReporterExample") 59 | XCTAssertEqual(sut.version, "1") 60 | XCTAssertEqual(sut.productType, "iPhone13,3") 61 | XCTAssertEqual(sut.systemVersion, "14.5.0") 62 | XCTAssertEqual(sut.operatingSystem.majorVersion, 14) 63 | XCTAssertEqual(sut.operatingSystem.minorVersion, 5) 64 | XCTAssertEqual(sut.operatingSystem.patchVersion, 0) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Tests/SourceTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SourceTests.swift 3 | // 4 | // 5 | // Created by Kachalov, Victor on 03.09.21. 6 | // 7 | 8 | import XCTest 9 | import HealthKitReporter 10 | 11 | class SourceTests: XCTestCase { 12 | func testCreateThenEncodeThenDecode() throws { 13 | let sut = Source(name: "myApp", bundleIdentifier: "com.my.app") 14 | let encoded = try sut.encoded() 15 | let decoded = try JSONDecoder().decode( 16 | Source.self, 17 | from: encoded.data(using: .utf8)! 18 | ) 19 | XCTAssertEqual(decoded.name, "myApp") 20 | XCTAssertEqual(decoded.bundleIdentifier, "com.my.app") 21 | } 22 | func testCreateFromDictionary() throws { 23 | let dictionary = [ 24 | "name": "myApp", 25 | "bundleIdentifier": "com.my.app", 26 | ] 27 | let sut = try Source.make(from: dictionary) 28 | XCTAssertEqual(sut.name, "myApp") 29 | XCTAssertEqual(sut.bundleIdentifier, "com.my.app") 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Tests/StatisticsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StatisticsTests.swift 3 | // 4 | // 5 | // Created by Kachalov, Victor on 04.09.21. 6 | // 7 | 8 | import XCTest 9 | import HealthKitReporter 10 | 11 | class StatisticsTests: XCTestCase { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /Tests/WorkoutConfigurationTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WorkoutConfigurationTests.swift 3 | // 4 | // 5 | // Created by Kachalov, Victor on 03.09.21. 6 | // 7 | 8 | import XCTest 9 | import HealthKitReporter 10 | 11 | class WorkoutConfigurationTests: XCTestCase { 12 | @available(iOS 10.0, *) 13 | func testCreateThenEncodeThenDecode() throws { 14 | let sut = WorkoutConfiguration( 15 | activityValue: 1, 16 | locationValue: 1, 17 | swimmingValue: 1, 18 | harmonized: WorkoutConfiguration.Harmonized( 19 | value: 10.0, 20 | unit: "m" 21 | ) 22 | ) 23 | let encoded = try sut.encoded() 24 | let decoded = try JSONDecoder().decode( 25 | WorkoutConfiguration.self, 26 | from: encoded.data(using: .utf8)! 27 | ) 28 | XCTAssertEqual(decoded.activityValue, 1) 29 | XCTAssertEqual(decoded.locationValue, 1) 30 | XCTAssertEqual(decoded.swimmingValue, 1) 31 | XCTAssertEqual(decoded.harmonized.unit, "m") 32 | XCTAssertEqual(decoded.harmonized.value, 10.0) 33 | XCTAssertEqual(decoded.harmonized.unit, "m") 34 | } 35 | @available(iOS 10.0, *) 36 | func testCreateFromDictionary() throws { 37 | let dictionary: [String: Any] = [ 38 | "activityValue": 1, 39 | "locationValue": 1, 40 | "swimmingValue": 1, 41 | "harmonized": [ 42 | "value": 1.5, 43 | "unit": "m" 44 | ] 45 | ] 46 | let sut = try WorkoutConfiguration.make(from: dictionary) 47 | XCTAssertEqual(sut.activityValue, 1) 48 | XCTAssertEqual(sut.locationValue, 1) 49 | XCTAssertEqual(sut.swimmingValue, 1) 50 | XCTAssertEqual(sut.harmonized.value, 1.5) 51 | XCTAssertEqual(sut.harmonized.unit, "m") 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Tests/WorkoutEventTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WorkoutEventTests.swift 3 | // 4 | // 5 | // Created by Kachalov, Victor on 04.09.21. 6 | // 7 | 8 | import XCTest 9 | import HealthKitReporter 10 | 11 | class WorkoutEventTests: XCTestCase { 12 | func testCreateThenEncodeThenDecode() throws { 13 | let startDate = Date(timeIntervalSince1970: 1626884800) 14 | let sut = WorkoutEvent( 15 | startTimestamp: startDate.timeIntervalSince1970, 16 | endTimestamp: startDate.timeIntervalSince1970, 17 | duration: 60.0, 18 | harmonized: WorkoutEvent.Harmonized( 19 | value: 6, 20 | description: "Paused", 21 | metadata: ["event": "value"] 22 | ) 23 | ) 24 | let encoded = try sut.encoded() 25 | let decoded = try JSONDecoder().decode( 26 | WorkoutEvent.self, 27 | from: encoded.data(using: .utf8)! 28 | ) 29 | XCTAssertEqual(decoded.startTimestamp, 1626884800) 30 | XCTAssertEqual(decoded.endTimestamp, 1626884800) 31 | XCTAssertEqual(decoded.duration, 60.0) 32 | XCTAssertEqual(decoded.harmonized.value, 6) 33 | XCTAssertEqual(decoded.harmonized.description, "Paused") 34 | XCTAssertEqual(decoded.harmonized.metadata, ["event": "value"]) 35 | } 36 | func testCreateFromDictionary() throws { 37 | let dictionary: [String: Any] = [ 38 | "startTimestamp": 1624906675.822, 39 | "endTimestamp": 1624906675.822, 40 | "duration": 0, 41 | "harmonized": [ 42 | "value": 6, 43 | "description": "Paused", 44 | "metadata": ["event": "value"] 45 | ] 46 | ] 47 | let epsilon = 1.0 48 | let sut = try WorkoutEvent.make(from: dictionary) 49 | XCTAssertEqual(sut.startTimestamp, 1624906675.822, accuracy: epsilon) 50 | XCTAssertEqual(sut.endTimestamp, 1624906675.822, accuracy: epsilon) 51 | XCTAssertEqual(sut.duration, 0.0) 52 | XCTAssertEqual(sut.harmonized.value, 6) 53 | XCTAssertEqual(sut.harmonized.description, "Paused") 54 | XCTAssertEqual(sut.harmonized.metadata, ["event": "value"]) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /_Pods.xcodeproj: -------------------------------------------------------------------------------- 1 | Example/Pods/Pods.xcodeproj --------------------------------------------------------------------------------