Compare commits
511 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 44c88877c5 | |||
| 6f2ac3f671 | |||
| 007583929c | |||
| c97d6bd78c | |||
| 6089b09963 | |||
| 87a7a6a718 | |||
| c1493b9c5c | |||
| d47e835e8e | |||
| 4b456ad2bb | |||
| 125ff93795 | |||
| 2a1bbeefe8 | |||
| ddc023fd4c | |||
| 160bb111f0 | |||
| 30742a5043 | |||
| ead0650dfb | |||
| 4f3d0f33df | |||
| 608d954197 | |||
| 74326baef4 | |||
| 5b1abe95c5 | |||
| fe9c9e1fc5 | |||
| b8b7b80f09 | |||
| 1bcc12bd80 | |||
| eb9464b6ce | |||
| c476ad838e | |||
| c167e4d84c | |||
| 77eb12636a | |||
| 27f4197374 | |||
| 34f473d8e5 | |||
| 42b9907579 | |||
| 67a04562d6 | |||
| f86668e8cb | |||
| 33ca348856 | |||
| bccef20ce5 | |||
| 0f92862e52 | |||
| c7a8cdd2c0 | |||
| 75142028b0 | |||
| d618d45635 | |||
| 7a33c6b4a7 | |||
| add20301a1 | |||
| fbc4dde858 | |||
| 18b6557cf3 | |||
| c195cf1a16 | |||
| 6679fb0922 | |||
| 740051812a | |||
| 5c602d7850 | |||
| 0b4b2ae6e5 | |||
| 0efee740e4 | |||
| 856d544fad | |||
| c257751d1d | |||
| 81e879e8f6 | |||
| ddeed599df | |||
| 65f525ff38 | |||
| 60bfacf507 | |||
| 0eae7e87f7 | |||
| 26597eb334 | |||
| 44c5535a8d | |||
| d06f070e83 | |||
| 633d31be97 | |||
| 6936eb34ac | |||
| 40891abb63 | |||
| f9b8e39876 | |||
| e14d327e3b | |||
| b9bcd6713c | |||
| bc2801bb9e | |||
| ba78feb4dd | |||
| 56a8bc1db3 | |||
| e86ef34a75 | |||
| 912a8112fb | |||
| 27644a292c | |||
| 3f2cb32b23 | |||
| d84944ac6a | |||
| ebfbfcb900 | |||
| 3bd27d6ebe | |||
| e11440b036 | |||
| fdedb11fea | |||
| 72a665b961 | |||
| cfe04441ad | |||
| 7fe3e227ad | |||
| 4d8f4f3bb1 | |||
| e29e7f2b73 | |||
| f2e133676b | |||
| 0c898b4abb | |||
| c1fa1ea154 | |||
| f659531d77 | |||
| 6e22f94cf5 | |||
| 88c7c87f28 | |||
| 6a9da73a8a | |||
| 0f0448bff7 | |||
| fd2c914b68 | |||
| 5122948de0 | |||
| 19d3624658 | |||
| 4f68c2bf4e | |||
| 9e1ed9e070 | |||
| 9fb7562ce5 | |||
| b1b25f9ac4 | |||
| a5b58c4c8e | |||
| 66c7b86484 | |||
| 7b850951ef | |||
| bd5f5bb231 | |||
| 2526450391 | |||
| 289a45c3dd | |||
| 931c7d7728 | |||
| 6d52269995 | |||
| f69401cae9 | |||
| 3d57bf65b8 | |||
| e7b75b5f88 | |||
| 590bb25559 | |||
| f7345070a9 | |||
| 558b5391a3 | |||
| 4ea6fc7d46 | |||
| 2289b01f7c | |||
| 4899548438 | |||
| bcc85b9a2c | |||
| 613d3a676c | |||
| e5fa2374ab | |||
| 859bbb4e17 | |||
| 9fe59ca7a8 | |||
| b3d72ce28a | |||
| e421214098 | |||
| 53e19548c4 | |||
| f3fdb66cf8 | |||
| ead682b402 | |||
| aeb3849fd7 | |||
| b9af6508ac | |||
| 2ff97e4558 | |||
| 26da4d8e34 | |||
| 6ee2b47dc1 | |||
| 84f806ee11 | |||
| a24dc1ac56 | |||
| c3618bb104 | |||
| 119b5f19d2 | |||
| 5195ed44f1 | |||
| 755db8b700 | |||
| 3d95111ace | |||
| 859fc28d8a | |||
| ebc4ccacc0 | |||
| 1c3aab92c1 | |||
| 70093149b5 | |||
| d595a0b199 | |||
| 764b5f8691 | |||
| 395eb936d5 | |||
| dcab7194c1 | |||
| 9c271d87f6 | |||
| d9ebc6d253 | |||
| 2e555356a6 | |||
| 69c830b8cd | |||
| a5aa647ed0 | |||
| d7d923e609 | |||
| 0cbb21d71d | |||
| f5fe7a47f3 | |||
| cbea809430 | |||
| a65d5da62f | |||
| 49f2914063 | |||
| f50340c7e1 | |||
| dfcc02d75f | |||
| 6ad71820d8 | |||
| 275cc3ae17 | |||
| 5f643fdcde | |||
| e444c32eb5 | |||
| 7c7dfc4861 | |||
| 9b9e941299 | |||
| 27a0755414 | |||
| 0dfb3455f7 | |||
| 47d6b5e0f9 | |||
| dbb0fcf2b7 | |||
| 170ca3e0b6 | |||
| 2c868e3bb4 | |||
| 1abd8bf548 | |||
| 5db0f6422e | |||
| 47f1a3bfd1 | |||
| 231ed3f90c | |||
| dd465a00e7 | |||
| 05325d6070 | |||
| ee446da653 | |||
| b39c2ea10d | |||
| 106b2d969f | |||
| cd02eefff7 | |||
| 55b97af36a | |||
| fa50f74e9c | |||
| 903e1cbffe | |||
| 4cda98e896 | |||
| 525d5b2350 | |||
| 191c17930d | |||
| cfe39f8201 | |||
| 4b35370e80 | |||
| 2404b20433 | |||
| a20f953467 | |||
| f41480f5bb | |||
| ddef74e9cc | |||
| a50f7cbc2f | |||
| 5715ea13c5 | |||
| 41575b830c | |||
| c6907b1e9e | |||
| 286f49bb5d | |||
| 59ac9c3213 | |||
| 0cfce94dd6 | |||
| cb4ff44859 | |||
| 0375b70146 | |||
| e9e2434d5f | |||
| 72fb6d5138 | |||
| 60aa464b02 | |||
| 8474d39cdf | |||
| 3f258d2c6c | |||
| aad49eac86 | |||
| 4b610ee085 | |||
| 78d59f7ed8 | |||
| a147b2e6e3 | |||
| 78bc96956c | |||
| 0e9ce26b82 | |||
| e2f284a4cb | |||
| 90af9211eb | |||
| 1107bd9dba | |||
| 4bcc83ecd3 | |||
| aa86bf3bed | |||
| 5de1888375 | |||
| 01eab7bd0c | |||
| 1da1d632b6 | |||
| 072d19c84b | |||
| 11effe846e | |||
| c185fce465 | |||
| 4863368e23 | |||
| 1ce947395d | |||
| c15b98e41a | |||
| 9341e85030 | |||
| 1934f143a5 | |||
| 9b1efb8641 | |||
| c1f30947be | |||
| dc22fe8191 | |||
| 4259a1cc92 | |||
| 70d4532d0e | |||
| 8e2313118f | |||
| 354cee7e7a | |||
| 7cfb8960c9 | |||
| cabaecb5d1 | |||
| 99bd214f23 | |||
| ea7c941424 | |||
| 75e880e1d9 | |||
| a788e35d25 | |||
| e9d7b847da | |||
| b0f5e2cbb7 | |||
| 2c4ef0f73e | |||
| 23900bed0e | |||
| 8c8a1adc47 | |||
| 4ada9cf5ff | |||
| 7b23d8b21e | |||
| d2f26a5ab9 | |||
| c170a8c38a | |||
| 934aa3283b | |||
| 639f75eaa6 | |||
| 5f969da0d7 | |||
| 080c69bd8b | |||
| 42c12ca73f | |||
| 025581ad55 | |||
| 65aee797ce | |||
| f7ea0167b1 | |||
| a5a8531fdf | |||
| e60db320ed | |||
| 8a6895d762 | |||
| 2ac579a610 | |||
| 4b9ae8e404 | |||
| f7d1591c19 | |||
| 9b539d7faf | |||
| e515278b4c | |||
| e3b2d91a8b | |||
| da41b8a43b | |||
| 86273181ac | |||
| 930d94f804 | |||
| d783c0b2b6 | |||
| 92cc2c0ef9 | |||
| 275b7b5ff9 | |||
| 642f87576a | |||
| 4735bc5d47 | |||
| 0e29f50f77 | |||
| daac3505b4 | |||
| 89838d63bd | |||
| 1337dbf851 | |||
| 0c60532068 | |||
| ea0fb64319 | |||
| 14bb563d26 | |||
| 09b7adc981 | |||
| 412f30652f | |||
| 9ef8b4a980 | |||
| ce365bbe10 | |||
| 778bcf71e0 | |||
| bf94333d32 | |||
| 9f5caad71d | |||
| 14fce4445f | |||
| 1984b46efb | |||
| 02a822baef | |||
| cdaa4d4c91 | |||
| 6e4a4560d8 | |||
| 657bcab2f7 | |||
| b8a999995f | |||
| 28ee60beb7 | |||
| e305e3c4b2 | |||
| 90f5a2cc62 | |||
| 75c75901a3 | |||
| c5396e8ae6 | |||
| 942a092bd1 | |||
| 1cb09d68d3 | |||
| fd11ee1e89 | |||
| 878acecad6 | |||
| 5d1fe51b42 | |||
| 077dfcc4ab | |||
| f24c371541 | |||
| 7a0101c12b | |||
| 887f8dd6bd | |||
| f6c8bc939b | |||
| b6cc2f3add | |||
| 24929ca8b1 | |||
| 8c270dca89 | |||
| 129ee6baef | |||
| a2e4b5aa1c | |||
| 75047cabb2 | |||
| 8a3fea6488 | |||
| 57cbebae64 | |||
| b13a470348 | |||
| 2ea828e29e | |||
| 7efb9ecb36 | |||
| ea777873af | |||
| fc7c55c3ab | |||
| df05877256 | |||
| 9025045a4c | |||
| 0571b6e960 | |||
| d1fbc178a1 | |||
| 6a2f18af4d | |||
| cc352493f9 | |||
| 43a4379c8e | |||
| 5c3db570f7 | |||
| 0980410b95 | |||
| 2e5d784608 | |||
| 288d710e51 | |||
| cc46125c69 | |||
| ca94aa7468 | |||
| d0293a0ee7 | |||
| b8c80c70c7 | |||
| 40e328e29e | |||
| 3f89fba2cf | |||
| bece29b351 | |||
| 4d1601a787 | |||
| 80d7685ab3 | |||
| 6e1334a2fc | |||
| 3c550b54b4 | |||
| 7ca7079522 | |||
| e4af7fe7fe | |||
| 8c3b5ce81f | |||
| 783ad4c6b4 | |||
| b85182b372 | |||
| 6e075cbfe3 | |||
| 410affbf5d | |||
| 182960a263 | |||
| 0971ef71f4 | |||
| 92ca8f86a5 | |||
| 24c46898e8 | |||
| 065a02eff1 | |||
| 2db99d6152 | |||
| f6cb8b09f0 | |||
| f9d49594fe | |||
| 62a31b087a | |||
| 70fd57488d | |||
| 1f457e1d54 | |||
| 729247cfcd | |||
| 218b8af66a | |||
| 069c388cb3 | |||
| e199db13b4 | |||
| a96c8b9540 | |||
| 865275d3aa | |||
| a1d29fe225 | |||
| 67bcc93947 | |||
| 8e8fabf79f | |||
| 292727ad91 | |||
| 2f1e58e234 | |||
| 86226e9ba8 | |||
| d40e8193d5 | |||
| 525f2c6efb | |||
| ad52da287c | |||
| 8d0fd95ad0 | |||
| 46c78c08a4 | |||
| 54927408bf | |||
| f325310d77 | |||
| dd4e4fdbce | |||
| 063cde7656 | |||
| fb1ef39165 | |||
| c570771b4d | |||
| 00ff2f3402 | |||
| 407de8ed11 | |||
| 6474ebfff7 | |||
| 340ba7210d | |||
| 8c86b8d80d | |||
| 21c89256fa | |||
| 62ae57bb11 | |||
| fbc4191081 | |||
| 674e06cebc | |||
| ade7f99010 | |||
| 94fbd3a6d1 | |||
| f24a74006a | |||
| 040b9d2220 | |||
| 2e1749a14d | |||
| 204d5dbf40 | |||
| 908b9cb713 | |||
| ff2c5ad055 | |||
| 66fa232ca7 | |||
| 75ad7512b8 | |||
| 8b521477dd | |||
| d60c81b004 | |||
| a44f2db8fd | |||
| 58d377d9ab | |||
| e56bf7d217 | |||
| 28ff1415dd | |||
| 8cbe836bb2 | |||
| 2289abf03a | |||
| 44ee48fa54 | |||
| b704be75b5 | |||
| 4048e34530 | |||
| b34fb01350 | |||
| 1b266c3652 | |||
| 8ab052577d | |||
| 1e34cf6d35 | |||
| d39a72e535 | |||
| 135e2209c8 | |||
| b865da3a75 | |||
| 40111a1431 | |||
| cc45d92266 | |||
| 1619428bb0 | |||
| 3ae773049a | |||
| 76eb4f97d9 | |||
| 12326634db | |||
| 36b90b813c | |||
| 22b769b786 | |||
| 0afc2027d7 | |||
| a78878c712 | |||
| f6fd29087b | |||
| 4517cd445e | |||
| d151e11004 | |||
| ec9b1c9108 | |||
| d7bf7a6ec3 | |||
| 4342caae71 | |||
| f7d7355c49 | |||
| 610ce5aefd | |||
| bd52a14196 | |||
| 423a89c879 | |||
| e444086399 | |||
| 0ed1df3945 | |||
| d71ab9538c | |||
| 7caaf525e3 | |||
| c3102d6183 | |||
| 73495244be | |||
| 85dd559217 | |||
| 81c8c10ebc | |||
| 63a7e30049 | |||
| 8fa839a66b | |||
| f8a77a1b1e | |||
| ddc0016bd1 | |||
| 8b81a8c5f1 | |||
| ff673f0ad4 | |||
| 116a8e08ad | |||
| 09e717c69f | |||
| 9b1bdd390c | |||
| b113ef0ee3 | |||
| 8e49cbc733 | |||
| 37cfadf81a | |||
| fb778eefcf | |||
| f384f9137d | |||
| 2f19a3e089 | |||
| b112945365 | |||
| 58c12a025b | |||
| 2aa09084c8 | |||
| 4d16e39f73 | |||
| 59d2ed7a39 | |||
| e5a4d01039 | |||
| e7aac14571 | |||
| 746b0866b0 | |||
| 11a29435cd | |||
| 00886afe69 | |||
| fdd608fee4 | |||
| b630dff27e | |||
| c41fac8023 | |||
| 2819b7714d | |||
| 81ec1f0056 | |||
| 36f710739d | |||
| 04f5a9e9f6 | |||
| aa2e83e2b5 | |||
| 79deaaa4b2 | |||
| a581d58243 | |||
| 718a4d3ed7 | |||
| 9241848bc3 | |||
| 913be47d6d | |||
| 88a94e106b | |||
| 2469bfe5a3 | |||
| 6ec5df751d | |||
| 7ef55e55e7 | |||
| 41d6a415ad | |||
| a3b2d68b7a | |||
| 87560025c8 | |||
| dc3c6e011c | |||
| 0180500607 | |||
| 42e6c97ee2 | |||
| ce6c79a60f | |||
| 86bcdaa3ee | |||
| 6d2c1400b1 | |||
| 8fa8dbae9e | |||
| d26712e5a1 | |||
| 0eba3970b5 | |||
| 20e9cb24d4 | |||
| 29f883b04b | |||
| 870a9f6ac5 | |||
| 30a346e94d | |||
| 7361442a04 | |||
| 398ae8029f | |||
| 1c0ac116d1 | |||
| 1ecf1d7027 |
@@ -1,8 +1,8 @@
|
||||
ResearchKit Framework
|
||||
===========
|
||||
|
||||
The ResearchKit™ framework is an open source software framework that makes it easy to
|
||||
create apps for medical research or for other research projects.
|
||||
The *ResearchKit™ framework* is an open source software framework that makes it easy to create apps
|
||||
for medical research or for other research projects.
|
||||
|
||||
* [Getting Started](#gettingstarted)
|
||||
* Documentation:
|
||||
@@ -16,36 +16,48 @@ create apps for medical research or for other research projects.
|
||||
Getting More Information
|
||||
========================
|
||||
|
||||
* Join the [ResearchKit Forum](https://forums.developer.apple.com/community/researchkit) for discussing uses of the ResearchKit framework and related projects.
|
||||
* Join the [*ResearchKit* Forum](https://forums.developer.apple.com/community/researchkit) for discussing uses of the *ResearchKit framework and* related projects.
|
||||
|
||||
Use Cases
|
||||
===========
|
||||
|
||||
A task in the ResearchKit framework contains a set of steps to present to a
|
||||
user. Everything, whether it’s a survey, the consent process, or active tasks,
|
||||
is represented as a task that can be presented with a task view controller.
|
||||
A task in the *ResearchKit framework* contains a set of steps to present to a user. Everything,
|
||||
whether it’s a *survey*, the *consent process*, or *active tasks*, is represented as a task that can
|
||||
be presented with a task view controller.
|
||||
|
||||
Surveys
|
||||
-------
|
||||
|
||||
The ResearchKit framework provides a pre-built user interface for surveys, which can be
|
||||
presented modally on an iPhone, iPod Touch, or iPad. See *[Creating Surveys](http://researchkit.org/docs/docs/Survey/CreatingSurveys.html)* for more information.
|
||||
The *ResearchKit framework* provides a pre-built user interface for surveys, which can be presented
|
||||
modally on an *iPhone*, *iPod Touch*, or *iPad*. See
|
||||
*[Creating Surveys](http://researchkit.org/docs/docs/Survey/CreatingSurveys.html)* for more
|
||||
information.
|
||||
|
||||
|
||||
Consent
|
||||
----------------
|
||||
|
||||
The ResearchKit framework provides visual consent templates that you can customize to
|
||||
explain the details of your research study and obtain a signature if needed. See *[Obtaining Consent](http://researchkit.org/docs/docs/InformedConsent/InformedConsent.html)* for more information.
|
||||
The *ResearchKit framework* provides visual consent templates that you can customize to explain the
|
||||
details of your research study and obtain a signature if needed.
|
||||
See *[Obtaining Consent](http://researchkit.org/docs/docs/InformedConsent/InformedConsent.html)* for
|
||||
more information.
|
||||
|
||||
|
||||
Active Tasks
|
||||
------------
|
||||
|
||||
Some studies may need data beyond survey questions or the passive data collection
|
||||
capabilities available through use of the HealthKit and CoreMotion APIs if you are
|
||||
programming for iOS. ResearchKit's active tasks invite users to perform activities
|
||||
under semi-controlled conditions, while iPhone sensors actively collect data. See *[Active Tasks](http://researchkit.org/docs/docs/ActiveTasks/ActiveTasks.html)* for more information.
|
||||
Some studies may need data beyond survey questions or the passive data collection capabilities
|
||||
available through use of the *HealthKit* and *CoreMotion* APIs if you are programming for *iOS*.
|
||||
*ResearchKit*'s active tasks invite users to perform activities under semi-controlled conditions,
|
||||
while *iPhone* sensors actively collect data. See
|
||||
*[Active Tasks](http://researchkit.org/docs/docs/ActiveTasks/ActiveTasks.html)* for more
|
||||
information.
|
||||
|
||||
Charts
|
||||
------------
|
||||
*ResearchKit* includes a *Charts module*. It features three chart types: a *pie chart* (`ORKPieChartView`), a *line graph chart* (`ORKLineGraphChartView`), and a *discrete graph chart* (`ORKDiscreteGraphChartView`).
|
||||
|
||||
The views in the *Charts module* can be used independently of the rest of *ResearchKit*. They don't automatically connect with any other part of *ResearchKit*: the developer has to supply the data to be displayed through the views' `dataSources`, which allows for maximum flexibility.
|
||||
|
||||
|
||||
Getting Started<a name="gettingstarted"></a>
|
||||
@@ -55,16 +67,15 @@ Getting Started<a name="gettingstarted"></a>
|
||||
Requirements
|
||||
------------
|
||||
|
||||
The primary ResearchKit framework codebase supports iOS and requires Xcode 7.0
|
||||
or newer.
|
||||
The ResearchKit framework has a Base SDK version of 8.0, meaning that apps
|
||||
using the ResearchKit framework can run on devices with iOS 8.0 or newer.
|
||||
The primary *ResearchKit framework* codebase supports *iOS* and requires *Xcode 8.0* or newer. The
|
||||
*ResearchKit framework* has a *Base SDK* version of *8.0*, meaning that apps using the *ResearchKit
|
||||
framework* can run on devices with *iOS 8.0* or newer.
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
The latest stable version of ResearchKit framework can be cloned with
|
||||
The latest stable version of *ResearchKit framework* can be cloned with
|
||||
|
||||
```
|
||||
git clone -b stable https://github.com/ResearchKit/ResearchKit.git
|
||||
@@ -79,48 +90,49 @@ git clone https://github.com/ResearchKit/ResearchKit.git
|
||||
Building
|
||||
--------
|
||||
|
||||
Build the ResearchKit framework by opening `ResearchKit.xcodeproj` and running the
|
||||
`ResearchKit` framework target. Optionally, run the unit tests too.
|
||||
Build the *ResearchKit framework* by opening `ResearchKit.xcodeproj` and running the `ResearchKit`
|
||||
framework target. Optionally, run the unit tests too.
|
||||
|
||||
|
||||
Adding the ResearchKit framework to your App
|
||||
------------------------------
|
||||
|
||||
This walk-through shows how to embed the ResearchKit framework in your app as a
|
||||
dynamic framework, and present a simple task view controller.
|
||||
This walk-through shows how to embed the *ResearchKit framework* in your app as a dynamic framework,
|
||||
and present a simple task view controller.
|
||||
|
||||
###1. Add the ResearchKit framework to Your Project
|
||||
### 1. Add the ResearchKit framework to Your Project
|
||||
|
||||
To get started, drag `ResearchKit.xcodeproj` from your checkout into
|
||||
your iOS app project in Xcode:
|
||||
To get started, drag `ResearchKit.xcodeproj` from your checkout into your *iOS* app project
|
||||
in *Xcode*:
|
||||
|
||||
<center>
|
||||
<figure>
|
||||
<img src="../../wiki/AddingResearchKitXcode.png" alt="Adding the ResearchKit framework to your project" align="middle"/>
|
||||
<img src="../../wiki/AddingResearchKitXcode.png" alt="Adding the ResearchKit framework to your
|
||||
project" align="middle"/>
|
||||
</figure>
|
||||
</center>
|
||||
|
||||
Then, embed the ResearchKit framework as a dynamic framework in your app, by adding
|
||||
it to the Embedded Binaries section of the General pane for your
|
||||
target as shown in the figure below.
|
||||
Then, embed the *ResearchKit framework* as a dynamic framework in your app, by adding it to the
|
||||
*Embedded Binaries* section of the *General* pane for your target as shown in the figure below.
|
||||
|
||||
<center>
|
||||
<figure>
|
||||
<img src="../../wiki/AddedBinaries.png" width="100%" alt="Adding the ResearchKit framework to Embedded Binaries" align="middle"/>
|
||||
<img src="../../wiki/AddedBinaries.png" width="100%" alt="Adding the ResearchKit framework to
|
||||
Embedded Binaries" align="middle"/>
|
||||
<figcaption><center>Adding the ResearchKit framework to Embedded Binaries</center></figcaption>
|
||||
</figure>
|
||||
</center>
|
||||
|
||||
Note: You can also import ResearchKit into your project using a [dependency manager](./docs-standalone/dependency-management.md) such as CocoaPods or Carthage.
|
||||
Note: You can also import *ResearchKit* into your project using a
|
||||
[dependency manager](./docs-standalone/dependency-management.md) such as *CocoaPods* or *Carthage*.
|
||||
|
||||
###2. Create a Step
|
||||
### 2. Create a Step
|
||||
|
||||
In this walk-through, we will use the ResearchKit framework to modally present a
|
||||
simple single-step task showing a single instruction.
|
||||
In this walk-through, we will use the *ResearchKit framework* to modally present a simple
|
||||
single-step task showing a single instruction.
|
||||
|
||||
Create a step for your task by adding some code, perhaps in
|
||||
`viewDidAppear:` of an existing view controller. To keep things
|
||||
simple, we'll use an instruction step (`ORKInstructionStep`) and name
|
||||
Create a step for your task by adding some code, perhaps in `viewDidAppear:` of an existing view
|
||||
controller. To keep things simple, we'll use an instruction step (`ORKInstructionStep`) and name
|
||||
the step `myStep`.
|
||||
|
||||
*Objective-C*
|
||||
@@ -138,12 +150,11 @@ let myStep = ORKInstructionStep(identifier: "intro")
|
||||
myStep.title = "Welcome to ResearchKit"
|
||||
```
|
||||
|
||||
###3. Create a Task
|
||||
### 3. Create a Task
|
||||
|
||||
Use the ordered task class (`ORKOrderedTask`) to create a task that
|
||||
contains `myStep`. An ordered task is just a task where the order and
|
||||
selection of later steps does not depend on the results of earlier
|
||||
ones. Name your task `task` and initialize it with `myStep`.
|
||||
Use the ordered task class (`ORKOrderedTask`) to create a task that contains `myStep`. An ordered
|
||||
task is just a task where the order and selection of later steps does not depend on the results of
|
||||
earlier ones. Name your task `task` and initialize it with `myStep`.
|
||||
|
||||
*Objective-C*
|
||||
|
||||
@@ -158,11 +169,10 @@ ORKOrderedTask *task =
|
||||
let task = ORKOrderedTask(identifier: "task", steps: [myStep])
|
||||
```
|
||||
|
||||
###4. Present the Task
|
||||
### 4. Present the Task
|
||||
|
||||
Create a task view controller (`ORKTaskViewController`) and initialize
|
||||
it with your `task`. A task view controller manages a task and collects the
|
||||
results of each step. In this case, your task view
|
||||
Create a task view controller (`ORKTaskViewController`) and initialize it with your `task`. A task
|
||||
view controller manages a task and collects the results of each step. In this case, your task view
|
||||
controller simply displays your instruction step.
|
||||
|
||||
*Objective-C*
|
||||
@@ -182,9 +192,9 @@ taskViewController.delegate = self
|
||||
presentViewController(taskViewController, animated: true, completion: nil)
|
||||
```
|
||||
|
||||
The above snippet assumes that your class implements the
|
||||
`ORKTaskViewControllerDelegate` protocol. This has just one required method,
|
||||
which you must implement in order to handle the completion of the task:
|
||||
The above snippet assumes that your class implements the `ORKTaskViewControllerDelegate` protocol.
|
||||
This has just one required method, which you must implement in order to handle the completion of
|
||||
the task:
|
||||
|
||||
*Objective-C*
|
||||
|
||||
@@ -204,20 +214,19 @@ which you must implement in order to handle the completion of the task:
|
||||
*Swift*
|
||||
|
||||
```swift
|
||||
func taskViewController(taskViewController: ORKTaskViewController,
|
||||
didFinishWithReason reason: ORKTaskViewControllerFinishReason,
|
||||
error: NSError?) {
|
||||
let taskResult = taskViewController.result
|
||||
// You could do something with the result here.
|
||||
func taskViewController(_ taskViewController: ORKTaskViewController,
|
||||
didFinishWith reason: ORKTaskViewControllerFinishReason,
|
||||
error: Error?) {
|
||||
let taskResult = taskViewController.result
|
||||
// You could do something with the result here.
|
||||
|
||||
// Then, dismiss the task view controller.
|
||||
dismissViewControllerAnimated(true, completion: nil)
|
||||
// Then, dismiss the task view controller.
|
||||
dismiss(true, completion: nil)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
If you now run your app, you should see your first ResearchKit framework
|
||||
instruction step:
|
||||
If you now run your app, you should see your first *ResearchKit framework* instruction step:
|
||||
|
||||
<center>
|
||||
<figure>
|
||||
@@ -230,22 +239,21 @@ instruction step:
|
||||
What else can the ResearchKit framework do?
|
||||
-----------------------------
|
||||
|
||||
The ResearchKit [`ORKCatalog`](samples/ORKCatalog) sample app is a
|
||||
good place to start. Find the project in ResearchKit's
|
||||
[`samples`](samples) directory. This project includes a list of all
|
||||
the types of steps supported by the ResearchKit framework in one tab, and displays a
|
||||
browser for the results of the last completed task in the other tab.
|
||||
The *ResearchKit* [`ORKCatalog`](samples/ORKCatalog) sample app is a good place to start. Find the
|
||||
project in ResearchKit's [`samples`](samples) directory. This project includes a list of all the
|
||||
types of steps supported by the *ResearchKit framework* in the first tab, and displays a browser for the
|
||||
results of the last completed task in the second tab. The third tab shows some examples from the *Charts module*.
|
||||
|
||||
|
||||
|
||||
License<a name="license"></a>
|
||||
=======
|
||||
|
||||
The source in the ResearchKit repository is made available under the
|
||||
following license unless another license is explicitly identified:
|
||||
The source in the *ResearchKit* repository is made available under the following license unless
|
||||
another license is explicitly identified:
|
||||
|
||||
```
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
Copyright (c) 2015 - 2017, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
+174
-47
@@ -1,5 +1,132 @@
|
||||
# ResearchKit Release Notes
|
||||
|
||||
## ResearchKit 1.5 Release Notes
|
||||
|
||||
*ResearchKit 1.5* supports *iOS* and requires *Xcode 8.0* or newer. The minimum supported *Base SDK* is *8.0*.
|
||||
|
||||
In addition to general stabiltiy and performance improvements, *ResearchKit 1.5* includes the following new features and enhancements.
|
||||
|
||||
- **New Active Tasks**
|
||||
|
||||
- **Stroop Test**
|
||||
|
||||
*Contributed by [Apple Inc](https://github.com/researchkit).*
|
||||
|
||||
The *Stroop Test* shows the participant different combinations of text and tint colors on the screen.
|
||||
|
||||
Users must ignore the text and instead select the button that reflects the first letter of the tint color.
|
||||
|
||||
- **Trail Making Test**
|
||||
|
||||
*Contributed by Faraz Hussain.*
|
||||
|
||||
The *Trail Making Test* instructs participants to connect a series of labelled circles and the time to complete the test is recorded.
|
||||
|
||||
- **Range of Motion Test**
|
||||
|
||||
*Contributed by Daren Levy, Dr. Raj Karia, John Guydo.*
|
||||
|
||||
Participants are instructed to follow a series of steps while accelerometer and gyroscope data is captured to measure flexed and extended positions for both the shoulder and knee.
|
||||
|
||||
- **Touch Anywhere Active Task**
|
||||
|
||||
*Contributed by Daren Levy, Dr. Raj Karia, John Guydo*
|
||||
|
||||
Allows the user to get their device in the proper position and then tap the screen to indicate they are ready to begin the next step.
|
||||
|
||||
- **New Steps**
|
||||
|
||||
- **Video Instruction Step**
|
||||
|
||||
*Contributed by [Oliver Schäfer](https://github.com/oliverschaefer).*
|
||||
|
||||
The *Video Instruction Step* provides a step to be used to display a video.
|
||||
|
||||
This step can be used to display videos to users from either a local or remote source.
|
||||
|
||||
- **Other Improvements**
|
||||
|
||||
- **Tone Audiometry Test**
|
||||
|
||||
*Contributed by [Apple Inc](https://github.com/researchkit).*
|
||||
|
||||
Updated to include both a left and right button.
|
||||
|
||||
- **Digital Object Identifier**
|
||||
|
||||
*Contributed by [Apple Inc](https://github.com/researchkit).*
|
||||
|
||||
Assigns a Digital Object Identifier to the ResearchKit repository on GitHub to use when referencing the framework.
|
||||
|
||||
|
||||
## ResearchKit 1.4 Release Notes
|
||||
|
||||
*ResearchKit 1.4* supports *iOS* and requires *Xcode 8.0* or newer. The minimum supported *Base SDK* is *8.0*.
|
||||
|
||||
In addition to general stabiltiy and performance improvements, *ResearchKit 1.4* includes the following new features and enhancements.
|
||||
|
||||
- **New Active Task**
|
||||
|
||||
- **Hand Tremor Task**
|
||||
|
||||
*Contributed by [Shannon Young](https://github.com/syoung-smallwisdom).*
|
||||
|
||||
The *Hand Tremor Task* asks the participant to hold the device with their most affected hand in various positions while accelerometer and motion data is captured.
|
||||
|
||||
- **Walk Back and Forth Task**
|
||||
|
||||
*Contributed by [Shannon Young](https://github.com/syoung-smallwisdom).*
|
||||
|
||||
The *Walk Back and Forth Task* addresses the concern of researchers/participants who have difficulty locating an unobstructed path for 20 steps.
|
||||
|
||||
Instructs users to walk and turn in a full circle, allowing the tests to be conducted in a smaller space.
|
||||
|
||||
- **New Steps**
|
||||
|
||||
- **Video Capture Step**
|
||||
|
||||
*Contributed by [Apple Inc](https://github.com/researchkit).*
|
||||
|
||||
The *Video Capture Step* provides a step to be used to record video.
|
||||
|
||||
The step can be used as part of a survey to capture video respones as well.
|
||||
|
||||
- **Review Step**
|
||||
|
||||
*Contributed by [Oliver Schäfer](https://github.com/oliverschaefer).*
|
||||
|
||||
The *Review Step* allows a participant to review and modify their answers to a survey.
|
||||
|
||||
The step can be used in the middle of a survey, at the end of a survey, or a standalone module.
|
||||
|
||||
- **Signature Step**
|
||||
|
||||
*Contributed by [Oliver Schäfer](https://github.com/oliverschaefer).*
|
||||
|
||||
The *Signature Step* provides an interface for a participant to sign their name.
|
||||
|
||||
The step can be used for handwriting detection or simply to sign a document.
|
||||
|
||||
- **Table Step**
|
||||
|
||||
*Contributed by [Shannon Young](https://github.com/syoung-smallwisdom).*
|
||||
|
||||
The *Table Step* provides a way to neatly display data in a table.
|
||||
|
||||
- **Other Improvements**
|
||||
|
||||
- **Data Collection Module**
|
||||
|
||||
*Contributed by [Apple Inc](https://github.com/researchkit).*
|
||||
|
||||
The *Data Collection Module* makes it even easier to aggregate data from HealthKit and device sensors.
|
||||
|
||||
- **Tapping Test**
|
||||
|
||||
*Contributed by [Michał Zaborowski](https://github.com/m1entus).*
|
||||
|
||||
The *Tapping Test* is updated to include tap duration as part of the result.
|
||||
|
||||
|
||||
## ResearchKit 1.3 Release Notes
|
||||
|
||||
@@ -7,88 +134,88 @@
|
||||
|
||||
In addition to general stability and performance improvements, *ResearchKit 1.3* includes the following new features and enhancements.
|
||||
|
||||
- **New Active Tasks**
|
||||
- **New Active Task**
|
||||
|
||||
- **9-Hole Peg Test**
|
||||
|
||||
|
||||
*Contributed by [Julien Therier](https://github.com/julientherier).*
|
||||
|
||||
The *[9-Hole Peg Test] task* is used to test upper extremity functionality.
|
||||
|
||||
The test involves putting a variable variable number of pegs in a hole, and then removing them.
|
||||
|
||||
The *9-Hole Peg Test task* is used to test upper extremity functionality.
|
||||
|
||||
The test involves putting a variable number of pegs in a hole and subsequently removing them.
|
||||
|
||||
The test is documented in the scientific literature to measure the *[MSFC score in Multiple Sclerosis](http://www.nationalmssociety.org/For-Professionals/Researchers/Resources-for-Researchers/Clinical-Study-Measures/9-Hole-Peg-Test-(9-HPT))* or *[Parkinson's Disease](http://www.ncbi.nlm.nih.gov/pubmed/22020457)*.
|
||||
|
||||
- **Sample App**
|
||||
|
||||
*Contributed by [Apple Inc](https://github.com/researchkit).*
|
||||
|
||||
The *[Sample App]* serves as a template application that combines different modules from the ResearchKit framework.
|
||||
The *Sample App* (`ORKSample` project on *ResearchKit*'s workspace) serves as a template application that combines different modules from the *ResearchKit framework*.
|
||||
|
||||
- **Account Module**
|
||||
|
||||
*Contributed by [Apple Inc](https://github.com/researchkit).*
|
||||
|
||||
The *[Account Module]* provides steps to facilitate account creation and login.
|
||||
|
||||
The *Account Module* provides steps to facilitate account creation and login.
|
||||
|
||||
The module includes the following steps:
|
||||
|
||||
1. Registration to create a new account.
|
||||
2. Verification to verify email.
|
||||
3. Login to allow registered users to login.
|
||||
|
||||
|
||||
1. *Registration*, used to allow the participant to create a new account.
|
||||
2. *Verification*, used to confirm if the participant has verified the provided email address.
|
||||
3. *Login*, used to allow registered users to login.
|
||||
|
||||
- **Passcode with Touch ID**
|
||||
|
||||
*Contributed by [Apple Inc](https://github.com/researchkit).*
|
||||
|
||||
The *[Passcode with Touch ID] module* provides the ability to secure any ResearchKit application with a pin entry.
|
||||
|
||||
This module includes a *Keychain Wrapper* that stores the passcode on the device, as well as the option to use Touch ID on compatible devices. The passcode module supports 4-pin and 6-pin entries.
|
||||
|
||||
The passcode module can be used in the following scenarios:
|
||||
|
||||
1. Passcode creation step which can be used as part of onboarding to create a passcode and store it in the keychain.
|
||||
2. Passcode authentication view controller which can be presented modally when appropriate.
|
||||
3. Passcode modification view controller which allows the participant to change their passcode.
|
||||
|
||||
The *Passcode with Touch ID module* provides the ability to secure any *ResearchKit* application with a numeric passcode.
|
||||
|
||||
This module includes a *Keychain Wrapper* that stores the passcode on the device, as well as the option to use *Touch ID* on compatible devices. The passcode module supports 4-digit and 6-digit numeric codes.
|
||||
|
||||
The passcode module provides the following components:
|
||||
|
||||
1. *Passcode creation step*, which can be used as part of onboarding to create a passcode and store it in the keychain.
|
||||
2. *Passcode authentication view controller*, which can be modally presented when appropriate.
|
||||
3. *Passcode modification view controller*, which allows the participant to change their passcode.
|
||||
|
||||
- **Other Improvements**
|
||||
|
||||
- **Optional Form Items**
|
||||
|
||||
*Contributed by [Ricardo Sánchez-Sáez](https://github.com/rsanchezsaez).*
|
||||
|
||||
Implements the `ORKFormItem` `optional` property.
|
||||
|
||||
The *Continue/Done* button of form steps enables only if:
|
||||
|
||||
- At least one form item has an answer.
|
||||
- All answered form items are valid.
|
||||
- All the non-optional form items have answers.
|
||||
Adds the `optional` property to `ORKFormItem`.
|
||||
|
||||
The *Continue/Done* button of form steps is enabled when all of the following conditions are met:
|
||||
|
||||
- At least one form item has an answer.
|
||||
- All the non-optional form items have answers.
|
||||
- All answered form items have valid answers.
|
||||
|
||||
- **Location Question**
|
||||
|
||||
|
||||
*Contributed by [Quintiles](https://github.com/QuintilesRK).*
|
||||
|
||||
A *Location Question* can be used to request details about the participant's current location or a specific address.
|
||||
|
||||
The question uses *MapKit* to provides a visual representation for the specified address.
|
||||
|
||||
|
||||
A *Location Question* can be used to request details about the participant's current location or about a specific address.
|
||||
|
||||
The question uses *MapKit* to provide a visual representation for the specified address.
|
||||
|
||||
- **Wait Step**
|
||||
|
||||
|
||||
*Contributed by [Quintiles](https://github.com/QuintilesRK).*
|
||||
|
||||
|
||||
The *Wait Step* provides a step to be used in-between steps when additional data processing is required.
|
||||
|
||||
|
||||
The step supports both indeterminate and determinate progress views, as well as the ability to show text status updates.
|
||||
|
||||
|
||||
- **Validated Text Answer Format**
|
||||
|
||||
|
||||
*Contributed by [Quintiles](https://github.com/QuintilesRK).*
|
||||
|
||||
|
||||
The *Validated Text Answer Format* enhances the existing *Text Answer Format* by providing input validation using a regular expression.
|
||||
|
||||
|
||||
A valid *NSRegularExpression* object and an *error message* string are required to properly use this answer format.
|
||||
|
||||
|
||||
|
||||
## ResearchKit 1.2 Release Notes
|
||||
|
||||
@@ -170,7 +297,7 @@ In addition to general stability and performance improvements, *ResearchKit 1.1*
|
||||
A new type of *conditional ordered task* (`ORKNavigableOrderedTask`) has been implemented.
|
||||
|
||||
The developer can use the `ORKStepNavigationRule` subclasses to dynamically navigate between the task steps:
|
||||
- `ORKPredicateStepNavigationRule` allows to make conditional jumps by matching previous results (either those of the the ongoing task, or those of any previously stored task result tree). You typically use the class methods in the `ORKResultPredicate` class to match answers in the most commonly used result types.
|
||||
- `ORKPredicateStepNavigationRule` allows to make conditional jumps by matching previous results (either those of the ongoing task, or those of any previously stored task result tree). You typically use the class methods in the `ORKResultPredicate` class to match answers in the most commonly used result types.
|
||||
- `ORKDirectStepNavigationRule` provides support for unconditional jumps.
|
||||
|
||||
- **New Active Tasks**
|
||||
@@ -216,4 +343,4 @@ In addition to general stability and performance improvements, *ResearchKit 1.1*
|
||||
|
||||
*Contributed by [Apple Inc.](https://github.com/researchkit) and [Ricardo Sánchez-Sáez](https://github.com/rsanchezsaez).*
|
||||
|
||||
*iPhone landscape orientation support* has been implemented.
|
||||
*iPhone landscape orientation support* has been implemented.
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'ResearchKit'
|
||||
s.version = '1.3.0'
|
||||
s.version = '1.5.2'
|
||||
s.summary = 'ResearchKit is an open source software framework that makes it easy to create apps for medical research or for other research projects.'
|
||||
s.homepage = 'https://www.github.com/ResearchKit/ResearchKit'
|
||||
s.documentation_url = 'http://researchkit.github.io/docs/'
|
||||
@@ -8,8 +8,8 @@ Pod::Spec.new do |s|
|
||||
s.author = { 'researchkit.org' => 'http://researchkit.org' }
|
||||
s.source = { :git => 'https://github.com/ResearchKit/ResearchKit.git', :tag => s.version.to_s }
|
||||
s.public_header_files = `./scripts/find_headers.rb --public --private`.split("\n")
|
||||
s.source_files = 'ResearchKit/**/*.{h,m}'
|
||||
s.source_files = 'ResearchKit/**/*.{h,m,swift}'
|
||||
s.resources = 'ResearchKit/**/*.{fsh,vsh}', 'ResearchKit/Animations/**/*.m4v', 'ResearchKit/Artwork.xcassets', 'ResearchKit/Localized/*.lproj'
|
||||
s.platform = :ios, '8.0'
|
||||
s.platform = :ios, '8.2'
|
||||
s.requires_arc = true
|
||||
end
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0700"
|
||||
LastUpgradeVersion = "0900"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0700"
|
||||
LastUpgradeVersion = "0900"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -30,6 +30,6 @@
|
||||
|
||||
|
||||
// Shared header for accessibility functionality.
|
||||
#import "UIView+ORKAccessibility.h"
|
||||
#import "ORKAccessibilityFunctions.h"
|
||||
#import "ORKLineGraphAccessibilityElement.h"
|
||||
#import "UIView+ORKAccessibility.h"
|
||||
|
||||
@@ -30,9 +30,11 @@
|
||||
|
||||
|
||||
#import "ORKDefines.h"
|
||||
#import "ORKHelpers.h"
|
||||
#import "ORKHelpers_Internal.h"
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class ORKScaleSlider;
|
||||
|
||||
// Used to properly format values from the ORKScaleSlider.
|
||||
@@ -43,7 +45,7 @@ ORK_EXTERN NSString *ORKAccessibilityFormatContinuousScaleSliderValue(CGFloat va
|
||||
ORK_EXTERN void ORKAccessibilityPerformBlockAfterDelay(NSTimeInterval delay, void(^block)(void));
|
||||
|
||||
// Convenience for posting an accessibility notification after a delay.
|
||||
ORK_INLINE void ORKAccessibilityPostNotificationAfterDelay(UIAccessibilityNotifications notification, id argument, NSTimeInterval delay) {
|
||||
ORK_INLINE void ORKAccessibilityPostNotificationAfterDelay(UIAccessibilityNotifications notification, _Nullable id argument, NSTimeInterval delay) {
|
||||
ORKAccessibilityPerformBlockAfterDelay(delay, ^{
|
||||
UIAccessibilityPostNotification(notification, argument);
|
||||
});
|
||||
@@ -52,3 +54,5 @@ ORK_INLINE void ORKAccessibilityPostNotificationAfterDelay(UIAccessibilityNotifi
|
||||
// Creates a string suitable for Voice Over by joining the variables with ", " and avoiding nil and empty strings.
|
||||
#define ORKAccessibilityStringForVariables(...) _ORKAccessibilityStringForVariables(ORK_NARG(__VA_ARGS__), ##__VA_ARGS__)
|
||||
ORK_EXTERN NSString *_ORKAccessibilityStringForVariables(NSInteger numParameters, NSString *baseString, ...);
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -29,11 +29,14 @@
|
||||
*/
|
||||
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "ORKAccessibilityFunctions.h"
|
||||
#import "ORKAnswerFormat_Internal.h"
|
||||
@import UIKit;
|
||||
|
||||
#import "ORKScaleSlider.h"
|
||||
#import "ORKScaleSliderView.h"
|
||||
|
||||
#import "ORKAnswerFormat_Internal.h"
|
||||
|
||||
#import "ORKAccessibilityFunctions.h"
|
||||
#import "UIView+ORKAccessibility.h"
|
||||
|
||||
|
||||
|
||||
@@ -28,10 +28,16 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@import UIKit;
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ORKLineGraphAccessibilityElement : UIAccessibilityElement
|
||||
|
||||
- (nonnull instancetype)initWithAccessibilityContainer:(nonnull UIView *)container index:(NSInteger)index maxIndex:(NSInteger)maxIndex;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -30,11 +30,15 @@
|
||||
|
||||
#import "ORKLineGraphAccessibilityElement.h"
|
||||
|
||||
|
||||
@interface ORKLineGraphAccessibilityElement()
|
||||
|
||||
@property (assign, nonatomic) NSInteger index;
|
||||
@property (assign, nonatomic) NSInteger maxIndex;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKLineGraphAccessibilityElement
|
||||
|
||||
- (nonnull instancetype)initWithAccessibilityContainer:(nonnull UIView *)container index:(NSInteger)index maxIndex:(NSInteger)maxIndex {
|
||||
|
||||
@@ -29,8 +29,7 @@
|
||||
*/
|
||||
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <ResearchKit/ResearchKit.h>
|
||||
@import UIKit;
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@@ -29,8 +29,7 @@
|
||||
*/
|
||||
|
||||
|
||||
#import <CoreLocation/CoreLocation.h>
|
||||
#import <ResearchKit/ResearchKit.h>
|
||||
@import CoreLocation;
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@@ -30,7 +30,8 @@
|
||||
|
||||
|
||||
#import "CLLocation+ORKJSONDictionary.h"
|
||||
#import "ORKHelpers.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
|
||||
|
||||
@implementation CLLocation (ORKJSONDictionary)
|
||||
@@ -45,10 +46,11 @@
|
||||
NSDate *timestamp = self.timestamp;
|
||||
CLFloor *floor = self.floor;
|
||||
|
||||
NSMutableDictionary *dictionary = [@{@"timestamp" : ORKStringFromDateISO8601(timestamp)} mutableCopy];
|
||||
NSMutableDictionary *dictionary = [@{ @"timestamp": ORKStringFromDateISO8601(timestamp) } mutableCopy];
|
||||
|
||||
if (horizAccuracy >= 0) {
|
||||
dictionary[@"coordinate"] = @{ @"latitude" : [NSDecimalNumber numberWithDouble:coord.latitude], @"longitude" : [NSDecimalNumber numberWithDouble:coord.longitude]};
|
||||
dictionary[@"coordinate"] = @{ @"latitude": [NSDecimalNumber numberWithDouble:coord.latitude],
|
||||
@"longitude": [NSDecimalNumber numberWithDouble:coord.longitude]};
|
||||
dictionary[@"horizontalAccuracy"] = [NSDecimalNumber numberWithDouble:horizAccuracy];
|
||||
}
|
||||
if (vertAccuracy >= 0) {
|
||||
|
||||
@@ -29,8 +29,7 @@
|
||||
*/
|
||||
|
||||
|
||||
#import <CoreMotion/CoreMotion.h>
|
||||
#import <ResearchKit/ResearchKit.h>
|
||||
@import CoreMotion;
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@@ -35,10 +35,10 @@
|
||||
@implementation CMAccelerometerData (ORKJSONDictionary)
|
||||
|
||||
- (NSDictionary *)ork_JSONDictionary {
|
||||
NSDictionary *dictionary = @{@"timestamp": [NSDecimalNumber numberWithDouble:self.timestamp],
|
||||
@"x" : [NSDecimalNumber numberWithDouble:self.acceleration.x],
|
||||
@"y" : [NSDecimalNumber numberWithDouble:self.acceleration.y],
|
||||
@"z" : [NSDecimalNumber numberWithDouble:self.acceleration.z]
|
||||
NSDictionary *dictionary = @{ @"timestamp": [NSDecimalNumber numberWithDouble:self.timestamp],
|
||||
@"x": [NSDecimalNumber numberWithDouble:self.acceleration.x],
|
||||
@"y": [NSDecimalNumber numberWithDouble:self.acceleration.y],
|
||||
@"z": [NSDecimalNumber numberWithDouble:self.acceleration.z]
|
||||
};
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
@@ -29,8 +29,7 @@
|
||||
*/
|
||||
|
||||
|
||||
#import <CoreMotion/CoreMotion.h>
|
||||
#import <ResearchKit/ResearchKit.h>
|
||||
@import CoreMotion;
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@@ -42,34 +42,34 @@
|
||||
CMCalibratedMagneticField field = self.magneticField;
|
||||
|
||||
NSDictionary *dictionary = @{@"timestamp": [NSDecimalNumber numberWithDouble:self.timestamp],
|
||||
@"attitude" : @{
|
||||
@"x" : [NSDecimalNumber numberWithDouble:attitude.x],
|
||||
@"y" : [NSDecimalNumber numberWithDouble:attitude.y],
|
||||
@"z" : [NSDecimalNumber numberWithDouble:attitude.z],
|
||||
@"w" : [NSDecimalNumber numberWithDouble:attitude.w]
|
||||
},
|
||||
@"rotationRate" : @{
|
||||
@"x" : [NSDecimalNumber numberWithDouble:rotationRate.x],
|
||||
@"y" : [NSDecimalNumber numberWithDouble:rotationRate.y],
|
||||
@"z" : [NSDecimalNumber numberWithDouble:rotationRate.z]
|
||||
},
|
||||
@"gravity" : @{
|
||||
@"x" : [NSDecimalNumber numberWithDouble:gravity.x],
|
||||
@"y" : [NSDecimalNumber numberWithDouble:gravity.y],
|
||||
@"z" : [NSDecimalNumber numberWithDouble:gravity.z]
|
||||
},
|
||||
@"userAcceleration" : @{
|
||||
@"x" : [NSDecimalNumber numberWithDouble:userAccel.x],
|
||||
@"y" : [NSDecimalNumber numberWithDouble:userAccel.y],
|
||||
@"z" : [NSDecimalNumber numberWithDouble:userAccel.z]
|
||||
},
|
||||
@"magneticField" : @{
|
||||
@"x" : [NSDecimalNumber numberWithDouble:field.field.x],
|
||||
@"y" : [NSDecimalNumber numberWithDouble:field.field.y],
|
||||
@"z" : [NSDecimalNumber numberWithDouble:field.field.z],
|
||||
@"accuracy" : [NSDecimalNumber numberWithDouble:field.accuracy]
|
||||
}
|
||||
};
|
||||
@"attitude": @{
|
||||
@"x": [NSDecimalNumber numberWithDouble:attitude.x],
|
||||
@"y": [NSDecimalNumber numberWithDouble:attitude.y],
|
||||
@"z": [NSDecimalNumber numberWithDouble:attitude.z],
|
||||
@"w": [NSDecimalNumber numberWithDouble:attitude.w]
|
||||
},
|
||||
@"rotationRate": @{
|
||||
@"x": [NSDecimalNumber numberWithDouble:rotationRate.x],
|
||||
@"y": [NSDecimalNumber numberWithDouble:rotationRate.y],
|
||||
@"z": [NSDecimalNumber numberWithDouble:rotationRate.z]
|
||||
},
|
||||
@"gravity": @{
|
||||
@"x": [NSDecimalNumber numberWithDouble:gravity.x],
|
||||
@"y": [NSDecimalNumber numberWithDouble:gravity.y],
|
||||
@"z": [NSDecimalNumber numberWithDouble:gravity.z]
|
||||
},
|
||||
@"userAcceleration": @{
|
||||
@"x": [NSDecimalNumber numberWithDouble:userAccel.x],
|
||||
@"y": [NSDecimalNumber numberWithDouble:userAccel.y],
|
||||
@"z": [NSDecimalNumber numberWithDouble:userAccel.z]
|
||||
},
|
||||
@"magneticField": @{
|
||||
@"x": [NSDecimalNumber numberWithDouble:field.field.x],
|
||||
@"y": [NSDecimalNumber numberWithDouble:field.field.y],
|
||||
@"z": [NSDecimalNumber numberWithDouble:field.field.z],
|
||||
@"accuracy": [NSDecimalNumber numberWithDouble:field.accuracy]
|
||||
}
|
||||
};
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,8 +29,7 @@
|
||||
*/
|
||||
|
||||
|
||||
#import <CoreMotion/CoreMotion.h>
|
||||
#import <ResearchKit/ResearchKit.h>
|
||||
@import CoreMotion;
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@@ -30,7 +30,8 @@
|
||||
|
||||
|
||||
#import "CMMotionActivity+ORKJSONDictionary.h"
|
||||
#import "ORKHelpers.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
|
||||
|
||||
static NSString *const ActivityUnknown = @"unknown";
|
||||
@@ -42,9 +43,9 @@ static NSString *const StartDateKey = @"startDate";
|
||||
static NSString *const EndDateKey = @"endDate";
|
||||
|
||||
static NSString *stringFromActivityConfidence(CMMotionActivityConfidence confidence) {
|
||||
NSDictionary *confidences = @{@(CMMotionActivityConfidenceHigh) : @"high",
|
||||
@(CMMotionActivityConfidenceMedium) : @"medium",
|
||||
@(CMMotionActivityConfidenceLow) : @"low"};
|
||||
NSDictionary *confidences = @{@(CMMotionActivityConfidenceHigh): @"high",
|
||||
@(CMMotionActivityConfidenceMedium): @"medium",
|
||||
@(CMMotionActivityConfidenceLow): @"low"};
|
||||
return confidences[@(confidence)];
|
||||
}
|
||||
|
||||
@@ -71,12 +72,13 @@ static NSArray *activityArray(CMMotionActivity *activity) {
|
||||
static NSString *const ActivityKey = @"activity";
|
||||
static NSString *const ConfidenceKey = @"confidence";
|
||||
|
||||
|
||||
@implementation CMMotionActivity (ORKJSONDictionary)
|
||||
|
||||
- (NSDictionary *)ork_JSONDictionary {
|
||||
return @{ConfidenceKey : stringFromActivityConfidence(self.confidence),
|
||||
ActivityKey : activityArray(self),
|
||||
StartDateKey : ORKStringFromDateISO8601(self.startDate)};
|
||||
return @{ConfidenceKey: stringFromActivityConfidence(self.confidence),
|
||||
ActivityKey: activityArray(self),
|
||||
StartDateKey: ORKStringFromDateISO8601(self.startDate)};
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -29,8 +29,7 @@
|
||||
*/
|
||||
|
||||
|
||||
#import <CoreMotion/CoreMotion.h>
|
||||
#import <ResearchKit/ResearchKit.h>
|
||||
@import CoreMotion;
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@@ -30,17 +30,17 @@
|
||||
|
||||
|
||||
#import "CMPedometerData+ORKJSONDictionary.h"
|
||||
#import "ORKHelpers.h"
|
||||
#import <CoreMotion/CoreMotion.h>
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
|
||||
@import CoreMotion;
|
||||
|
||||
|
||||
@implementation CMPedometerData (ORKJSONDictionary)
|
||||
|
||||
- (NSDictionary *)ork_JSONDictionary {
|
||||
NSMutableDictionary *dictionary = [@{@"startDate": ORKStringFromDateISO8601(self.startDate),
|
||||
@"endDate": ORKStringFromDateISO8601(self.endDate)
|
||||
} mutableCopy];
|
||||
for (NSString *key in @[@"numberOfSteps", @"distance", @"floorsAscended", @"floorsDescended"]) {
|
||||
NSMutableDictionary *dictionary = [@{ @"startDate": ORKStringFromDateISO8601(self.startDate), @"endDate": ORKStringFromDateISO8601(self.endDate) } mutableCopy];
|
||||
for (NSString *key in @[ @"numberOfSteps", @"distance", @"floorsAscended", @"floorsDescended" ]) {
|
||||
[dictionary setValue:[self valueForKey:key] forKey:key];
|
||||
}
|
||||
return dictionary;
|
||||
|
||||
@@ -29,8 +29,7 @@
|
||||
*/
|
||||
|
||||
|
||||
#import <HealthKit/HealthKit.h>
|
||||
#import <ResearchKit/ResearchKit.h>
|
||||
@import HealthKit;
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@@ -30,7 +30,8 @@
|
||||
|
||||
|
||||
#import "HKSample+ORKJSONDictionary.h"
|
||||
#import "ORKHelpers.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
|
||||
|
||||
static NSString *const HKSampleIdentifierKey = @"type"; // For compatibility with Health XML export
|
||||
@@ -89,7 +90,7 @@ static NSString *const HKCorrelatedObjectsKey = @"objects";
|
||||
}
|
||||
|
||||
if (options & ORKSampleIncludeSource) {
|
||||
HKSource *source = [self source];
|
||||
HKSource *source = [[self sourceRevision] source];
|
||||
if (source.name) {
|
||||
mutableDictionary[HKSourceKey] = source.name;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
*/
|
||||
|
||||
|
||||
@import UIKit;
|
||||
#import <ResearchKit/ORKRecorder.h>
|
||||
|
||||
|
||||
@@ -58,7 +59,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@return An initialized accelerometer recorder.
|
||||
*/
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifer
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier
|
||||
frequency:(double)frequency
|
||||
step:(nullable ORKStep *)step
|
||||
outputDirectory:(nullable NSURL *)outputDirectory;
|
||||
|
||||
@@ -30,12 +30,15 @@
|
||||
|
||||
|
||||
#import "ORKAccelerometerRecorder.h"
|
||||
|
||||
#import "ORKDataLogger.h"
|
||||
#import "CMAccelerometerData+ORKJSONDictionary.h"
|
||||
#import <CoreMotion/CoreMotion.h>
|
||||
|
||||
#import "ORKRecorder_Internal.h"
|
||||
#import "ORKRecorder_Private.h"
|
||||
#import "ORKHelpers.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
#import "CMAccelerometerData+ORKJSONDictionary.h"
|
||||
|
||||
@import CoreMotion;
|
||||
|
||||
|
||||
@interface ORKAccelerometerRecorder () {
|
||||
@@ -70,7 +73,7 @@
|
||||
}
|
||||
|
||||
- (void)setFrequency:(double)frequency {
|
||||
if (frequency <=0) {
|
||||
if (frequency <= 0) {
|
||||
_frequency = 1;
|
||||
} else {
|
||||
_frequency = frequency;
|
||||
@@ -87,10 +90,10 @@
|
||||
self.motionManager = [self createMotionManager];
|
||||
|
||||
if (!_logger) {
|
||||
NSError *err = nil;
|
||||
_logger = [self makeJSONDataLoggerWithError:&err];
|
||||
NSError *error = nil;
|
||||
_logger = [self makeJSONDataLoggerWithError:&error];
|
||||
if (!_logger) {
|
||||
[self finishRecordingWithError:err];
|
||||
[self finishRecordingWithError:error];
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -98,7 +101,7 @@
|
||||
if (!self.motionManager || !self.motionManager.accelerometerAvailable) {
|
||||
NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain
|
||||
code:NSFeatureUnsupportedError
|
||||
userInfo:@{@"recorder" : self}];
|
||||
userInfo:@{@"recorder": self}];
|
||||
[self finishRecordingWithError:error];
|
||||
return;
|
||||
}
|
||||
@@ -124,7 +127,7 @@
|
||||
}
|
||||
|
||||
- (NSDictionary *)userInfo {
|
||||
return @{ @"frequency" : @(self.frequency) };
|
||||
return @{ @"frequency": @(self.frequency) };
|
||||
}
|
||||
|
||||
- (void)stop {
|
||||
|
||||
@@ -29,10 +29,10 @@
|
||||
*/
|
||||
|
||||
|
||||
#import <ResearchKit/ORKDefines.h>
|
||||
@import UIKit;
|
||||
@import HealthKit;
|
||||
#import <ResearchKit/ORKStep.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <HealthKit/HealthKit.h>
|
||||
|
||||
|
||||
@class ORKRecorderConfiguration;
|
||||
|
||||
@@ -103,6 +103,17 @@ automatically navigates forward when the timer expires.
|
||||
*/
|
||||
@property (nonatomic) BOOL shouldSpeakCountDown;
|
||||
|
||||
/**
|
||||
A Boolean value indicating whether to speak the halfway point in the count down of the
|
||||
duration of a timed step.
|
||||
|
||||
When the value of this property is `YES`, `AVSpeechSynthesizer` is used to synthesize the countdown. Note that this property is ignored if VoiceOver is enabled.
|
||||
|
||||
The default value of this property is `NO`.
|
||||
*/
|
||||
@property (nonatomic) BOOL shouldSpeakRemainingTimeAtHalfway;
|
||||
|
||||
|
||||
/**
|
||||
A Boolean value indicating whether to start the count down timer automatically when the step starts, or
|
||||
require the user to take some explicit action to start the step, such as tapping a button.
|
||||
@@ -171,6 +182,14 @@ The default value of this property is `NO`.
|
||||
*/
|
||||
@property (nonatomic, copy, nullable) NSString *spokenInstruction;
|
||||
|
||||
/**
|
||||
Localized text that represents an instructional voice prompt for when the step finishes.
|
||||
|
||||
Instructional speech begins when the step finishes. If VoiceOver is active,
|
||||
the instruction is spoken by VoiceOver.
|
||||
*/
|
||||
@property (nonatomic, copy, nullable) NSString *finishedSpokenInstruction;
|
||||
|
||||
/**
|
||||
An image to be displayed below the instructions for the step.
|
||||
|
||||
@@ -195,18 +214,6 @@ The default value of this property is `NO`.
|
||||
*/
|
||||
@property (nonatomic, copy, nullable) NSArray<ORKRecorderConfiguration *> *recorderConfigurations;
|
||||
|
||||
/**
|
||||
The set of HealthKit types the step requests for reading. (read-only)
|
||||
|
||||
The task view controller uses this set of types when constructing a list of
|
||||
all the HealthKit types required by all the steps in a task, so that it can
|
||||
present the HealthKit access dialog just once during that task.
|
||||
|
||||
By default, the property scans the recorders and collates the HealthKit
|
||||
types the recorders require. Subclasses may override this implementation.
|
||||
*/
|
||||
@property (nonatomic, readonly, nullable) NSSet<HKObjectType *> *requestedHealthKitTypesForReading;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -30,12 +30,15 @@
|
||||
|
||||
|
||||
#import "ORKActiveStep.h"
|
||||
#import "ORKHelpers.h"
|
||||
#import "ORKStep_Private.h"
|
||||
#import "ORKActiveStep_Internal.h"
|
||||
|
||||
#import "ORKActiveStepViewController.h"
|
||||
#import "ORKRecorder_Private.h"
|
||||
|
||||
#import "ORKStep_Private.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
|
||||
|
||||
@implementation ORKActiveStep
|
||||
|
||||
@@ -62,7 +65,9 @@
|
||||
}
|
||||
|
||||
- (BOOL)hasVoice {
|
||||
return (_spokenInstruction != nil && _spokenInstruction.length > 0);
|
||||
BOOL hasSpokenInstruction = (_spokenInstruction != nil && _spokenInstruction.length > 0);
|
||||
BOOL hasFinishedSpokenInstruction = (_finishedSpokenInstruction != nil && _finishedSpokenInstruction.length > 0);
|
||||
return (hasSpokenInstruction || hasFinishedSpokenInstruction);
|
||||
}
|
||||
|
||||
- (BOOL)isRestorable {
|
||||
@@ -86,6 +91,7 @@
|
||||
step.stepDuration = self.stepDuration;
|
||||
step.shouldStartTimerAutomatically = self.shouldStartTimerAutomatically;
|
||||
step.shouldSpeakCountDown = self.shouldSpeakCountDown;
|
||||
step.shouldSpeakRemainingTimeAtHalfway = self.shouldSpeakRemainingTimeAtHalfway;
|
||||
step.shouldShowDefaultTimer = self.shouldShowDefaultTimer;
|
||||
step.shouldPlaySoundOnStart = self.shouldPlaySoundOnStart;
|
||||
step.shouldPlaySoundOnFinish = self.shouldPlaySoundOnFinish;
|
||||
@@ -94,6 +100,7 @@
|
||||
step.shouldUseNextAsSkipButton = self.shouldUseNextAsSkipButton;
|
||||
step.shouldContinueOnFinish = self.shouldContinueOnFinish;
|
||||
step.spokenInstruction = self.spokenInstruction;
|
||||
step.finishedSpokenInstruction = self.finishedSpokenInstruction;
|
||||
step.recorderConfigurations = [self.recorderConfigurations copy];
|
||||
step.image = self.image;
|
||||
return step;
|
||||
@@ -105,6 +112,7 @@
|
||||
ORK_DECODE_DOUBLE(aDecoder, stepDuration);
|
||||
ORK_DECODE_BOOL(aDecoder, shouldStartTimerAutomatically);
|
||||
ORK_DECODE_BOOL(aDecoder, shouldSpeakCountDown);
|
||||
ORK_DECODE_BOOL(aDecoder, shouldSpeakRemainingTimeAtHalfway);
|
||||
ORK_DECODE_BOOL(aDecoder, shouldShowDefaultTimer);
|
||||
ORK_DECODE_BOOL(aDecoder, shouldPlaySoundOnStart);
|
||||
ORK_DECODE_BOOL(aDecoder, shouldPlaySoundOnFinish);
|
||||
@@ -113,6 +121,7 @@
|
||||
ORK_DECODE_BOOL(aDecoder, shouldUseNextAsSkipButton);
|
||||
ORK_DECODE_BOOL(aDecoder, shouldContinueOnFinish);
|
||||
ORK_DECODE_OBJ_CLASS(aDecoder, spokenInstruction, NSString);
|
||||
ORK_DECODE_OBJ_CLASS(aDecoder, finishedSpokenInstruction, NSString);
|
||||
ORK_DECODE_IMAGE(aDecoder, image);
|
||||
ORK_DECODE_OBJ_ARRAY(aDecoder, recorderConfigurations, ORKRecorderConfiguration);
|
||||
}
|
||||
@@ -124,6 +133,7 @@
|
||||
ORK_ENCODE_DOUBLE(aCoder, stepDuration);
|
||||
ORK_ENCODE_BOOL(aCoder, shouldStartTimerAutomatically);
|
||||
ORK_ENCODE_BOOL(aCoder, shouldSpeakCountDown);
|
||||
ORK_ENCODE_BOOL(aCoder, shouldSpeakRemainingTimeAtHalfway);
|
||||
ORK_ENCODE_BOOL(aCoder, shouldShowDefaultTimer);
|
||||
ORK_ENCODE_BOOL(aCoder, shouldPlaySoundOnStart);
|
||||
ORK_ENCODE_BOOL(aCoder, shouldPlaySoundOnFinish);
|
||||
@@ -133,6 +143,7 @@
|
||||
ORK_ENCODE_BOOL(aCoder, shouldContinueOnFinish);
|
||||
ORK_ENCODE_IMAGE(aCoder, image);
|
||||
ORK_ENCODE_OBJ(aCoder, spokenInstruction);
|
||||
ORK_ENCODE_OBJ(aCoder, finishedSpokenInstruction);
|
||||
ORK_ENCODE_OBJ(aCoder, recorderConfigurations);
|
||||
}
|
||||
|
||||
@@ -142,12 +153,14 @@
|
||||
__typeof(self) castObject = object;
|
||||
return (isParentSame &&
|
||||
ORKEqualObjects(self.spokenInstruction, castObject.spokenInstruction) &&
|
||||
ORKEqualObjects(self.finishedSpokenInstruction, castObject.finishedSpokenInstruction) &&
|
||||
ORKEqualObjects(self.recorderConfigurations, castObject.recorderConfigurations) &&
|
||||
ORKEqualObjects(self.image, castObject.image) &&
|
||||
(self.stepDuration == castObject.stepDuration) &&
|
||||
(self.shouldShowDefaultTimer == castObject.shouldShowDefaultTimer) &&
|
||||
(self.shouldStartTimerAutomatically == castObject.shouldStartTimerAutomatically) &&
|
||||
(self.shouldSpeakCountDown == castObject.shouldSpeakCountDown) &&
|
||||
(self.shouldSpeakRemainingTimeAtHalfway == castObject.shouldSpeakRemainingTimeAtHalfway) &&
|
||||
(self.shouldPlaySoundOnStart == castObject.shouldPlaySoundOnStart) &&
|
||||
(self.shouldPlaySoundOnFinish == castObject.shouldPlaySoundOnFinish) &&
|
||||
(self.shouldVibrateOnStart == castObject.shouldVibrateOnStart) &&
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
*/
|
||||
|
||||
|
||||
#import <ResearchKit/ResearchKit_Private.h>
|
||||
@import UIKit;
|
||||
#import "ORKLabel.h"
|
||||
|
||||
|
||||
|
||||
@@ -30,10 +30,12 @@
|
||||
|
||||
|
||||
#import "ORKActiveStepQuantityView.h"
|
||||
#import "ORKHelpers.h"
|
||||
#import "ORKSkin.h"
|
||||
#import "ORKTintedImageView.h"
|
||||
|
||||
#import "ORKSubheadlineLabel.h"
|
||||
#import "ORKTintedImageView.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
#import "ORKSkin.h"
|
||||
|
||||
|
||||
@implementation ORKQuantityLabel
|
||||
|
||||
@@ -29,8 +29,7 @@
|
||||
*/
|
||||
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <ResearchKit/ResearchKit.h>
|
||||
@import Foundation;
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@@ -30,9 +30,12 @@
|
||||
|
||||
|
||||
#import "ORKActiveStepTimer.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
|
||||
@import UIKit;
|
||||
#include <mach/mach.h>
|
||||
#include <mach/mach_time.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
|
||||
static NSTimeInterval timeIntervalFromMachTime(uint64_t delta) {
|
||||
@@ -44,6 +47,7 @@ static NSTimeInterval timeIntervalFromMachTime(uint64_t delta) {
|
||||
return elapsedNano * 1.0 / NSEC_PER_SEC;
|
||||
}
|
||||
|
||||
|
||||
@implementation ORKActiveStepTimer {
|
||||
uint64_t _startTime;
|
||||
NSTimeInterval _preExistingRuntime;
|
||||
@@ -197,9 +201,9 @@ static NSTimeInterval timeIntervalFromMachTime(uint64_t delta) {
|
||||
assert(0);
|
||||
return;
|
||||
}
|
||||
__weak typeof(self) weakSelf = self;
|
||||
ORKWeakTypeOf(self) weakSelf = self;
|
||||
dispatch_source_set_event_handler(_timer, ^{
|
||||
typeof(self) strongSelf = weakSelf;
|
||||
ORKStrongTypeOf(self) strongSelf = weakSelf;
|
||||
[strongSelf hiqueue_event];
|
||||
});
|
||||
|
||||
|
||||
@@ -29,18 +29,20 @@
|
||||
*/
|
||||
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <ResearchKit/ResearchKit_Private.h>
|
||||
@import UIKit;
|
||||
#import "ORKCustomStepView_Internal.h"
|
||||
#import "ORKCountdownLabel.h"
|
||||
#import "ORKTextButton.h"
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class ORKActiveStep;
|
||||
@class ORKCountdownLabel;
|
||||
@class ORKTextButton;
|
||||
|
||||
@interface ORKActiveStepTimerView : ORKActiveStepCustomView
|
||||
|
||||
@property (nonatomic, strong, nullable) ORKCountdownLabel *countDownLabel;
|
||||
|
||||
@property (nonatomic, strong, nullable) ORKTextButton *startTimerButton;
|
||||
|
||||
@property (nonatomic, strong, nullable) ORKActiveStep *step;
|
||||
|
||||
@@ -30,17 +30,22 @@
|
||||
|
||||
|
||||
#import "ORKActiveStepTimerView.h"
|
||||
|
||||
#import "ORKActiveStepTimer.h"
|
||||
#import "ORKHelpers.h"
|
||||
#import "ORKSkin.h"
|
||||
#import "ORKActiveStepViewController_Internal.h"
|
||||
#import "ORKVoiceEngine.h"
|
||||
#import "ORKCountdownLabel.h"
|
||||
#import "ORKSurveyAnswerCellForText.h"
|
||||
#import "ORKSurveyAnswerCellForNumber.h"
|
||||
#import "ORKActiveStep_Internal.h"
|
||||
#import "ORKTextButton.h"
|
||||
#import "ORKVoiceEngine.h"
|
||||
|
||||
#import "ORKActiveStepViewController_Internal.h"
|
||||
#import "ORKStepViewController_Internal.h"
|
||||
|
||||
#import "ORKActiveStep_Internal.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
#import "ORKSkin.h"
|
||||
|
||||
|
||||
@implementation ORKActiveStepTimerView {
|
||||
BOOL _started;
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
*/
|
||||
|
||||
|
||||
@import UIKit;
|
||||
#import <ResearchKit/ORKStepViewController.h>
|
||||
#import <ResearchKit/ORKRecorder.h>
|
||||
|
||||
|
||||
@@ -30,23 +30,27 @@
|
||||
|
||||
|
||||
#import "ORKActiveStepViewController.h"
|
||||
#import "ORKVoiceEngine.h"
|
||||
#import "ORKSkin.h"
|
||||
#import "ORKHelpers.h"
|
||||
#import "ORKActiveStep.h"
|
||||
#import "ORKTask.h"
|
||||
#import "ORKTaskViewController.h"
|
||||
#import "ORKVerticalContainerView.h"
|
||||
#import "ORKStepViewController_Internal.h"
|
||||
#import "ORKActiveStepViewController_Internal.h"
|
||||
#import "ORKActiveStep_Internal.h"
|
||||
#import "ORKRecorder_Internal.h"
|
||||
#import "ORKTaskViewController_Internal.h"
|
||||
#import "ORKActiveStepTimerView.h"
|
||||
|
||||
#import "ORKActiveStepTimer.h"
|
||||
#import "ORKAccessibility.h"
|
||||
#import "ORKStepHeaderView_Internal.h"
|
||||
#import "ORKActiveStepTimerView.h"
|
||||
#import "ORKActiveStepView.h"
|
||||
#import "ORKNavigationContainerView.h"
|
||||
#import "ORKStepHeaderView_Internal.h"
|
||||
#import "ORKVerticalContainerView.h"
|
||||
#import "ORKVoiceEngine.h"
|
||||
|
||||
#import "ORKActiveStepViewController_Internal.h"
|
||||
#import "ORKStepViewController_Internal.h"
|
||||
#import "ORKTaskViewController_Internal.h"
|
||||
#import "ORKRecorder_Internal.h"
|
||||
|
||||
#import "ORKActiveStep_Internal.h"
|
||||
#import "ORKResult.h"
|
||||
#import "ORKTask.h"
|
||||
|
||||
#import "ORKAccessibility.h"
|
||||
#import "ORKHelpers_Internal.h"
|
||||
#import "ORKSkin.h"
|
||||
|
||||
|
||||
@interface ORKActiveStepViewController () {
|
||||
@@ -57,6 +61,7 @@
|
||||
|
||||
SystemSoundID _alertSound;
|
||||
NSURL *_alertSoundURL;
|
||||
BOOL _hasSpokenHalfwayCountdown;
|
||||
}
|
||||
|
||||
@property (nonatomic, strong) NSArray *recorders;
|
||||
@@ -92,6 +97,7 @@
|
||||
}
|
||||
|
||||
- (ORKActiveStep *)activeStep {
|
||||
NSAssert(self.step == nil || [self.step isKindOfClass:[ORKActiveStep class]], @"Step should be a subclass of an ORKActiveStep");
|
||||
return (ORKActiveStep *)self.step;
|
||||
}
|
||||
|
||||
@@ -205,7 +211,9 @@
|
||||
|
||||
- (ORKStepResult *)result {
|
||||
ORKStepResult *sResult = [super result];
|
||||
sResult.results = _recorderResults;
|
||||
if (_recorderResults) {
|
||||
sResult.results = [sResult.results arrayByAddingObjectsFromArray:_recorderResults] ? : _recorderResults;
|
||||
}
|
||||
return sResult;
|
||||
}
|
||||
|
||||
@@ -362,6 +370,9 @@
|
||||
if (self.activeStep.shouldVibrateOnFinish) {
|
||||
AudioServicesPlayAlertSound(kSystemSoundID_Vibrate);
|
||||
}
|
||||
if (self.activeStep.hasVoice && self.activeStep.finishedSpokenInstruction) {
|
||||
[[ORKVoiceEngine sharedVoiceEngine] speakText:self.activeStep.finishedSpokenInstruction];
|
||||
}
|
||||
if (!self.activeStep.startsFinished) {
|
||||
if (self.activeStep.shouldContinueOnFinish) {
|
||||
[self goForward];
|
||||
@@ -390,12 +401,12 @@
|
||||
NSTimeInterval stepDuration = self.activeStep.stepDuration;
|
||||
|
||||
if (stepDuration > 0) {
|
||||
__weak typeof(self) weakSelf = self;
|
||||
ORKWeakTypeOf(self) weakSelf = self;
|
||||
_activeStepTimer = [[ORKActiveStepTimer alloc] initWithDuration:stepDuration
|
||||
interval:_timerUpdateInterval
|
||||
runtime:0
|
||||
handler:^(ORKActiveStepTimer *timer, BOOL finished) {
|
||||
typeof(self) strongSelf = weakSelf;
|
||||
ORKStrongTypeOf(self) strongSelf = weakSelf;
|
||||
[strongSelf countDownTimerFired:timer finished:finished];
|
||||
}];
|
||||
[_activeStepTimer resume];
|
||||
@@ -410,6 +421,7 @@
|
||||
ORKActiveStepCustomView *customView = _activeStepView.activeCustomView;
|
||||
[customView updateDisplay:self];
|
||||
|
||||
|
||||
ORKVoiceEngine *voice = [ORKVoiceEngine sharedVoiceEngine];
|
||||
|
||||
if (!finished && self.activeStep.shouldSpeakCountDown) {
|
||||
@@ -423,6 +435,13 @@
|
||||
[voice speakInt:countDownValue];
|
||||
}
|
||||
}
|
||||
|
||||
BOOL isHalfway = !_hasSpokenHalfwayCountdown && timer.runtime > timer.duration / 2.0;
|
||||
if (!finished && self.activeStep.shouldSpeakRemainingTimeAtHalfway && !UIAccessibilityIsVoiceOverRunning() && isHalfway) {
|
||||
_hasSpokenHalfwayCountdown = YES;
|
||||
NSString *text = [NSString localizedStringWithFormat:ORKLocalizedString(@"COUNTDOWN_SPOKEN_REMAINING_%@", nil), @(countDownValue)];
|
||||
[voice speakText:text];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)timerActive {
|
||||
@@ -450,7 +469,7 @@
|
||||
|
||||
- (void)recorder:(ORKRecorder *)recorder didFailWithError:(NSError *)error {
|
||||
if (error) {
|
||||
STRONGTYPE(self.delegate) strongDelegate = self.delegate;
|
||||
ORKStrongTypeOf(self.delegate) strongDelegate = self.delegate;
|
||||
if ([strongDelegate respondsToSelector:@selector(stepViewController:recorder:didFailWithError:)]) {
|
||||
[strongDelegate stepViewController:self recorder:recorder didFailWithError:error];
|
||||
}
|
||||
|
||||
@@ -30,11 +30,11 @@
|
||||
|
||||
|
||||
#import "ORKActiveStepViewController.h"
|
||||
#import "ORKActiveStepTimer.h"
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class ORKActiveStepTimer;
|
||||
@class ORKActiveStepView;
|
||||
|
||||
@interface ORKActiveStepViewController ()
|
||||
|
||||
@@ -29,14 +29,14 @@
|
||||
*/
|
||||
|
||||
|
||||
#import <ResearchKit/ORKActiveStep.h>
|
||||
#import "ORKActiveStep.h"
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ORKActiveStep ()
|
||||
|
||||
/**
|
||||
Convenience methods.
|
||||
*/
|
||||
// Convenience methods.
|
||||
- (BOOL)startsFinished;
|
||||
- (BOOL)hasCountDown;
|
||||
- (BOOL)hasTitle;
|
||||
@@ -44,3 +44,5 @@
|
||||
- (BOOL)hasVoice;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
*/
|
||||
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
@import UIKit;
|
||||
#import "ORKCustomStepView_Internal.h"
|
||||
|
||||
|
||||
|
||||
@@ -30,11 +30,13 @@
|
||||
|
||||
|
||||
#import "ORKAudioContentView.h"
|
||||
#import "ORKHelpers.h"
|
||||
#import "ORKSkin.h"
|
||||
#import "ORKLabel.h"
|
||||
|
||||
#import "ORKHeadlineLabel.h"
|
||||
#import "ORKLabel.h"
|
||||
|
||||
#import "ORKAccessibility.h"
|
||||
#import "ORKHelpers_Internal.h"
|
||||
#import "ORKSkin.h"
|
||||
|
||||
|
||||
// The central blue region.
|
||||
@@ -59,6 +61,7 @@ static const CGFloat GraphViewRedZoneHeight = 25;
|
||||
static const CGFloat ValueLineWidth = 4.5;
|
||||
static const CGFloat ValueLineMargin = 1.5;
|
||||
|
||||
|
||||
@implementation ORKAudioGraphView
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
@@ -67,7 +70,7 @@ static const CGFloat ValueLineMargin = 1.5;
|
||||
[self setUpConstraints];
|
||||
|
||||
#if TARGET_IPHONE_SIMULATOR
|
||||
_values = @[@(0.2),@(0.6),@(0.55), @(0.1), @(0.75), @(0.7)];
|
||||
_values = @[ @(0.2), @(0.6), @(0.55), @(0.1), @(0.75), @(0.7) ];
|
||||
#endif
|
||||
}
|
||||
return self;
|
||||
@@ -328,17 +331,16 @@ static const CGFloat ValueLineMargin = 1.5;
|
||||
}
|
||||
|
||||
- (void)updateTimerLabel {
|
||||
static NSDateComponentsFormatter *_formatter = nil;
|
||||
static NSDateComponentsFormatter *formatter = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
NSDateComponentsFormatter *formatter = [NSDateComponentsFormatter new];
|
||||
formatter.unitsStyle = NSDateComponentsFormatterUnitsStylePositional;
|
||||
formatter.zeroFormattingBehavior = NSDateComponentsFormatterZeroFormattingBehaviorPad;
|
||||
formatter.allowedUnits = NSCalendarUnitMinute | NSCalendarUnitSecond;
|
||||
_formatter = formatter;
|
||||
});
|
||||
|
||||
NSString *string = [_formatter stringFromTimeInterval:MAX(round(_timeLeft),0)];
|
||||
NSString *string = [formatter stringFromTimeInterval:MAX(round(_timeLeft),0)];
|
||||
_timerLabel.text = string;
|
||||
_timerLabel.hidden = (string == nil);
|
||||
}
|
||||
@@ -351,6 +353,10 @@ static const CGFloat ValueLineMargin = 1.5;
|
||||
- (void)updateAlertLabelHidden {
|
||||
NSNumber *sample = _samples.lastObject;
|
||||
BOOL show = (!_finished && (sample.doubleValue > _alertThreshold)) || _failed;
|
||||
|
||||
if (_alertLabel.hidden && show) {
|
||||
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, _alertLabel.text);
|
||||
}
|
||||
_alertLabel.hidden = !show;
|
||||
}
|
||||
|
||||
@@ -384,11 +390,9 @@ static const CGFloat ValueLineMargin = 1.5;
|
||||
}
|
||||
|
||||
- (NSString *)accessibilityLabel {
|
||||
if (_alertLabel.isHidden) {
|
||||
return _timerLabel.accessibilityLabel;
|
||||
}
|
||||
|
||||
return ORKAccessibilityStringForVariables(_timerLabel.accessibilityLabel, _alertLabel.accessibilityLabel);
|
||||
NSString *timerAxString = _timerLabel.isHidden ? nil : _timerLabel.accessibilityLabel;
|
||||
NSString *alertAxString = _alertLabel.isHidden ? nil : _alertLabel.accessibilityLabel;
|
||||
return ORKAccessibilityStringForVariables(ORKLocalizedString(@"AX_AUDIO_BAR_GRAPH", nil), timerAxString, alertAxString);
|
||||
}
|
||||
|
||||
- (UIAccessibilityTraits)accessibilityTraits {
|
||||
|
||||
@@ -49,8 +49,11 @@
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
#import <ResearchKit/ORKRecorder.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
@import UIKit;
|
||||
@import AVFoundation;
|
||||
#import "ORKTypes.h"
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
|
||||
@@ -49,15 +49,15 @@
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKAudioGenerator.h"
|
||||
|
||||
@import AudioToolbox;
|
||||
|
||||
@interface ORKAudioGenerator () {
|
||||
@public
|
||||
AudioComponentInstance _toneUnit;
|
||||
|
||||
@public
|
||||
@interface ORKAudioGenerator () {
|
||||
@public
|
||||
AudioComponentInstance _toneUnit;
|
||||
double _frequency;
|
||||
double _theta;
|
||||
ORKAudioChannel _activeChannel;
|
||||
@@ -148,15 +148,15 @@ OSStatus ORKAudioGeneratorRenderTone(void *inRefCon,
|
||||
|
||||
- (void)applicationDidBecomeActive:(NSNotification *)notification {
|
||||
if (_toneUnit) {
|
||||
__unused OSErr err = AudioOutputUnitStart(_toneUnit);
|
||||
NSAssert1(err == noErr, @"Error starting unit: %hd", err);
|
||||
__unused OSErr error = AudioOutputUnitStart(_toneUnit);
|
||||
NSAssert1(error == noErr, @"Error starting unit: %hd", error);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applicationWillResignActive:(NSNotification *)notification {
|
||||
if (_toneUnit) {
|
||||
__unused OSErr err = AudioOutputUnitStop(_toneUnit);
|
||||
NSAssert1(err == noErr, @"Error stopping unit: %hd", err);
|
||||
__unused OSErr error = AudioOutputUnitStop(_toneUnit);
|
||||
NSAssert1(error == noErr, @"Error stopping unit: %hd", error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,12 +194,12 @@ OSStatus ORKAudioGeneratorRenderTone(void *inRefCon,
|
||||
[self createToneUnit];
|
||||
|
||||
// Stop changing parameters on the unit
|
||||
OSErr err = AudioUnitInitialize(_toneUnit);
|
||||
NSAssert1(err == noErr, @"Error initializing unit: %hd", err);
|
||||
OSErr error = AudioUnitInitialize(_toneUnit);
|
||||
NSAssert1(error == noErr, @"Error initializing unit: %hd", error);
|
||||
|
||||
// Start playback
|
||||
err = AudioOutputUnitStart(_toneUnit);
|
||||
NSAssert1(err == noErr, @"Error starting unit: %hd", err);
|
||||
error = AudioOutputUnitStart(_toneUnit);
|
||||
NSAssert1(error == noErr, @"Error starting unit: %hd", error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,20 +240,20 @@ OSStatus ORKAudioGeneratorRenderTone(void *inRefCon,
|
||||
NSAssert(defaultOutput, @"Can't find default output");
|
||||
|
||||
// Create a new unit based on this that we'll use for output
|
||||
OSErr err = AudioComponentInstanceNew(defaultOutput, &_toneUnit);
|
||||
NSAssert1(_toneUnit, @"Error creating unit: %hd", err);
|
||||
OSErr error = AudioComponentInstanceNew(defaultOutput, &_toneUnit);
|
||||
NSAssert1(_toneUnit, @"Error creating unit: %hd", error);
|
||||
|
||||
// Set our tone rendering function on the unit
|
||||
AURenderCallbackStruct input;
|
||||
input.inputProc = ORKAudioGeneratorRenderTone;
|
||||
input.inputProcRefCon = (__bridge void *)(self);
|
||||
err = AudioUnitSetProperty(_toneUnit,
|
||||
error = AudioUnitSetProperty(_toneUnit,
|
||||
kAudioUnitProperty_SetRenderCallback,
|
||||
kAudioUnitScope_Input,
|
||||
0,
|
||||
&input,
|
||||
sizeof(input));
|
||||
NSAssert1(err == noErr, @"Error setting callback: %hd", err);
|
||||
NSAssert1(error == noErr, @"Error setting callback: %hd", error);
|
||||
|
||||
// Set the format to 32 bit, single channel, floating point, linear PCM
|
||||
const int four_bytes_per_float = 4;
|
||||
@@ -267,13 +267,13 @@ OSStatus ORKAudioGeneratorRenderTone(void *inRefCon,
|
||||
streamFormat.mBytesPerFrame = four_bytes_per_float;
|
||||
streamFormat.mChannelsPerFrame = 2;
|
||||
streamFormat.mBitsPerChannel = four_bytes_per_float * eight_bits_per_byte;
|
||||
err = AudioUnitSetProperty (_toneUnit,
|
||||
error = AudioUnitSetProperty (_toneUnit,
|
||||
kAudioUnitProperty_StreamFormat,
|
||||
kAudioUnitScope_Input,
|
||||
0,
|
||||
&streamFormat,
|
||||
sizeof(AudioStreamBasicDescription));
|
||||
NSAssert1(err == noErr, @"Error setting stream format: %hd", err);
|
||||
NSAssert1(error == noErr, @"Error setting stream format: %hd", error);
|
||||
}
|
||||
|
||||
- (void)handleInterruption:(id)sender {
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
Copyright (c) 2016, Sage Bionetworks
|
||||
Copyright (c) 2016, Apple Inc.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import <ResearchKit/ORKStepNavigationRule.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKAudioLevelNavigationRule : ORKStepNavigationRule
|
||||
|
||||
/*
|
||||
The `init` and `new` methods are unavailable.
|
||||
|
||||
`ORKStepNavigationRule` classes should be initialized with custom designated initializers on each
|
||||
subclass.
|
||||
*/
|
||||
+ (instancetype)new NS_UNAVAILABLE;
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
/**
|
||||
Returns an initialized direct-step navigation rule using the specified destination step identifier.
|
||||
|
||||
@param audioLevelStepIdentifier The identifier of the step with the audio file to check.
|
||||
@param destinationStepIdentifier The identifier of the destination step if audio test passes.
|
||||
@param recordingSettings Use key AVNumberOfChannelsKey to sepcify the number of recording channels.
|
||||
@return A audio level step navigation rule.
|
||||
*/
|
||||
- (instancetype)initWithAudioLevelStepIdentifier:(NSString *)audioLevelStepIdentifier
|
||||
destinationStepIdentifier:(NSString *)destinationStepIdentifier
|
||||
recordingSettings:(NSDictionary *)recordingSettings NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/**
|
||||
Returns a new direct-step navigation rule initialized from data in a given unarchiver.
|
||||
|
||||
@param aDecoder The coder from which to initialize the step navigation rule.
|
||||
|
||||
@return A new direct-step navigation rule.
|
||||
*/
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@property (nonatomic, copy, readonly) NSString *audioLevelStepIdentifier;
|
||||
@property (nonatomic, copy, readonly) NSString *destinationStepIdentifier;
|
||||
@property (nonatomic, copy, readonly) NSDictionary *recordingSettings;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
Copyright (c) 2016, Sage Bionetworks
|
||||
Copyright (c) 2016, Apple Inc.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKAudioLevelNavigationRule.h"
|
||||
|
||||
#import "ORKResult.h"
|
||||
#import "ORKResultPredicate.h"
|
||||
#import "ORKStepNavigationRule_Internal.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
|
||||
Float32 const VolumeThreshold = 0.45;
|
||||
UInt16 const LinearPCMBitDepth = 16;
|
||||
Float32 const MaxAmplitude = 32767.0;
|
||||
Float32 const VolumeClamp = 60.0;
|
||||
|
||||
|
||||
@interface ORKAudioLevelNavigationRule ()
|
||||
|
||||
@property (nonatomic, copy, readwrite) NSString *audioLevelStepIdentifier;
|
||||
@property (nonatomic, copy, readwrite) NSString *destinationStepIdentifier;
|
||||
@property (nonatomic, copy, readwrite) NSDictionary *recordingSettings;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKAudioLevelNavigationRule
|
||||
|
||||
+ (instancetype)new {
|
||||
ORKThrowMethodUnavailableException();
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
ORKThrowMethodUnavailableException();
|
||||
}
|
||||
|
||||
- (instancetype)initWithAudioLevelStepIdentifier:(NSString *)audioLevelStepIdentifier
|
||||
destinationStepIdentifier:(NSString *)destinationStepIdentifier
|
||||
recordingSettings:(NSDictionary *)recordingSettings
|
||||
{
|
||||
ORKThrowInvalidArgumentExceptionIfNil(audioLevelStepIdentifier);
|
||||
ORKThrowInvalidArgumentExceptionIfNil(destinationStepIdentifier);
|
||||
ORKThrowInvalidArgumentExceptionIfNil(recordingSettings);
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_audioLevelStepIdentifier = [audioLevelStepIdentifier copy];
|
||||
_destinationStepIdentifier = [destinationStepIdentifier copy];
|
||||
_recordingSettings = [recordingSettings copy];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark NSSecureCoding
|
||||
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [super initWithCoder:aDecoder];
|
||||
if (self) {
|
||||
ORK_DECODE_OBJ_CLASS(aDecoder, audioLevelStepIdentifier, NSString);
|
||||
ORK_DECODE_OBJ_CLASS(aDecoder, destinationStepIdentifier, NSString);
|
||||
ORK_DECODE_OBJ_CLASS(aDecoder, recordingSettings, NSDictionary);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
[super encodeWithCoder:aCoder];
|
||||
ORK_ENCODE_OBJ(aCoder, audioLevelStepIdentifier);
|
||||
ORK_ENCODE_OBJ(aCoder, destinationStepIdentifier);
|
||||
ORK_ENCODE_OBJ(aCoder, recordingSettings);
|
||||
}
|
||||
|
||||
#pragma mark NSCopying
|
||||
|
||||
- (instancetype)copyWithZone:(NSZone *)zone {
|
||||
typeof(self) rule = [[[self class] allocWithZone:zone] initWithAudioLevelStepIdentifier:self.audioLevelStepIdentifier destinationStepIdentifier:self.destinationStepIdentifier recordingSettings:self.recordingSettings];
|
||||
return rule;
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object {
|
||||
BOOL isParentSame = [super isEqual:object];
|
||||
__typeof(self) castObject = object;
|
||||
return (isParentSame
|
||||
&& ORKEqualObjects(self.audioLevelStepIdentifier, castObject.audioLevelStepIdentifier)
|
||||
&& ORKEqualObjects(self.destinationStepIdentifier, castObject.destinationStepIdentifier)
|
||||
&& ORKEqualObjects(self.recordingSettings, castObject.recordingSettings));
|
||||
}
|
||||
|
||||
- (NSUInteger)hash {
|
||||
return _audioLevelStepIdentifier.hash ^ _destinationStepIdentifier.hash ^ _recordingSettings.hash;
|
||||
}
|
||||
|
||||
#pragma mark - Required overrides
|
||||
|
||||
- (NSString *)identifierForDestinationStepWithTaskResult:(ORKTaskResult *)taskResult {
|
||||
|
||||
// Get the result file
|
||||
ORKStepResult *stepResult = (ORKStepResult *)[taskResult resultForIdentifier:self.audioLevelStepIdentifier];
|
||||
ORKFileResult *audioLevelResult = (ORKFileResult *)[stepResult.results firstObject];
|
||||
|
||||
// Check the volume
|
||||
if ((audioLevelResult.fileURL != nil) && [self checkAudioLevelFromSoundFile:audioLevelResult.fileURL]) {
|
||||
// Returning nil will drop through to the next step (which should be the the step that has the instructions
|
||||
// for moving to a quieter room).
|
||||
return nil;
|
||||
}
|
||||
|
||||
return self.destinationStepIdentifier;
|
||||
}
|
||||
|
||||
- (BOOL)checkAudioLevelFromSoundFile:(NSURL *)fileURL {
|
||||
// Setup reader
|
||||
AVURLAsset *urlAsset = [AVURLAsset URLAssetWithURL:fileURL options:nil];
|
||||
if (urlAsset.tracks.count == 0) {
|
||||
NSLog(@"No tracks found for urlAsset: %@", fileURL);
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSError *error = nil;
|
||||
AVAssetReader *reader = [[AVAssetReader alloc] initWithAsset:urlAsset error:&error];
|
||||
AVAssetTrack *track = [urlAsset.tracks objectAtIndex:0];
|
||||
NSDictionary *outputSettings = @{AVFormatIDKey: @(kAudioFormatLinearPCM),
|
||||
AVLinearPCMBitDepthKey: @(LinearPCMBitDepth),
|
||||
AVLinearPCMIsBigEndianKey: @(NO),
|
||||
AVLinearPCMIsFloatKey: @(NO),
|
||||
AVLinearPCMIsNonInterleaved: @(NO)};
|
||||
AVAssetReaderTrackOutput *output = [[AVAssetReaderTrackOutput alloc] initWithTrack:track outputSettings:outputSettings];
|
||||
[reader addOutput:output];
|
||||
|
||||
// Setup initial values - Assume 2 channels if not in recording settings
|
||||
const UInt32 channelCount = (UInt32)[self.recordingSettings[AVNumberOfChannelsKey] unsignedIntegerValue] ? : 2;
|
||||
const UInt32 bytesPerSample = 2 * channelCount;
|
||||
|
||||
// setup criteria block - Use a high-pass filter and a rolling average of the amplitude
|
||||
// normalized to be < 1
|
||||
__block Float32 rollingAvg = 0;
|
||||
__block UInt64 totalCount = 0;
|
||||
void (^processVolume)(Float32) = ^(Float32 amplitude) {
|
||||
if (amplitude != 0) {
|
||||
Float32 dB = 20 * log10(ABS(amplitude) / MaxAmplitude);
|
||||
float clampedValue = MAX(dB / VolumeClamp, -1) + 1;
|
||||
totalCount++;
|
||||
rollingAvg = (rollingAvg * (totalCount - 1) + clampedValue) / totalCount;
|
||||
}
|
||||
};
|
||||
|
||||
// While there are samples to read and the number of samples above the decibel threshold
|
||||
// is less than the total number of allowed samples over the limit, keep going
|
||||
[reader startReading];
|
||||
while (reader.status == AVAssetReaderStatusReading) {
|
||||
|
||||
AVAssetReaderTrackOutput *trackOutput = (AVAssetReaderTrackOutput *)[reader.outputs objectAtIndex:0];
|
||||
CMSampleBufferRef sampleBufferRef = [trackOutput copyNextSampleBuffer];
|
||||
|
||||
if (sampleBufferRef) {
|
||||
CMBlockBufferRef blockBufferRef = CMSampleBufferGetDataBuffer(sampleBufferRef);
|
||||
size_t length = CMBlockBufferGetDataLength(blockBufferRef);
|
||||
|
||||
NSMutableData *data = [NSMutableData dataWithLength:length];
|
||||
CMBlockBufferCopyDataBytes(blockBufferRef, 0, length, data.mutableBytes);
|
||||
|
||||
SInt16 *samples = (SInt16 *) data.mutableBytes;
|
||||
UInt64 sampleCount = length / bytesPerSample;
|
||||
for (UInt32 i = 0; i < sampleCount ; i++) {
|
||||
Float32 left = (Float32) *samples++;
|
||||
processVolume(left);
|
||||
if (channelCount == 2) {
|
||||
Float32 right = (Float32) *samples++;
|
||||
processVolume(right);
|
||||
}
|
||||
}
|
||||
|
||||
CMSampleBufferInvalidate(sampleBufferRef);
|
||||
CFRelease(sampleBufferRef);
|
||||
}
|
||||
}
|
||||
|
||||
return rollingAvg > VolumeThreshold;
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
@@ -29,8 +29,9 @@
|
||||
*/
|
||||
|
||||
|
||||
@import UIKit;
|
||||
@import AVFoundation;
|
||||
#import <ResearchKit/ORKRecorder.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@@ -30,10 +30,10 @@
|
||||
|
||||
|
||||
#import "ORKAudioRecorder.h"
|
||||
#import "ORKHelpers.h"
|
||||
|
||||
#import "ORKRecorder_Internal.h"
|
||||
#import "ORKRecorder_Private.h"
|
||||
#import "ORKDefines_Private.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
|
||||
|
||||
@interface ORKAudioRecorder ()
|
||||
@@ -42,6 +42,8 @@
|
||||
|
||||
@property (nonatomic, copy) NSDictionary *recorderSettings;
|
||||
|
||||
@property (nonatomic, copy) NSString *savedSessionCategory;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -79,6 +81,16 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)restoreSavedAudioSessionCategory {
|
||||
if (_savedSessionCategory) {
|
||||
NSError *error;
|
||||
if (![[AVAudioSession sharedInstance] setCategory:_savedSessionCategory error:&error]) {
|
||||
ORK_Log_Error(@"Failed to restore the audio session category: %@", [error localizedDescription]);
|
||||
}
|
||||
_savedSessionCategory = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)start {
|
||||
if (self.outputDirectory == nil) {
|
||||
@throw [NSException exceptionWithName:NSDestinationInvalidException reason:@"audioRecorder requires an output directory" userInfo:nil];
|
||||
@@ -95,6 +107,7 @@
|
||||
|
||||
|
||||
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
|
||||
_savedSessionCategory = audioSession.category;
|
||||
if (![audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:&error]) {
|
||||
[self finishRecordingWithError:error];
|
||||
return;
|
||||
@@ -191,12 +204,13 @@
|
||||
|
||||
[self applyFileProtection:ORKFileProtectionComplete toFileAtURL:[self recordingFileURL]];
|
||||
#endif
|
||||
[self restoreSavedAudioSessionCategory];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)finishRecordingWithError:(NSError *)error {
|
||||
[self doStopRecording];
|
||||
|
||||
|
||||
[super finishRecordingWithError:error];
|
||||
}
|
||||
|
||||
@@ -231,7 +245,7 @@
|
||||
return [[self recordingDirectoryURL] URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", [self logName], [self extension]]];
|
||||
}
|
||||
|
||||
- (BOOL)recreateFileWithError:(NSError * __autoreleasing *)error {
|
||||
- (BOOL)recreateFileWithError:(NSError **)error {
|
||||
NSURL *url = [self recordingFileURL];
|
||||
if (!url) {
|
||||
if (error) {
|
||||
@@ -253,7 +267,7 @@
|
||||
}
|
||||
|
||||
[fileManager createFileAtPath:[url path] contents:nil attributes:nil];
|
||||
[fileManager setAttributes:@{NSFileProtectionKey : ORKFileProtectionFromMode(ORKFileProtectionCompleteUnlessOpen)} ofItemAtPath:[url path] error:error];
|
||||
[fileManager setAttributes:@{NSFileProtectionKey: ORKFileProtectionFromMode(ORKFileProtectionCompleteUnlessOpen)} ofItemAtPath:[url path] error:error];
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
*/
|
||||
|
||||
|
||||
@import Foundation;
|
||||
#import <ResearchKit/ORKActiveStep.h>
|
||||
|
||||
|
||||
|
||||
@@ -30,10 +30,13 @@
|
||||
|
||||
|
||||
#import "ORKAudioStep.h"
|
||||
|
||||
#import "ORKAudioStepViewController.h"
|
||||
#import "ORKHelpers.h"
|
||||
|
||||
#import "ORKStep_Private.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
|
||||
|
||||
@implementation ORKAudioStep
|
||||
|
||||
@@ -45,6 +48,7 @@
|
||||
self = [super initWithIdentifier:identifier];
|
||||
if (self) {
|
||||
self.shouldShowDefaultTimer = NO;
|
||||
self.shouldStartTimerAutomatically = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,9 @@
|
||||
*/
|
||||
|
||||
|
||||
#import <ResearchKit/ResearchKit_Private.h>
|
||||
@import Foundation;
|
||||
#import <ResearchKit/ORKDefines.h>
|
||||
#import <ResearchKit/ORKActiveStepViewController.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
@@ -37,6 +39,8 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKAudioStepViewController : ORKActiveStepViewController
|
||||
|
||||
@property (nonatomic, assign) CGFloat alertThreshold;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -30,17 +30,22 @@
|
||||
|
||||
|
||||
#import "ORKAudioStepViewController.h"
|
||||
#import "ORKAudioContentView.h"
|
||||
#import "ORKActiveStepViewController_Internal.h"
|
||||
#import "ORKVerticalContainerView.h"
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
#import "ORKActiveStepTimer.h"
|
||||
#import "ORKHelpers.h"
|
||||
#import "ORKStep_Private.h"
|
||||
#import "ORKAudioStep.h"
|
||||
#import "ORKAudioRecorder.h"
|
||||
#import "ORKActiveStepView.h"
|
||||
#import "ORKAudioContentView.h"
|
||||
#import "ORKCustomStepView_Internal.h"
|
||||
#import "ORKVerticalContainerView.h"
|
||||
|
||||
#import "ORKActiveStepViewController_Internal.h"
|
||||
#import "ORKAudioRecorder.h"
|
||||
|
||||
#import "ORKAudioStep.h"
|
||||
#import "ORKStep_Private.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
|
||||
@import AVFoundation;
|
||||
|
||||
|
||||
@interface ORKAudioStepViewController ()
|
||||
@@ -66,17 +71,24 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setAlertThreshold:(CGFloat)alertThreshold {
|
||||
_alertThreshold = alertThreshold;
|
||||
if (self.isViewLoaded && alertThreshold > 0) {
|
||||
_audioContentView.alertThreshold = alertThreshold;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
// Do any additional setup after loading the view.
|
||||
_audioContentView = [ORKAudioContentView new];
|
||||
_audioContentView.timeLeft = self.audioStep.stepDuration;
|
||||
self.activeStepView.activeCustomView = _audioContentView;
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
[super viewDidAppear:animated];
|
||||
[self start];
|
||||
if (self.alertThreshold > 0) {
|
||||
_audioContentView.alertThreshold = self.alertThreshold;
|
||||
}
|
||||
|
||||
self.activeStepView.activeCustomView = _audioContentView;
|
||||
}
|
||||
|
||||
- (void)audioRecorderDidChange {
|
||||
@@ -115,9 +127,9 @@
|
||||
- (void)startNewTimerIfNeeded {
|
||||
if (!_timer) {
|
||||
NSTimeInterval duration = self.audioStep.stepDuration;
|
||||
__weak typeof(self) weakSelf = self;
|
||||
ORKWeakTypeOf(self) weakSelf = self;
|
||||
_timer = [[ORKActiveStepTimer alloc] initWithDuration:duration interval:duration / 100 runtime:0 handler:^(ORKActiveStepTimer *timer, BOOL finished) {
|
||||
typeof(self) strongSelf = weakSelf;
|
||||
ORKStrongTypeOf(self) strongSelf = weakSelf;
|
||||
[strongSelf doSample];
|
||||
if (finished) {
|
||||
[strongSelf finish];
|
||||
@@ -170,7 +182,7 @@
|
||||
_avAudioRecorder = recorder;
|
||||
}
|
||||
|
||||
- (void) recorder:(ORKRecorder *)recorder didFailWithError:(NSError *)error {
|
||||
- (void)recorder:(ORKRecorder *)recorder didFailWithError:(NSError *)error {
|
||||
[super recorder:recorder didFailWithError:error];
|
||||
_audioRecorderError = error;
|
||||
_audioContentView.failed = YES;
|
||||
|
||||
@@ -29,10 +29,13 @@
|
||||
*/
|
||||
|
||||
|
||||
#import <ResearchKit/ResearchKit.h>
|
||||
@import Foundation;
|
||||
#import <ResearchKit/ORKDefines.h>
|
||||
#import <ResearchKit/ORKActiveStep.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
The `ORKCountdownStep` class represents a step that displays a label and a
|
||||
countdown for a time equal to its duration.
|
||||
@@ -46,3 +49,5 @@ ORK_CLASS_AVAILABLE
|
||||
@interface ORKCountdownStep : ORKActiveStep
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
|
||||
|
||||
#import "ORKCountdownStep.h"
|
||||
|
||||
#import "ORKCountdownStepViewController.h"
|
||||
|
||||
|
||||
|
||||
@@ -29,9 +29,13 @@
|
||||
*/
|
||||
|
||||
|
||||
#import <ResearchKit/ResearchKit_Private.h>
|
||||
@import Foundation;
|
||||
#import <ResearchKit/ORKDefines.h>
|
||||
#import <ResearchKit/ORKActiveStepViewController.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
The `ORKCountdownStepViewController` class represents the step view controller that corresponds to an `ORKCountdownStep`.
|
||||
|
||||
@@ -43,3 +47,5 @@ ORK_CLASS_AVAILABLE
|
||||
@interface ORKCountdownStepViewController : ORKActiveStepViewController
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -30,23 +30,29 @@
|
||||
|
||||
|
||||
#import "ORKCountdownStepViewController.h"
|
||||
#import "ORKCustomStepView_Internal.h"
|
||||
#import "ORKActiveStepViewController_internal.h"
|
||||
#import "ORKVerticalContainerView.h"
|
||||
#import "ORKStepViewController_Internal.h"
|
||||
|
||||
#import "ORKActiveStepTimer.h"
|
||||
#import "ORKResult.h"
|
||||
#import "ORKActiveStepView.h"
|
||||
#import "ORKCustomStepView_Internal.h"
|
||||
#import "ORKLabel.h"
|
||||
#import "ORKSubheadlineLabel.h"
|
||||
#import "ORKHelpers.h"
|
||||
#import "ORKVerticalContainerView.h"
|
||||
|
||||
#import "ORKActiveStepViewController_Internal.h"
|
||||
#import "ORKStepViewController_Internal.h"
|
||||
|
||||
#import "ORKActiveStep.h"
|
||||
#import "ORKResult.h"
|
||||
|
||||
#import "ORKAccessibility.h"
|
||||
#import "ORKActiveStepView.h"
|
||||
#import "ORKHelpers_Internal.h"
|
||||
|
||||
|
||||
@interface ORKCountDownViewLabel : ORKLabel
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKCountDownViewLabel
|
||||
+ (UIFont *)defaultFont {
|
||||
return ORKThinFontWithSize(56);
|
||||
@@ -173,8 +179,8 @@ static const CGFloat ProgressIndicatorOuterMargin = 1.0;
|
||||
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"strokeEnd"];
|
||||
animation.duration = duration * 2;
|
||||
animation.removedOnCompletion = YES;
|
||||
animation.values = @[@(1.0), @(0.0), @(0.0)];
|
||||
animation.keyTimes = @[@(0.0), @(0.5), @(1.0)];
|
||||
animation.values = @[ @(1.0), @(0.0), @(0.0) ];
|
||||
animation.keyTimes = @[ @(0.0), @(0.5), @(1.0) ];
|
||||
animation.timingFunction=[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
|
||||
[_circleLayer addAnimation:animation forKey:@"drawCircleAnimation"];
|
||||
}
|
||||
|
||||
@@ -29,8 +29,8 @@
|
||||
*/
|
||||
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <ResearchKit/ORKDefines.h>
|
||||
@import Foundation;
|
||||
#import <ResearchKit/ORKTypes.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
@@ -65,6 +65,18 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@end
|
||||
|
||||
|
||||
@protocol ORKDataLoggerExtendedDelegate <ORKDataLoggerDelegate>
|
||||
|
||||
@optional
|
||||
/**
|
||||
Tells the delegate that the maximum current log file lifetime changed.
|
||||
@param dataLogger Source of this event.
|
||||
*/
|
||||
- (void)dataLoggerThresholdsDidChange:(ORKDataLogger *)dataLogger;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@class ORKLogFormatter;
|
||||
|
||||
/**
|
||||
@@ -101,6 +113,7 @@ ORK_CLASS_AVAILABLE
|
||||
*/
|
||||
+ (ORKDataLogger *)JSONDataLoggerWithDirectory:(NSURL *)url logName:(NSString *)logName delegate:(nullable id<ORKDataLoggerDelegate>)delegate;
|
||||
|
||||
+ (instancetype)new NS_UNAVAILABLE;
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
/**
|
||||
@@ -166,7 +179,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@return `YES` if the enumeration was successful; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)enumerateLogs:(void (^)(NSURL *logFileUrl, BOOL *stop))block error:(NSError * __autoreleasing *)error;
|
||||
- (BOOL)enumerateLogs:(void (^)(NSURL *logFileUrl, BOOL *stop))block error:(NSError * _Nullable *)error;
|
||||
|
||||
/**
|
||||
Enumerates the URLs of completed log files not yet marked uploaded,
|
||||
@@ -181,7 +194,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@return `YES` if the enumeration was successful; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)enumerateLogsNeedingUpload:(void (^)(NSURL *logFileUrl, BOOL *stop))block error:(NSError * __autoreleasing *)error;
|
||||
- (BOOL)enumerateLogsNeedingUpload:(void (^)(NSURL *logFileUrl, BOOL *stop))block error:(NSError * _Nullable *)error;
|
||||
|
||||
/**
|
||||
Enumerates the URLs of completed log files not already marked uploaded,
|
||||
@@ -196,7 +209,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@return `YES` if the enumeration was successful; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)enumerateLogsAlreadyUploaded:(void (^)(NSURL *logFileUrl, BOOL *stop))block error:(NSError * __autoreleasing *)error;
|
||||
- (BOOL)enumerateLogsAlreadyUploaded:(void (^)(NSURL *logFileUrl, BOOL *stop))block error:(NSError * _Nullable *)error;
|
||||
|
||||
/**
|
||||
Appends an object to the log file, which is formatted with `logFormatter`.
|
||||
@@ -207,12 +220,12 @@ ORK_CLASS_AVAILABLE
|
||||
log data is made. If an attempt is made to log data and there is no access due
|
||||
to file protection, the log is immediately rolled over and a new file created.
|
||||
|
||||
@param object Should be an object of a class that is accepted by the logFormatter.
|
||||
@param error Error output, if the append fails.
|
||||
@param object Should be an object of a class that is accepted by the logFormatter.
|
||||
@param error Error output, if the append fails.
|
||||
|
||||
@return `YES` if appending succeeds; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)append:(id)object error:(NSError * __autoreleasing *)error;
|
||||
- (BOOL)append:(id)object error:(NSError * _Nullable *)error;
|
||||
|
||||
/**
|
||||
Appends multiple objects to the log file.
|
||||
@@ -220,12 +233,12 @@ ORK_CLASS_AVAILABLE
|
||||
This method formats and appends all the objects at once. Using this method may have efficiency
|
||||
and atomicity gains for error handling, compared to making multiple calls to `append:error`.
|
||||
|
||||
@param objects An array of objects of a class that is accepted by the logFormatter.
|
||||
@param error Error output, if the append fails.
|
||||
@param objects An array of objects of a class that is accepted by the logFormatter.
|
||||
@param error Error output, if the append fails.
|
||||
|
||||
@return `YES` if appending succeeds; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)appendObjects:(NSArray *)objects error:(NSError * _Nullable __autoreleasing *)error;
|
||||
- (BOOL)appendObjects:(NSArray *)objects error:(NSError * _Nullable *)error;
|
||||
|
||||
/**
|
||||
Checks whether a file has been marked as uploaded.
|
||||
@@ -251,7 +264,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@return `YES` if adding or removing the attribute succeeded; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)markFileUploaded:(BOOL)uploaded atURL:(NSURL *)url error:(NSError * _Nullable __autoreleasing *)error;
|
||||
- (BOOL)markFileUploaded:(BOOL)uploaded atURL:(NSURL *)url error:(NSError * _Nullable *)error;
|
||||
|
||||
/**
|
||||
Removes files if they are marked uploaded.
|
||||
@@ -265,7 +278,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@return `YES` if removing the files succeeded; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)removeUploadedFiles:(NSArray<NSURL *> *)fileURLs withError:(NSError * _Nullable __autoreleasing *)error;
|
||||
- (BOOL)removeUploadedFiles:(NSArray<NSURL *> *)fileURLs withError:(NSError * _Nullable *)error;
|
||||
|
||||
/**
|
||||
Removes all files managed by this logger (files that have the `logName` prefix).
|
||||
@@ -274,7 +287,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@return `YES` if removing the files succeeded.; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)removeAllFilesWithError:(NSError *_Nullable __autoreleasing *)error;
|
||||
- (BOOL)removeAllFilesWithError:(NSError * _Nullable *)error;
|
||||
|
||||
@end
|
||||
|
||||
@@ -320,7 +333,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@return `YES` if the write succeeds; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)beginLogWithFileHandle:(NSFileHandle *)fileHandle error:(NSError * _Nullable __autoreleasing *)error;
|
||||
- (BOOL)beginLogWithFileHandle:(NSFileHandle *)fileHandle error:(NSError * _Nullable *)error;
|
||||
|
||||
/**
|
||||
Appends the specified object to the log file.
|
||||
@@ -331,7 +344,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@return `YES` if the write succeeds; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)appendObject:(id)object fileHandle:(NSFileHandle *)fileHandle error:(NSError * _Nullable __autoreleasing *)error;
|
||||
- (BOOL)appendObject:(id)object fileHandle:(NSFileHandle *)fileHandle error:(NSError * _Nullable *)error;
|
||||
|
||||
/**
|
||||
Appends the specified objects to the log file.
|
||||
@@ -342,7 +355,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@return `YES` if the write succeeds; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)appendObjects:(NSArray *)objects fileHandle:(NSFileHandle *)fileHandle error:(NSError * _Nullable __autoreleasing *)error;
|
||||
- (BOOL)appendObjects:(NSArray *)objects fileHandle:(NSFileHandle *)fileHandle error:(NSError * _Nullable *)error;
|
||||
|
||||
@end
|
||||
|
||||
@@ -425,6 +438,7 @@ ORK_CLASS_AVAILABLE
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKDataLoggerManager : NSObject <ORKDataLoggerDelegate>
|
||||
|
||||
+ (instancetype)new NS_UNAVAILABLE;
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
/**
|
||||
@@ -505,7 +519,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@return `YES` if the enumeration succeeds; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)enumerateLogsNeedingUpload:(void (^)(ORKDataLogger *dataLogger, NSURL *logFileUrl, BOOL *stop))block error:(NSError * __autoreleasing *)error;
|
||||
- (BOOL)enumerateLogsNeedingUpload:(void (^)(ORKDataLogger *dataLogger, NSURL *logFileUrl, BOOL *stop))block error:(NSError * _Nullable *)error;
|
||||
|
||||
/**
|
||||
Unmarks the set of uploaded files.
|
||||
@@ -518,7 +532,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@return `YES` if the operation succeeds; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)unmarkUploadedFiles:(NSArray<NSURL *> *)fileURLs error:(NSError * _Nullable __autoreleasing *)error;
|
||||
- (BOOL)unmarkUploadedFiles:(NSArray<NSURL *> *)fileURLs error:(NSError * _Nullable *)error;
|
||||
|
||||
/**
|
||||
Removes a set of uploaded files.
|
||||
@@ -532,7 +546,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@return `YES` if the operation succeeds; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)removeUploadedFiles:(NSArray<NSURL *> *)fileURLs error:(NSError * _Nullable __autoreleasing *)error;
|
||||
- (BOOL)removeUploadedFiles:(NSArray<NSURL *> *)fileURLs error:(NSError * _Nullable *)error;
|
||||
|
||||
/**
|
||||
Removes old and uploaded logs to bring total bytes down to a threshold.
|
||||
@@ -545,7 +559,23 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@return `YES` if the operation succeeds; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)removeOldAndUploadedLogsToThreshold:(unsigned long long)bytes error:(NSError * _Nullable __autoreleasing *)error;
|
||||
- (BOOL)removeOldAndUploadedLogsToThreshold:(unsigned long long)bytes error:(NSError * _Nullable *)error;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface ORKDataLogger (Tests)
|
||||
|
||||
/// The file handle to which to write
|
||||
- (nullable NSFileHandle *)fileHandle;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface NSURL (ORKDataLogger)
|
||||
|
||||
- (BOOL)ork_isUploaded;
|
||||
- (BOOL)ork_setUploaded:(BOOL)uploaded error:(NSError * _Nullable *)error;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -30,13 +30,12 @@
|
||||
|
||||
|
||||
#import "ORKDataLogger.h"
|
||||
#import <ResearchKit/ResearchKit.h>
|
||||
#import "ORKHelpers.h"
|
||||
#include <sys/xattr.h>
|
||||
#import "ORKDataLogger_Private.h"
|
||||
#import "HKSample+ORKJSONDictionary.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
#import "CMMotionActivity+ORKJSONDictionary.h"
|
||||
#import "ORKDefines_Private.h"
|
||||
#import "HKSample+ORKJSONDictionary.h"
|
||||
|
||||
#include <sys/xattr.h>
|
||||
|
||||
|
||||
static const char *ORKDataLoggerUploadedAttr = "com.apple.ResearchKit.uploaded";
|
||||
@@ -109,7 +108,7 @@ static NSString *const ORKDataLoggerManagerConfigurationFilename = @".ORKDataLog
|
||||
return (string.integerValue != 0);
|
||||
}
|
||||
|
||||
- (BOOL)ork_setUploaded:(BOOL)uploaded error:(NSError * __autoreleasing *)error {
|
||||
- (BOOL)ork_setUploaded:(BOOL)uploaded error:(NSError **)error {
|
||||
NSString *value = (uploaded ? @"1" : @"0");
|
||||
NSData *encodedString = [value dataUsingEncoding:NSUTF8StringEncoding];
|
||||
return [self ork_setData:encodedString forAttr:ORKDataLoggerUploadedAttr error:error];
|
||||
@@ -133,12 +132,12 @@ static NSString *const ORKDataLoggerManagerConfigurationFilename = @".ORKDataLog
|
||||
return data;
|
||||
}
|
||||
|
||||
- (BOOL)ork_setData:(NSData *)data forAttr:(const char *)attr error:(NSError * __autoreleasing *)error {
|
||||
- (BOOL)ork_setData:(NSData *)data forAttr:(const char *)attr error:(NSError **)error {
|
||||
const char *path = [self fileSystemRepresentation];
|
||||
int rc = setxattr(path, attr, data.bytes, data.length, 0, 0);
|
||||
if (rc != 0) {
|
||||
if (error) {
|
||||
*error = [NSError errorWithDomain:NSCocoaErrorDomain code:rc userInfo:@{NSLocalizedDescriptionKey : ORKLocalizedString(@"ERROR_DATALOGGER_SET_ATTRIBUTE", nil)}];
|
||||
*error = [NSError errorWithDomain:NSCocoaErrorDomain code:rc userInfo:@{NSLocalizedDescriptionKey: ORKLocalizedString(@"ERROR_DATALOGGER_SET_ATTRIBUTE", nil)}];
|
||||
}
|
||||
}
|
||||
return (rc == 0);
|
||||
@@ -239,11 +238,11 @@ static void *ORKObjectObserverContext = &ORKObjectObserverContext;
|
||||
return [object isKindOfClass:[NSData class]];
|
||||
}
|
||||
|
||||
- (BOOL)beginLogWithFileHandle:(NSFileHandle *)fileHandle error:(NSError * __autoreleasing *)error {
|
||||
- (BOOL)beginLogWithFileHandle:(NSFileHandle *)fileHandle error:(NSError **)error {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)writeData:(NSData *)data fileHandle:(NSFileHandle *)fileHandle error:(NSError * __autoreleasing *)error {
|
||||
- (BOOL)writeData:(NSData *)data fileHandle:(NSFileHandle *)fileHandle error:(NSError **)error {
|
||||
BOOL result = YES;
|
||||
@try {
|
||||
[fileHandle writeData:data];
|
||||
@@ -251,7 +250,7 @@ static void *ORKObjectObserverContext = &ORKObjectObserverContext;
|
||||
@catch (NSException *exception) {
|
||||
result = NO;
|
||||
if (error) {
|
||||
*error = [NSError errorWithDomain:ORKErrorDomain code:ORKErrorException userInfo:@{@"exception" : exception}];
|
||||
*error = [NSError errorWithDomain:ORKErrorDomain code:ORKErrorException userInfo:@{@"exception": exception}];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -266,14 +265,14 @@ static void *ORKObjectObserverContext = &ORKObjectObserverContext;
|
||||
[fileHandle truncateFileAtOffset:offset];
|
||||
}
|
||||
|
||||
- (BOOL)appendObject:(id)object fileHandle:(NSFileHandle *)fileHandle error:(NSError * __autoreleasing *)error {
|
||||
- (BOOL)appendObject:(id)object fileHandle:(NSFileHandle *)fileHandle error:(NSError **)error {
|
||||
if (![self canAcceptLogObject:object]) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"ORKLogFormatter accepts NSData only" userInfo:nil];
|
||||
}
|
||||
return [self writeData:(NSData *)object fileHandle:fileHandle error:error];
|
||||
}
|
||||
|
||||
- (BOOL)appendObjects:(NSArray *)objects fileHandle:(NSFileHandle *)fileHandle error:(NSError * __autoreleasing *)error {
|
||||
- (BOOL)appendObjects:(NSArray *)objects fileHandle:(NSFileHandle *)fileHandle error:(NSError **)error {
|
||||
unsigned long long checkpoint = [self checkpointWithFileHandle:fileHandle];
|
||||
|
||||
NSError *errorOut = nil;
|
||||
@@ -327,7 +326,7 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
return [object isKindOfClass:[NSDictionary class]] && [NSJSONSerialization isValidJSONObject:object];
|
||||
}
|
||||
|
||||
- (BOOL)beginLogWithFileHandle:(NSFileHandle *)fileHandle error:(NSError * __autoreleasing *)error {
|
||||
- (BOOL)beginLogWithFileHandle:(NSFileHandle *)fileHandle error:(NSError **)error {
|
||||
// Write valid JSON containing no objects
|
||||
NSData *data = [kJSONLogEmptyLogString dataUsingEncoding:NSUTF8StringEncoding];
|
||||
return [self writeData:data fileHandle:fileHandle error:error];
|
||||
@@ -348,7 +347,7 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)appendObject:(id)object fileHandle:(NSFileHandle *)fileHandle error:(NSError * __autoreleasing *)error {
|
||||
- (BOOL)appendObject:(id)object fileHandle:(NSFileHandle *)fileHandle error:(NSError **)error {
|
||||
return [self appendObjects:@[object] fileHandle:fileHandle error:error];
|
||||
}
|
||||
|
||||
@@ -446,10 +445,12 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
return [[ORKDataLogger alloc] initWithDirectory:url logName:logName formatter:[ORKJSONLogFormatter new] delegate:delegate];
|
||||
}
|
||||
|
||||
+ (instancetype)new {
|
||||
ORKThrowMethodUnavailableException();
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
ORKThrowMethodUnavailableException();
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (instancetype)initWithDirectory:(NSURL *)url logName:(NSString *)logName formatter:(ORKLogFormatter *)formatter delegate:(id<ORKDataLoggerDelegate>)delegate {
|
||||
@@ -490,7 +491,7 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
@throw [NSException exceptionWithName:NSGenericException reason:[NSString stringWithFormat:@"%@ is not a class", configuration[@"formatterClass"]] userInfo:nil];
|
||||
}
|
||||
|
||||
self = [self initWithDirectory:url logName:configuration[@"logName"] formatter:[formatterClass new] delegate:delegate];
|
||||
self = [self initWithDirectory:url logName:configuration[@"logName"] formatter:[[formatterClass alloc] init] delegate:delegate];
|
||||
if (self) {
|
||||
// Don't notify about initial setup
|
||||
[_observer pause];
|
||||
@@ -502,11 +503,11 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
}
|
||||
|
||||
- (NSDictionary *)configuration {
|
||||
return @{@"logName" : self.logName,
|
||||
@"formatterClass" : NSStringFromClass([self.logFormatter class]),
|
||||
@"fileProtectionMode" : @(self.fileProtectionMode),
|
||||
@"maximumCurrentLogFileSize" : @(self.maximumCurrentLogFileSize),
|
||||
@"maximumCurrentLogFileLifetime" : @(self.maximumCurrentLogFileLifetime)
|
||||
return @{@"logName": self.logName,
|
||||
@"formatterClass": NSStringFromClass([self.logFormatter class]),
|
||||
@"fileProtectionMode": @(self.fileProtectionMode),
|
||||
@"maximumCurrentLogFileSize": @(self.maximumCurrentLogFileSize),
|
||||
@"maximumCurrentLogFileLifetime": @(self.maximumCurrentLogFileLifetime)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -527,9 +528,9 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
|
||||
if (_directorySource) {
|
||||
dispatch_source_set_cancel_handler(_directorySource, ^{ close(dirFD); });
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
ORKWeakTypeOf(self) weakSelf = self;
|
||||
dispatch_source_set_event_handler(_directorySource, ^{
|
||||
__strong __typeof(self) strongSelf = weakSelf;
|
||||
ORKStrongTypeOf(self) strongSelf = weakSelf;
|
||||
[strongSelf directoryUpdated];
|
||||
});
|
||||
dispatch_resume(_directorySource);
|
||||
@@ -594,11 +595,11 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
return success;
|
||||
}
|
||||
|
||||
- (BOOL)enumerateLogsNeedingUpload:(void (^)(NSURL *logFileUrl, BOOL *stop))block error:(NSError * __autoreleasing *)error {
|
||||
- (BOOL)enumerateLogsNeedingUpload:(void (^)(NSURL *logFileUrl, BOOL *stop))block error:(NSError **)error {
|
||||
return [self enumerateLogsUploaded:NO block:block error:error];
|
||||
}
|
||||
|
||||
- (BOOL)enumerateLogsAlreadyUploaded:(void (^)(NSURL *logFileUrl, BOOL *stop))block error:(NSError * __autoreleasing *)error {
|
||||
- (BOOL)enumerateLogsAlreadyUploaded:(void (^)(NSURL *logFileUrl, BOOL *stop))block error:(NSError **)error {
|
||||
return [self enumerateLogsUploaded:YES block:block error:error];
|
||||
}
|
||||
|
||||
@@ -686,7 +687,7 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
});
|
||||
}
|
||||
|
||||
- (BOOL)queue_enumerateLogs:(void (^)(NSURL *logFileUrl, BOOL *stop))block error:(NSError * __autoreleasing *)error {
|
||||
- (BOOL)queue_enumerateLogs:(void (^)(NSURL *logFileUrl, BOOL *stop))block error:(NSError **)error {
|
||||
static NSArray *keys = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
@@ -744,7 +745,7 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
return (errorOut ? NO : YES);
|
||||
}
|
||||
|
||||
- (BOOL)queue_enumerateLogsUploaded:(BOOL)uploaded block:(void (^)(NSURL *logFileUrl, BOOL *stop))block error:(NSError * __autoreleasing *)error {
|
||||
- (BOOL)queue_enumerateLogsUploaded:(BOOL)uploaded block:(void (^)(NSURL *logFileUrl, BOOL *stop))block error:(NSError **)error {
|
||||
return [self queue_enumerateLogs:^(NSURL *logFileUrl, BOOL *stop) {
|
||||
NSError *errorOut = nil;
|
||||
BOOL wantUploaded = [logFileUrl ork_isUploaded];
|
||||
@@ -758,7 +759,7 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
} error:error];
|
||||
}
|
||||
|
||||
- (NSFileHandle *)queue_makeFileHandleWithError:(NSError * __autoreleasing *)error {
|
||||
- (NSFileHandle *)queue_makeFileHandleWithError:(NSError **)error {
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
NSURL *url = [self currentLogFileURL];
|
||||
|
||||
@@ -784,7 +785,7 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
BOOL success = [fileManager createFileAtPath:filePath contents:nil attributes:nil];
|
||||
if (!success) {
|
||||
if (error) {
|
||||
*error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileNoSuchFileError userInfo:@{NSLocalizedDescriptionKey : ORKLocalizedString(@"ERROR_DATALOGGER_CREATE_FILE", nil)}];
|
||||
*error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileNoSuchFileError userInfo:@{NSLocalizedDescriptionKey: ORKLocalizedString(@"ERROR_DATALOGGER_CREATE_FILE", nil)}];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
@@ -799,7 +800,7 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
assert(fileHandle);
|
||||
|
||||
// Set file protection after opening the file, so that class B works as expected.
|
||||
BOOL success = [fileManager setAttributes:@{NSFileProtectionKey : ORKFileProtectionFromMode(self.fileProtectionMode)} ofItemAtPath:[url path] error:error];
|
||||
BOOL success = [fileManager setAttributes:@{NSFileProtectionKey: ORKFileProtectionFromMode(self.fileProtectionMode)} ofItemAtPath:[url path] error:error];
|
||||
|
||||
// Allow formatter to initialize the log file with header content
|
||||
success = success && [self.logFormatter beginLogWithFileHandle:fileHandle error:error];
|
||||
@@ -814,7 +815,7 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
return _currentFileHandle;
|
||||
}
|
||||
|
||||
- (NSFileHandle *)queue_fileHandleWithError:(NSError * __autoreleasing *)error {
|
||||
- (NSFileHandle *)queue_fileHandleWithError:(NSError **)error {
|
||||
if (!_currentFileHandle) {
|
||||
_currentFileHandle = [self queue_makeFileHandleWithError:error];
|
||||
|
||||
@@ -824,15 +825,15 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
}
|
||||
|
||||
+ (NSURL *)nextUrlForDirectoryUrl:(NSURL *)directory logName:(NSString *)logName {
|
||||
static NSDateFormatter *dfm = nil;
|
||||
static NSDateFormatter *dateFromatter = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
dfm = [NSDateFormatter new];
|
||||
[dfm setLocale:[NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]];
|
||||
dfm.dateFormat = @"yyyyMMddHHmmss";
|
||||
dateFromatter = [NSDateFormatter new];
|
||||
[dateFromatter setLocale:[NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]];
|
||||
dateFromatter.dateFormat = @"yyyyMMddHHmmss";
|
||||
});
|
||||
|
||||
NSString *datedLog = [NSString stringWithFormat:@"%@-%@",logName, [dfm stringFromDate:[NSDate date]]];
|
||||
NSString *datedLog = [NSString stringWithFormat:@"%@-%@",logName, [dateFromatter stringFromDate:[NSDate date]]];
|
||||
NSURL *destinationUrl = [directory URLByAppendingPathComponent:datedLog];
|
||||
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
@@ -868,8 +869,8 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
if (self.fileProtectionMode == ORKFileProtectionCompleteUnlessOpen) {
|
||||
// Upgrade to complete file protection after roll-over
|
||||
NSError *error = nil;
|
||||
if (![fileManager setAttributes:@{NSFileProtectionKey : NSFileProtectionComplete}
|
||||
ofItemAtPath:[destinationUrl path] error:&error]) {
|
||||
if (![fileManager setAttributes:@{NSFileProtectionKey: NSFileProtectionComplete}
|
||||
ofItemAtPath:[destinationUrl path] error:&error]) {
|
||||
ORK_Log_Warning(@"Error setting NSFileProtectionComplete on %@: %@", destinationUrl, error);
|
||||
}
|
||||
}
|
||||
@@ -907,7 +908,7 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
[self queue_closeAndRenameLog];
|
||||
}
|
||||
|
||||
- (BOOL)queue_append:(id)object error:(NSError * __autoreleasing *)error {
|
||||
- (BOOL)queue_append:(id)object error:(NSError **)error {
|
||||
[self queue_rolloverIfNeeded];
|
||||
|
||||
NSFileHandle *fileHandle = [self queue_fileHandleWithError:error];
|
||||
@@ -925,7 +926,7 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
- (BOOL)queue_appendObjects:(NSArray *)objects error:(NSError * __autoreleasing *)error {
|
||||
- (BOOL)queue_appendObjects:(NSArray *)objects error:(NSError **)error {
|
||||
[self queue_rolloverIfNeeded];
|
||||
|
||||
NSFileHandle *fileHandle = [self queue_fileHandleWithError:error];
|
||||
@@ -942,13 +943,13 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
- (BOOL)queue_markFileUploaded:(BOOL)uploaded atURL:(NSURL *)url error:(NSError * __autoreleasing *)error {
|
||||
- (BOOL)queue_markFileUploaded:(BOOL)uploaded atURL:(NSURL *)url error:(NSError **)error {
|
||||
BOOL success = [url ork_setUploaded:uploaded error:error];
|
||||
[self queue_setNeedsUpdateBytes];
|
||||
return success;
|
||||
}
|
||||
|
||||
- (BOOL)queue_removeUploadedFiles:(NSArray<NSURL *> *)fileURLs withError:(NSError * __autoreleasing *)error {
|
||||
- (BOOL)queue_removeUploadedFiles:(NSArray<NSURL *> *)fileURLs withError:(NSError **)error {
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
__block NSMutableArray *errors = [NSMutableArray array];
|
||||
BOOL success = [self queue_enumerateLogs:^(NSURL *logFileUrl, BOOL *stop) {
|
||||
@@ -962,7 +963,9 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
}
|
||||
} else {
|
||||
// File was requested to be removed, but was not marked uploaded
|
||||
[errors addObject:[NSError errorWithDomain:ORKErrorDomain code:ORKErrorInvalidObject userInfo:@{NSLocalizedDescriptionKey : ORKLocalizedString(@"ERROR_DATALOGGER_COULD_NOT_MAORK", nil), @"url" : logFileUrl}]];
|
||||
[errors addObject:[NSError errorWithDomain:ORKErrorDomain
|
||||
code:ORKErrorInvalidObject
|
||||
userInfo:@{NSLocalizedDescriptionKey: ORKLocalizedString(@"ERROR_DATALOGGER_COULD_NOT_MAORK", nil), @"url": logFileUrl}]];
|
||||
}
|
||||
}
|
||||
} error:error];
|
||||
@@ -971,7 +974,9 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
if (errors.count) {
|
||||
if (!success && error && *error) {
|
||||
[errors addObject:*error];
|
||||
*error = [NSError errorWithDomain:ORKErrorDomain code:ORKErrorMultipleErrors userInfo:@{NSLocalizedDescriptionKey : ORKLocalizedString(@"ERROR_DATALOGGER_MULTIPLE", nil), @"errors" : errors}];
|
||||
*error = [NSError errorWithDomain:ORKErrorDomain
|
||||
code:ORKErrorMultipleErrors
|
||||
userInfo:@{NSLocalizedDescriptionKey: ORKLocalizedString(@"ERROR_DATALOGGER_MULTIPLE", nil), @"errors": errors}];
|
||||
}
|
||||
success = NO;
|
||||
}
|
||||
@@ -1042,10 +1047,12 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
|
||||
@implementation ORKDataLoggerManager
|
||||
|
||||
+ (instancetype)new {
|
||||
ORKThrowMethodUnavailableException();
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
ORKThrowMethodUnavailableException();
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (instancetype)initWithDirectory:(NSURL *)directory delegate:(id<ORKDataLoggerManagerDelegate>)delegate {
|
||||
@@ -1094,9 +1101,9 @@ static NSString *const LoggerConfigurationsKey = @"loggers";
|
||||
- (NSDictionary *)queue_configuration {
|
||||
NSMutableArray *loggerConfigurations = [_records.allValues valueForKey:@"configuration"];
|
||||
|
||||
return @{PendingUploadBytesThresholdKey : @(self.pendingUploadBytesThreshold),
|
||||
TotalBytesThresholdKey : @(self.totalBytesThreshold),
|
||||
LoggerConfigurationsKey : loggerConfigurations };
|
||||
return @{PendingUploadBytesThresholdKey: @(self.pendingUploadBytesThreshold),
|
||||
TotalBytesThresholdKey: @(self.totalBytesThreshold),
|
||||
LoggerConfigurationsKey: loggerConfigurations };
|
||||
}
|
||||
|
||||
- (void)queue_synchronizeConfiguration {
|
||||
@@ -1175,7 +1182,7 @@ static NSString *const LoggerConfigurationsKey = @"loggers";
|
||||
return logNames;
|
||||
}
|
||||
|
||||
- (BOOL)queue_enumerateLogsNeedingUpload:(void (^)(ORKDataLogger *dataLogger, NSURL *logFileUrl, BOOL *stop))block error:(NSError * __autoreleasing *)error {
|
||||
- (BOOL)queue_enumerateLogsNeedingUpload:(void (^)(ORKDataLogger *dataLogger, NSURL *logFileUrl, BOOL *stop))block error:(NSError **)error {
|
||||
BOOL success = YES;
|
||||
NSMutableArray *allFiles = [NSMutableArray array];
|
||||
// Collect all the log file URLs so we can sort them by date rather than enumerating by logger.
|
||||
@@ -1226,7 +1233,7 @@ static NSString *const LoggerConfigurationsKey = @"loggers";
|
||||
return success;
|
||||
}
|
||||
|
||||
- (BOOL)queue_removeUploadedFiles:(NSArray<NSURL *> *)fileURLs error:(NSError * __autoreleasing *)error {
|
||||
- (BOOL)queue_removeUploadedFiles:(NSArray<NSURL *> *)fileURLs error:(NSError **)error {
|
||||
BOOL success = YES;
|
||||
NSMutableArray *notRemoved = [NSMutableArray array];
|
||||
for (NSURL *url in fileURLs) {
|
||||
@@ -1258,7 +1265,7 @@ static NSString *const LoggerConfigurationsKey = @"loggers";
|
||||
return success;
|
||||
}
|
||||
|
||||
- (BOOL)queue_unmarkUploadedFiles:(NSArray<NSURL *> *)fileURLs error:(NSError * __autoreleasing *)error {
|
||||
- (BOOL)queue_unmarkUploadedFiles:(NSArray<NSURL *> *)fileURLs error:(NSError **)error {
|
||||
BOOL success = YES;
|
||||
NSMutableArray<NSURL *> *notRemoved = [NSMutableArray array];
|
||||
for (NSURL *url in fileURLs) {
|
||||
@@ -1281,7 +1288,7 @@ static NSString *const LoggerConfigurationsKey = @"loggers";
|
||||
return success;
|
||||
}
|
||||
|
||||
- (BOOL)unmarkUploadedFiles:(NSArray<NSURL *> *)fileURLs error:(NSError *__autoreleasing *)error {
|
||||
- (BOOL)unmarkUploadedFiles:(NSArray<NSURL *> *)fileURLs error:(NSError * __autoreleasing *)error {
|
||||
__block BOOL success = YES;
|
||||
dispatch_sync(_queue, ^{
|
||||
success = [self queue_unmarkUploadedFiles:fileURLs error:error];
|
||||
@@ -1289,7 +1296,7 @@ static NSString *const LoggerConfigurationsKey = @"loggers";
|
||||
return success;
|
||||
}
|
||||
|
||||
- (BOOL)queue_removeOldAndUploadedLogsToThreshold:(unsigned long long)bytes error:(NSError *__autoreleasing *)error {
|
||||
- (BOOL)queue_removeOldAndUploadedLogsToThreshold:(unsigned long long)bytes error:(NSError **)error {
|
||||
if (bytes == 0) {
|
||||
for (ORKDataLogger *logger in _records) {
|
||||
[logger removeAllFilesWithError:nil];
|
||||
@@ -1346,7 +1353,7 @@ static NSString *const LoggerConfigurationsKey = @"loggers";
|
||||
return (totalBytes <= bytes);
|
||||
}
|
||||
|
||||
- (BOOL)removeOldAndUploadedLogsToThreshold:(unsigned long long)bytes error:(NSError *__autoreleasing *)error {
|
||||
- (BOOL)removeOldAndUploadedLogsToThreshold:(unsigned long long)bytes error:(NSError * __autoreleasing *)error {
|
||||
__block BOOL success = YES;
|
||||
dispatch_sync(_queue, ^{
|
||||
success = [self queue_removeOldAndUploadedLogsToThreshold:bytes error:error];
|
||||
|
||||
@@ -28,8 +28,11 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
@import UIKit;
|
||||
#import <ResearchKit/ORKRecorder.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class CMDeviceMotion;
|
||||
|
||||
@@ -30,13 +30,16 @@
|
||||
|
||||
|
||||
#import "ORKDeviceMotionRecorder.h"
|
||||
#import "ORKHelpers.h"
|
||||
#import "ORKRecorder_Internal.h"
|
||||
#import "ORKRecorder_Private.h"
|
||||
|
||||
#import "ORKDataLogger.h"
|
||||
#import <CoreMotion/CoreMotion.h>
|
||||
|
||||
#import "ORKRecorder_Internal.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
#import "CMDeviceMotion+ORKJSONDictionary.h"
|
||||
|
||||
@import CoreMotion;
|
||||
|
||||
|
||||
@interface ORKDeviceMotionRecorder () {
|
||||
ORKDataLogger *_logger;
|
||||
@@ -85,10 +88,10 @@
|
||||
[super start];
|
||||
|
||||
if (!_logger) {
|
||||
NSError *err = nil;
|
||||
_logger = [self makeJSONDataLoggerWithError:&err];
|
||||
NSError *error = nil;
|
||||
_logger = [self makeJSONDataLoggerWithError:&error];
|
||||
if (!_logger) {
|
||||
[self finishRecordingWithError:err];
|
||||
[self finishRecordingWithError:error];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,12 +30,16 @@
|
||||
|
||||
|
||||
#import "ORKFitnessContentView.h"
|
||||
#import "ORKHelpers.h"
|
||||
#import <CoreMotion/CoreMotion.h>
|
||||
#import "ORKSkin.h"
|
||||
|
||||
#import "ORKActiveStepQuantityView.h"
|
||||
#import "ORKTintedImageView.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
#import "ORKSkin.h"
|
||||
|
||||
@import CoreMotion;
|
||||
@import HealthKit;
|
||||
|
||||
|
||||
// #define LAYOUT_TEST 1
|
||||
// #define LAYOUT_DEBUG 1
|
||||
@@ -315,7 +319,7 @@
|
||||
HKQuantity *quantity = [HKQuantity quantityWithUnit:[HKUnit meterUnit] doubleValue:displayDistance];
|
||||
distanceString = [_lengthFormatter.numberFormatter stringFromNumber:@([quantity doubleValueForUnit:hkUnit]*conversionFactor)];
|
||||
|
||||
[self distanceView].title = [NSString stringWithFormat:ORKLocalizedString(@"FITNESS_DISTANCE_TITLE_FORMAT", nil), unitString];
|
||||
[self distanceView].title = [NSString localizedStringWithFormat:ORKLocalizedString(@"FITNESS_DISTANCE_TITLE_FORMAT", nil), unitString];
|
||||
[self distanceView].value = distanceString;
|
||||
}
|
||||
|
||||
@@ -325,19 +329,18 @@
|
||||
}
|
||||
|
||||
- (void)updateTimerLabel {
|
||||
static NSDateComponentsFormatter *_formatter = nil;
|
||||
static NSDateComponentsFormatter *formatter = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
NSDateComponentsFormatter *fmt = [NSDateComponentsFormatter new];
|
||||
fmt.unitsStyle = NSDateComponentsFormatterUnitsStylePositional;
|
||||
fmt.zeroFormattingBehavior = NSDateComponentsFormatterZeroFormattingBehaviorPad;
|
||||
fmt.allowedUnits = NSCalendarUnitMinute | NSCalendarUnitSecond;
|
||||
_formatter = fmt;
|
||||
formatter = [NSDateComponentsFormatter new];
|
||||
formatter.unitsStyle = NSDateComponentsFormatterUnitsStylePositional;
|
||||
formatter.zeroFormattingBehavior = NSDateComponentsFormatterZeroFormattingBehaviorPad;
|
||||
formatter.allowedUnits = NSCalendarUnitMinute | NSCalendarUnitSecond;
|
||||
});
|
||||
|
||||
NSString *s = [_formatter stringFromTimeInterval:MAX(round(_timeLeft),0)];
|
||||
_timerLabel.text = s;
|
||||
_timerLabel.hidden = (s == nil);
|
||||
NSString *labelString = [formatter stringFromTimeInterval:MAX(round(_timeLeft),0)];
|
||||
_timerLabel.text = labelString;
|
||||
_timerLabel.hidden = (labelString == nil);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -29,10 +29,13 @@
|
||||
*/
|
||||
|
||||
|
||||
#import <ResearchKit/ResearchKit.h>
|
||||
@import Foundation;
|
||||
#import <ResearchKit/ORKDefines.h>
|
||||
#import <ResearchKit/ORKActiveStep.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
Fitness step.
|
||||
|
||||
@@ -45,3 +48,5 @@ ORK_CLASS_AVAILABLE
|
||||
@interface ORKFitnessStep : ORKActiveStep
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
|
||||
|
||||
#import "ORKFitnessStep.h"
|
||||
|
||||
#import "ORKFitnessStepViewController.h"
|
||||
|
||||
|
||||
|
||||
@@ -29,9 +29,13 @@
|
||||
*/
|
||||
|
||||
|
||||
#import <ResearchKit/ResearchKit_Private.h>
|
||||
@import Foundation;
|
||||
#import <ResearchKit/ORKDefines.h>
|
||||
#import <ResearchKit/ORKActiveStepViewController.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
Step view controller corresponding to `ORKFitnessStep`.
|
||||
|
||||
@@ -42,3 +46,5 @@ ORK_CLASS_AVAILABLE
|
||||
@interface ORKFitnessStepViewController : ORKActiveStepViewController
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -30,19 +30,24 @@
|
||||
|
||||
|
||||
#import "ORKFitnessStepViewController.h"
|
||||
#import "ORKHelpers.h"
|
||||
#import "ORKStep_Private.h"
|
||||
#import "ORKStepViewController_Internal.h"
|
||||
#import "ORKActiveStepViewController_Internal.h"
|
||||
|
||||
#import "ORKActiveStepTimer.h"
|
||||
#import "ORKActiveStepView.h"
|
||||
#import "ORKFitnessContentView.h"
|
||||
#import "ORKVerticalContainerView.h"
|
||||
#import "ORKFitnessStep.h"
|
||||
|
||||
#import "ORKStepViewController_Internal.h"
|
||||
#import "ORKHealthQuantityTypeRecorder.h"
|
||||
#import "ORKPedometerRecorder.h"
|
||||
#import "ORKActiveStepView.h"
|
||||
|
||||
#import "ORKActiveStepViewController_Internal.h"
|
||||
#import "ORKFitnessStep.h"
|
||||
#import "ORKStep_Private.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
|
||||
|
||||
@interface ORKFitnessStepViewController () <ORKHealthQuantityTypeRecorderDelegate,ORKPedometerRecorderDelegate> {
|
||||
@interface ORKFitnessStepViewController () <ORKHealthQuantityTypeRecorderDelegate, ORKPedometerRecorderDelegate> {
|
||||
NSInteger _intendedSteps;
|
||||
ORKFitnessContentView *_contentView;
|
||||
NSNumberFormatter *_hrFormatter;
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
*/
|
||||
|
||||
|
||||
@import UIKit;
|
||||
#import <ResearchKit/ORKRecorder.h>
|
||||
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
|
||||
#import "ORKHealthQuantityTypeRecorder.h"
|
||||
#import "ORKHelpers.h"
|
||||
#import "ORKHelpers_Internal.h"
|
||||
#import "ORKDataLogger.h"
|
||||
#import "ORKRecorder_Private.h"
|
||||
#import "ORKRecorder_Internal.h"
|
||||
@@ -43,12 +43,29 @@
|
||||
HKHealthStore *_healthStore;
|
||||
NSPredicate *_samplePredicate;
|
||||
HKObserverQuery *_observerQuery;
|
||||
NSInteger _anchor;
|
||||
/// Either the HKQueryAnchor object *or* NSUInteger value are tracked since the initializer for
|
||||
/// iOS 8 and iOS 9 use different objects. Only one will actually be referenced in the initalizer.
|
||||
HKQueryAnchor *_anchor;
|
||||
NSUInteger _anchorValue;
|
||||
HKQuantitySample *_lastSample;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#ifdef __IPHONE_10_0
|
||||
/// Add a protocol defining the initializer for iOS 8 apps. This signature was deprecated in iOS 9
|
||||
/// and deleted in iOS 10.
|
||||
@interface HKAnchoredObjectQuery (iOS8)
|
||||
- (instancetype)initWithType:(HKSampleType *)type
|
||||
predicate:(NSPredicate *)predicate
|
||||
anchor:(NSUInteger)anchor
|
||||
limit:(NSUInteger)limit
|
||||
completionHandler:(void (^)(HKAnchoredObjectQuery *query,
|
||||
NSArray<__kindof HKSample *> *results,
|
||||
NSUInteger newAnchor,
|
||||
NSError *error))handler NS_DEPRECATED_IOS(8_0, 9_0);
|
||||
@end
|
||||
#endif
|
||||
|
||||
@implementation ORKHealthQuantityTypeRecorder
|
||||
|
||||
@@ -67,7 +84,8 @@
|
||||
_quantityType = quantityType;
|
||||
_unit = unit;
|
||||
self.continuesInBackground = YES;
|
||||
_anchor = HKAnchoredObjectQueryNoAnchor;
|
||||
_anchorValue = HKAnchoredObjectQueryNoAnchor;
|
||||
_anchor = [HKQueryAnchor anchorFromValue:_anchorValue];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -89,7 +107,7 @@
|
||||
|
||||
static const NSInteger _HealthAnchoredQueryLimit = 100;
|
||||
|
||||
- (void)query_logResults:(NSArray *)results withAnchor:(NSUInteger)newAnchor {
|
||||
- (void)query_logResults:(NSArray *)results withAnchor:(HKQueryAnchor*)newAnchor anchorValue:(NSUInteger)anchorValue {
|
||||
|
||||
NSUInteger resultCount = results.count;
|
||||
if (resultCount == 0) {
|
||||
@@ -113,6 +131,7 @@ static const NSInteger _HealthAnchoredQueryLimit = 100;
|
||||
}
|
||||
|
||||
_anchor = newAnchor;
|
||||
_anchorValue = anchorValue;
|
||||
|
||||
if (resultCount == _HealthAnchoredQueryLimit) {
|
||||
// Do another fetch immediately rather than wait for an observation
|
||||
@@ -128,23 +147,46 @@ static const NSInteger _HealthAnchoredQueryLimit = 100;
|
||||
NSAssert(_samplePredicate != nil, @"Sample predicate should be non-nil if recording");
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
HKAnchoredObjectQuery *anchoredQuery = [[HKAnchoredObjectQuery alloc]
|
||||
initWithType:_quantityType
|
||||
predicate:_samplePredicate
|
||||
anchor:_anchor
|
||||
limit:_HealthAnchoredQueryLimit
|
||||
completionHandler:^(HKAnchoredObjectQuery *query, NSArray *results, NSUInteger newAnchor, NSError *error)
|
||||
{
|
||||
if (error) {
|
||||
// An error in the query's not the end of the world: we'll probably get another chance. Just log it.
|
||||
ORK_Log_Warning(@"Anchored query error: %@", error);
|
||||
return;
|
||||
}
|
||||
|
||||
__typeof(self) strongSelf = weakSelf;
|
||||
[strongSelf query_logResults:results withAnchor:newAnchor];
|
||||
|
||||
}];
|
||||
void (^handleResults)(NSArray <__kindof HKSample *> *, HKQueryAnchor *, NSUInteger, NSError *) = ^ (NSArray *results, HKQueryAnchor *newAnchor, NSUInteger newAnchorValue, NSError *error) {
|
||||
if (error) {
|
||||
// An error in the query's not the end of the world: we'll probably get another chance. Just log it.
|
||||
ORK_Log_Warning(@"Anchored query error: %@", error);
|
||||
return;
|
||||
}
|
||||
|
||||
__typeof(self) strongSelf = weakSelf;
|
||||
[strongSelf query_logResults:results withAnchor:newAnchor anchorValue:newAnchorValue];
|
||||
};
|
||||
|
||||
|
||||
HKAnchoredObjectQuery *anchoredQuery;
|
||||
if ([HKAnchoredObjectQuery instancesRespondToSelector:@selector(initWithType:predicate:anchor:limit:resultsHandler:)]) {
|
||||
|
||||
anchoredQuery = [[HKAnchoredObjectQuery alloc] initWithType:_quantityType
|
||||
predicate:_samplePredicate
|
||||
anchor:_anchor
|
||||
limit:_HealthAnchoredQueryLimit
|
||||
resultsHandler:
|
||||
^(HKAnchoredObjectQuery *query, NSArray *sampleObjects, NSArray *deletedObjects, HKQueryAnchor *newAnchor, NSError *error) {
|
||||
handleResults(sampleObjects, newAnchor, 0, error);
|
||||
}];
|
||||
} else if ([HKAnchoredObjectQuery instancesRespondToSelector:@selector(initWithType:predicate:anchor:limit:completionHandler:)]) {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
anchoredQuery = [[HKAnchoredObjectQuery alloc] initWithType:_quantityType
|
||||
predicate:_samplePredicate
|
||||
anchor:_anchorValue
|
||||
limit:_HealthAnchoredQueryLimit
|
||||
completionHandler:
|
||||
^(HKAnchoredObjectQuery *query, NSArray<__kindof HKSample *> *results, NSUInteger newAnchor, NSError *error) {
|
||||
handleResults(results, nil, newAnchor, error);
|
||||
}];
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
else {
|
||||
NSAssert(NO, @"Could not instantiate an HKAnchoredObjectQuery.");
|
||||
}
|
||||
|
||||
[_healthStore executeQuery:anchoredQuery];
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,9 @@
|
||||
*/
|
||||
|
||||
|
||||
@import UIKit;
|
||||
#import "ORKCustomStepView_Internal.h"
|
||||
#import "ORKTypes.h"
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@@ -30,10 +30,12 @@
|
||||
|
||||
|
||||
#import "ORKHolePegTestPlaceContentView.h"
|
||||
#import "ORKHolePegTestPlacePegView.h"
|
||||
#import "ORKHolePegTestPlaceHoleView.h"
|
||||
|
||||
#import "ORKDirectionView.h"
|
||||
#import "ORKHelpers.h"
|
||||
#import "ORKHolePegTestPlaceHoleView.h"
|
||||
#import "ORKHolePegTestPlacePegView.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
#import "ORKSkin.h"
|
||||
|
||||
|
||||
@@ -163,7 +165,7 @@ static const CGFloat ORKHolePegViewDiameter = 88.0f;
|
||||
NSMutableArray *constraintsArray = [NSMutableArray array];
|
||||
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(_progressView, _pegView, _holeView, _directionView);
|
||||
NSDictionary *metrics = @{@"diameter" : @(ORKHolePegViewDiameter)};
|
||||
NSDictionary *metrics = @{@"diameter": @(ORKHolePegViewDiameter)};
|
||||
|
||||
[constraintsArray addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_progressView]-|"
|
||||
|
||||
@@ -29,13 +29,14 @@
|
||||
*/
|
||||
|
||||
|
||||
#import <ResearchKit/ResearchKit_Private.h>
|
||||
@import UIKit;
|
||||
#import "ORKDefines.h"
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKHolePegTestPlaceHoleView : UIView
|
||||
@interface ORKHolePegTestPlaceHoleView : UIView <CAAnimationDelegate>
|
||||
|
||||
@property (nonatomic, assign, getter = isRotated) BOOL rotated;
|
||||
@property (nonatomic, assign, getter = isSuccess) BOOL success;
|
||||
|
||||
@@ -31,6 +31,8 @@
|
||||
|
||||
#import "ORKHolePegTestPlaceHoleView.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
|
||||
|
||||
static const CGFloat ORKPlaceHoleViewRotation = 45.0f;
|
||||
|
||||
@@ -161,9 +163,9 @@ static const CGFloat ORKPlaceHoleViewRotation = 45.0f;
|
||||
}
|
||||
|
||||
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag {
|
||||
__weak typeof(self) weakSelf = self;
|
||||
ORKWeakTypeOf(self) weakSelf = self;
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.7f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
typeof(self) strongSelf = weakSelf;
|
||||
ORKStrongTypeOf(self) strongSelf = weakSelf;
|
||||
strongSelf.success = NO;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -29,7 +29,8 @@
|
||||
*/
|
||||
|
||||
|
||||
#import <ResearchKit/ResearchKit_Private.h>
|
||||
@import UIKit;
|
||||
#import "ORKDefines.h"
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@@ -29,7 +29,9 @@
|
||||
*/
|
||||
|
||||
|
||||
#import <ResearchKit/ResearchKit_Private.h>
|
||||
@import Foundation;
|
||||
#import <ResearchKit/ORKDefines.h>
|
||||
#import <ResearchKit/ORKActiveStep.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
@@ -39,7 +41,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@property (nonatomic, assign) ORKBodySagittal movingDirection;
|
||||
@property (nonatomic, assign, getter = isDominantHandTested) BOOL dominantHandTested;
|
||||
@property (nonatomic, assign) int numberOfPegs;
|
||||
@property (nonatomic, assign) NSInteger numberOfPegs;
|
||||
@property (nonatomic, assign) double threshold;
|
||||
@property (nonatomic, assign, getter = isRotated) BOOL rotated;
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
|
||||
#import "ORKHolePegTestPlaceStep.h"
|
||||
#import "ORKHolePegTestPlaceStepViewController.h"
|
||||
#import "ORKHelpers_Internal.h"
|
||||
|
||||
|
||||
@implementation ORKHolePegTestPlaceStep
|
||||
@@ -73,7 +74,7 @@
|
||||
}
|
||||
|
||||
if (self.stepDuration < ORKHolePegTestMinimumDuration) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"duration can not be shorter than %@ seconds.", @(ORKHolePegTestMinimumDuration)] userInfo:nil];
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"duration cannot be shorter than %@ seconds.", @(ORKHolePegTestMinimumDuration)] userInfo:nil];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,4 +82,55 @@
|
||||
return NO;
|
||||
}
|
||||
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [super initWithCoder:aDecoder];
|
||||
if (self) {
|
||||
ORK_DECODE_ENUM(aDecoder, movingDirection);
|
||||
ORK_DECODE_BOOL(aDecoder, dominantHandTested);
|
||||
ORK_DECODE_INTEGER(aDecoder, numberOfPegs);
|
||||
ORK_DECODE_DOUBLE(aDecoder, threshold);
|
||||
ORK_DECODE_BOOL(aDecoder, rotated);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
[super encodeWithCoder:aCoder];
|
||||
ORK_ENCODE_ENUM(aCoder, movingDirection);
|
||||
ORK_ENCODE_BOOL(aCoder, dominantHandTested);
|
||||
ORK_ENCODE_INTEGER(aCoder, numberOfPegs);
|
||||
ORK_ENCODE_DOUBLE(aCoder, threshold);
|
||||
ORK_ENCODE_BOOL(aCoder, rotated);
|
||||
}
|
||||
|
||||
- (instancetype)copyWithZone:(NSZone *)zone {
|
||||
__typeof(self) step = [super copyWithZone:zone];
|
||||
step.movingDirection = self.movingDirection;
|
||||
step.dominantHandTested = self.dominantHandTested;
|
||||
step.numberOfPegs = self.numberOfPegs;
|
||||
step.threshold = self.threshold;
|
||||
step.rotated = self.rotated;
|
||||
return step;
|
||||
}
|
||||
|
||||
- (NSUInteger)hash {
|
||||
return [super hash] ^ self.movingDirection ^ self.dominantHandTested ^ self.numberOfPegs ^ (NSInteger)(self.threshold * 100) ^ self.rotated;
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object {
|
||||
BOOL isParentSame = [super isEqual:object];
|
||||
|
||||
__typeof(self) castObject = object;
|
||||
return (isParentSame &&
|
||||
(self.movingDirection == castObject.movingDirection) &&
|
||||
(self.dominantHandTested == castObject.dominantHandTested) &&
|
||||
(self.numberOfPegs == castObject.numberOfPegs) &&
|
||||
(self.threshold == castObject.threshold) &&
|
||||
(self.rotated == castObject.rotated));
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -29,7 +29,9 @@
|
||||
*/
|
||||
|
||||
|
||||
#import <ResearchKit/ResearchKit_Private.h>
|
||||
@import UIKit;
|
||||
#import <ResearchKit/ORKDefines.h>
|
||||
#import <ResearchKit/ORKActiveStepViewController.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@@ -30,11 +30,19 @@
|
||||
|
||||
|
||||
#import "ORKHolePegTestPlaceStepViewController.h"
|
||||
#import "ORKHolePegTestPlaceStep.h"
|
||||
#import "ORKHolePegTestPlaceContentView.h"
|
||||
#import "ORKActiveStepViewController_internal.h"
|
||||
#import "ORKStepViewController_internal.h"
|
||||
|
||||
#import "ORKActiveStepView.h"
|
||||
#import "ORKHolePegTestPlaceContentView.h"
|
||||
|
||||
#import "ORKActiveStepViewController_Internal.h"
|
||||
#import "ORKStepViewController_Internal.h"
|
||||
#import "ORKTaskViewController.h"
|
||||
|
||||
#import "ORKHolePegTestPlaceStep.h"
|
||||
#import "ORKNavigableOrderedTask.h"
|
||||
#import "ORKResult.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
|
||||
|
||||
@interface ORKHolePegTestPlaceStepViewController () <ORKHolePegTestPlaceContentViewDelegate>
|
||||
|
||||
@@ -29,7 +29,9 @@
|
||||
*/
|
||||
|
||||
|
||||
@import UIKit;
|
||||
#import "ORKCustomStepView_Internal.h"
|
||||
#import "ORKTypes.h"
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@@ -30,10 +30,12 @@
|
||||
|
||||
|
||||
#import "ORKHolePegTestRemoveContentView.h"
|
||||
|
||||
#import "ORKDirectionView.h"
|
||||
#import "ORKHolePegTestRemovePegView.h"
|
||||
#import "ORKSeparatorView.h"
|
||||
#import "ORKDirectionView.h"
|
||||
#import "ORKHelpers.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
#import "ORKSkin.h"
|
||||
|
||||
|
||||
@@ -156,7 +158,7 @@ static const CGFloat PegViewSeparatorWidth = 2.0f;
|
||||
NSMutableArray *constraintsArray = [NSMutableArray array];
|
||||
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(_progressView, _container, _pegView, _separatorView, _directionView);
|
||||
NSDictionary *metrics = @{@"diameter" : @(PegViewDiameter), @"separator" : @(PegViewSeparatorWidth), @"margin" : @((1 + self.threshold) * PegViewDiameter)};
|
||||
NSDictionary *metrics = @{@"diameter": @(PegViewDiameter), @"separator": @(PegViewSeparatorWidth), @"margin": @((1 + self.threshold) * PegViewDiameter)};
|
||||
|
||||
[constraintsArray addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_progressView]-|"
|
||||
|
||||
@@ -29,13 +29,14 @@
|
||||
*/
|
||||
|
||||
|
||||
#import <ResearchKit/ResearchKit_Private.h>
|
||||
@import UIKit;
|
||||
#import "ORKDefines.h"
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKHolePegTestRemovePegView : UIView
|
||||
@interface ORKHolePegTestRemovePegView : UIView <CAAnimationDelegate>
|
||||
|
||||
@property (nonatomic, assign, getter = isSuccess) BOOL success;
|
||||
|
||||
|
||||
@@ -29,7 +29,9 @@
|
||||
*/
|
||||
|
||||
|
||||
#import <ResearchKit/ResearchKit_Private.h>
|
||||
@import Foundation;
|
||||
#import <ResearchKit/ORKDefines.h>
|
||||
#import <ResearchKit/ORKActiveStep.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
@@ -39,7 +41,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@property (nonatomic, assign) ORKBodySagittal movingDirection;
|
||||
@property (nonatomic, assign, getter = isDominantHandTested) BOOL dominantHandTested;
|
||||
@property (nonatomic, assign) int numberOfPegs;
|
||||
@property (nonatomic, assign) NSInteger numberOfPegs;
|
||||
@property (nonatomic, assign) double threshold;
|
||||
|
||||
@end
|
||||
|
||||
@@ -30,8 +30,11 @@
|
||||
|
||||
|
||||
#import "ORKHolePegTestRemoveStep.h"
|
||||
|
||||
#import "ORKHolePegTestRemoveStepViewController.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
|
||||
|
||||
@implementation ORKHolePegTestRemoveStep
|
||||
|
||||
@@ -73,7 +76,7 @@
|
||||
}
|
||||
|
||||
if (self.stepDuration < ORKHolePegTestMinimumDuration) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"duration can not be shorter than %@ seconds.", @(ORKHolePegTestMinimumDuration)] userInfo:nil];
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"duration cannot be shorter than %@ seconds.", @(ORKHolePegTestMinimumDuration)] userInfo:nil];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,4 +84,51 @@
|
||||
return NO;
|
||||
}
|
||||
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [super initWithCoder:aDecoder];
|
||||
if (self) {
|
||||
ORK_DECODE_ENUM(aDecoder, movingDirection);
|
||||
ORK_DECODE_BOOL(aDecoder, dominantHandTested);
|
||||
ORK_DECODE_INTEGER(aDecoder, numberOfPegs);
|
||||
ORK_DECODE_DOUBLE(aDecoder, threshold);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
[super encodeWithCoder:aCoder];
|
||||
ORK_ENCODE_ENUM(aCoder, movingDirection);
|
||||
ORK_ENCODE_BOOL(aCoder, dominantHandTested);
|
||||
ORK_ENCODE_INTEGER(aCoder, numberOfPegs);
|
||||
ORK_ENCODE_DOUBLE(aCoder, threshold);
|
||||
}
|
||||
|
||||
- (instancetype)copyWithZone:(NSZone *)zone {
|
||||
__typeof(self) step = [super copyWithZone:zone];
|
||||
step.movingDirection = self.movingDirection;
|
||||
step.dominantHandTested = self.dominantHandTested;
|
||||
step.numberOfPegs = self.numberOfPegs;
|
||||
step.threshold = self.threshold;
|
||||
return step;
|
||||
}
|
||||
|
||||
- (NSUInteger)hash {
|
||||
return [super hash] ^ self.movingDirection ^ self.dominantHandTested ^ self.numberOfPegs ^ (NSInteger)(self.threshold * 100);
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object {
|
||||
BOOL isParentSame = [super isEqual:object];
|
||||
|
||||
__typeof(self) castObject = object;
|
||||
return (isParentSame &&
|
||||
(self.movingDirection == castObject.movingDirection) &&
|
||||
(self.dominantHandTested == castObject.dominantHandTested) &&
|
||||
(self.numberOfPegs == castObject.numberOfPegs) &&
|
||||
(self.threshold == castObject.threshold));
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -29,7 +29,9 @@
|
||||
*/
|
||||
|
||||
|
||||
#import <ResearchKit/ResearchKit_Private.h>
|
||||
@import UIKit;
|
||||
#import <ResearchKit/ORKDefines.h>
|
||||
#import <ResearchKit/ORKActiveStepViewController.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@@ -30,11 +30,18 @@
|
||||
|
||||
|
||||
#import "ORKHolePegTestRemoveStepViewController.h"
|
||||
#import "ORKHolePegTestRemoveStep.h"
|
||||
#import "ORKHolePegTestRemoveContentView.h"
|
||||
#import "ORKActiveStepViewController_internal.h"
|
||||
#import "ORKStepViewController_internal.h"
|
||||
|
||||
#import "ORKActiveStepView.h"
|
||||
#import "ORKHolePegTestRemoveContentView.h"
|
||||
|
||||
#import "ORKActiveStepViewController_Internal.h"
|
||||
#import "ORKStepViewController_Internal.h"
|
||||
#import "ORKTaskViewController.h"
|
||||
|
||||
#import "ORKHolePegTestRemoveStep.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
#import "ORKResult.h"
|
||||
|
||||
|
||||
@interface ORKHolePegTestRemoveStepViewController () <ORKHolePegTestRemoveContentViewDelegate>
|
||||
|
||||
@@ -29,8 +29,9 @@
|
||||
*/
|
||||
|
||||
|
||||
@import UIKit;
|
||||
@import CoreLocation;
|
||||
#import <ResearchKit/ORKRecorder.h>
|
||||
#import <CoreLocation/CoreLocation.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@@ -30,11 +30,14 @@
|
||||
|
||||
|
||||
#import "ORKLocationRecorder.h"
|
||||
#import <CoreLocation/CoreLocation.h>
|
||||
#import "CLLocation+ORKJSONDictionary.h"
|
||||
|
||||
#import "ORKDataLogger.h"
|
||||
|
||||
#import "ORKRecorder_Internal.h"
|
||||
#import "ORKRecorder_Private.h"
|
||||
|
||||
#import "CLLocation+ORKJSONDictionary.h"
|
||||
|
||||
#import <CoreLocation/CoreLocation.h>
|
||||
|
||||
|
||||
@interface ORKLocationRecorder () <CLLocationManagerDelegate> {
|
||||
@@ -76,10 +79,10 @@
|
||||
[super start];
|
||||
|
||||
if (!_logger) {
|
||||
NSError *err = nil;
|
||||
_logger = [self makeJSONDataLoggerWithError:&err];
|
||||
NSError *error = nil;
|
||||
_logger = [self makeJSONDataLoggerWithError:&error];
|
||||
if (!_logger) {
|
||||
[self finishRecordingWithError:err];
|
||||
[self finishRecordingWithError:error];
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -94,7 +97,7 @@
|
||||
if (!self.locationManager) {
|
||||
NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain
|
||||
code:NSFeatureUnsupportedError
|
||||
userInfo:@{@"recorder" : self}];
|
||||
userInfo:@{@"recorder": self}];
|
||||
[self finishRecordingWithError:error];
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,8 @@
|
||||
*/
|
||||
|
||||
|
||||
#import <ResearchKit/ResearchKit_Private.h>
|
||||
@import UIKit;
|
||||
#import "ORKTypes.h"
|
||||
#import "ORKCustomStepView_Internal.h"
|
||||
|
||||
|
||||
|
||||
@@ -30,12 +30,14 @@
|
||||
|
||||
|
||||
#import "ORKPSATContentView.h"
|
||||
#import "ORKSkin.h"
|
||||
|
||||
#import "ORKBorderedButton.h"
|
||||
#import "ORKPSATKeyboardView.h"
|
||||
#import "ORKTapCountLabel.h"
|
||||
#import "ORKBorderedButton.h"
|
||||
#import "ORKVoiceEngine.h"
|
||||
#import "ORKHelpers.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
#import "ORKSkin.h"
|
||||
|
||||
|
||||
@interface ORKPSATContentView ()
|
||||
|
||||
@@ -29,7 +29,8 @@
|
||||
*/
|
||||
|
||||
|
||||
#import <ResearchKit/ResearchKit.h>
|
||||
@import UIKit;
|
||||
#import "ORKDefines.h"
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
|
||||
|
||||
#import "ORKPSATKeyboardView.h"
|
||||
|
||||
#import "ORKBorderedButton.h"
|
||||
|
||||
|
||||
|
||||
@@ -29,7 +29,9 @@
|
||||
*/
|
||||
|
||||
|
||||
#import <ResearchKit/ResearchKit_Private.h>
|
||||
@import Foundation;
|
||||
#import <ResearchKit/ORKDefines.h>
|
||||
#import <ResearchKit/ORKActiveStep.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@@ -30,8 +30,11 @@
|
||||
|
||||
|
||||
#import "ORKPSATStep.h"
|
||||
|
||||
#import "ORKPSATStepViewController.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
|
||||
|
||||
@implementation ORKPSATStep
|
||||
|
||||
@@ -90,4 +93,51 @@
|
||||
return NO;
|
||||
}
|
||||
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [super initWithCoder:aDecoder];
|
||||
if (self) {
|
||||
ORK_DECODE_ENUM(aDecoder, presentationMode);
|
||||
ORK_DECODE_DOUBLE(aDecoder, interStimulusInterval);
|
||||
ORK_DECODE_DOUBLE(aDecoder, stimulusDuration);
|
||||
ORK_DECODE_INTEGER(aDecoder, seriesLength);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
[super encodeWithCoder:aCoder];
|
||||
ORK_ENCODE_ENUM(aCoder, presentationMode);
|
||||
ORK_ENCODE_DOUBLE(aCoder, interStimulusInterval);
|
||||
ORK_ENCODE_DOUBLE(aCoder, stimulusDuration);
|
||||
ORK_ENCODE_INTEGER(aCoder, seriesLength);
|
||||
}
|
||||
|
||||
- (instancetype)copyWithZone:(NSZone *)zone {
|
||||
ORKPSATStep *step = [super copyWithZone:zone];
|
||||
step.presentationMode = self.presentationMode;
|
||||
step.interStimulusInterval = self.interStimulusInterval;
|
||||
step.stimulusDuration = self.stimulusDuration;
|
||||
step.seriesLength = self.seriesLength;
|
||||
return step;
|
||||
}
|
||||
|
||||
- (NSUInteger)hash {
|
||||
return [super hash] ^ self.presentationMode ^ (NSInteger)(self.interStimulusInterval*100) ^ (NSInteger)(self.stimulusDuration*100) ^ self.seriesLength;
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object {
|
||||
BOOL isParentSame = [super isEqual:object];
|
||||
|
||||
__typeof(self) castObject = object;
|
||||
return (isParentSame &&
|
||||
(self.presentationMode == castObject.presentationMode) &&
|
||||
(self.interStimulusInterval == castObject.interStimulusInterval) &&
|
||||
(self.stimulusDuration == castObject.stimulusDuration) &&
|
||||
(self.seriesLength == castObject.seriesLength));
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -29,7 +29,9 @@
|
||||
*/
|
||||
|
||||
|
||||
#import <ResearchKit/ResearchKit_Private.h>
|
||||
@import UIKit;
|
||||
#import <ResearchKit/ORKDefines.h>
|
||||
#import <ResearchKit/ORKActiveStepViewController.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@@ -30,13 +30,19 @@
|
||||
|
||||
|
||||
#import "ORKPSATStepViewController.h"
|
||||
#import "ORKActiveStepViewController_Internal.h"
|
||||
#import "ORKStepViewController_Internal.h"
|
||||
#import "ORKPSATContentView.h"
|
||||
#import "ORKPSATStep.h"
|
||||
#import "ORKVerticalContainerView.h"
|
||||
|
||||
#import "ORKActiveStepTimer.h"
|
||||
#import "ORKActiveStepView.h"
|
||||
#import "ORKPSATContentView.h"
|
||||
#import "ORKPSATKeyboardView.h"
|
||||
#import "ORKVerticalContainerView.h"
|
||||
|
||||
#import "ORKActiveStepViewController_Internal.h"
|
||||
#import "ORKPSATStep.h"
|
||||
#import "ORKResult.h"
|
||||
#import "ORKStepViewController_Internal.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
|
||||
|
||||
@interface ORKPSATStepViewController () <ORKPSATKeyboardViewDelegate>
|
||||
@@ -52,6 +58,7 @@
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKPSATStepViewController
|
||||
|
||||
- (instancetype)initWithStep:(ORKStep *)step {
|
||||
@@ -65,6 +72,7 @@
|
||||
}
|
||||
|
||||
- (ORKPSATStep *)psatStep {
|
||||
NSAssert(self.step == nil || [self.step isKindOfClass:[ORKPSATStep class]], @"Step class must be subclass of ORKPSATStep.");
|
||||
return (ORKPSATStep *)self.step;
|
||||
}
|
||||
|
||||
@@ -155,12 +163,12 @@
|
||||
([self psatStep].interStimulusInterval - [self psatStep].stimulusDuration) > 0.05 ) {
|
||||
|
||||
// Don't show `-` if the difference between stimulusDuration and interStimulusInterval is less than timer's resolution.
|
||||
__weak typeof(self) weakSelf = self;
|
||||
ORKWeakTypeOf(self) weakSelf = self;
|
||||
self.clearDigitsTimer = [[ORKActiveStepTimer alloc] initWithDuration:[self psatStep].stepDuration
|
||||
interval:[self psatStep].interStimulusInterval
|
||||
runtime:-[self psatStep].stimulusDuration
|
||||
handler:^(ORKActiveStepTimer *timer, BOOL finished) {
|
||||
typeof(self) strongSelf = weakSelf;
|
||||
ORKStrongTypeOf(self) strongSelf = weakSelf;
|
||||
[strongSelf clearDigitsTimerFired];
|
||||
}];
|
||||
[self.clearDigitsTimer resume];
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
*/
|
||||
|
||||
|
||||
@import UIKit;
|
||||
#import <ResearchKit/ORKRecorder.h>
|
||||
|
||||
|
||||
|
||||
@@ -30,10 +30,13 @@
|
||||
|
||||
|
||||
#import "ORKPedometerRecorder.h"
|
||||
|
||||
#import "ORKDataLogger.h"
|
||||
#import "CMPedometerData+ORKJSONDictionary.h"
|
||||
|
||||
#import "ORKRecorder_Internal.h"
|
||||
#import "ORKRecorder_Private.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
#import "CMPedometerData+ORKJSONDictionary.h"
|
||||
|
||||
|
||||
@interface ORKPedometerRecorder () {
|
||||
@@ -91,10 +94,10 @@
|
||||
_totalDistance = -1;
|
||||
|
||||
if (!_logger) {
|
||||
NSError *err = nil;
|
||||
_logger = [self makeJSONDataLoggerWithError:&err];
|
||||
NSError *error = nil;
|
||||
_logger = [self makeJSONDataLoggerWithError:&error];
|
||||
if (!_logger) {
|
||||
[self finishRecordingWithError:err];
|
||||
[self finishRecordingWithError:error];
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -104,25 +107,25 @@
|
||||
if (![[self.pedometer class] isStepCountingAvailable]) {
|
||||
[self finishRecordingWithError:[NSError errorWithDomain:NSCocoaErrorDomain
|
||||
code:NSFeatureUnsupportedError
|
||||
userInfo:@{@"recorder" : self}]];
|
||||
userInfo:@{@"recorder": self}]];
|
||||
return;
|
||||
}
|
||||
|
||||
_isRecording = YES;
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
ORKWeakTypeOf(self) weakSelf = self;
|
||||
[self.pedometer startPedometerUpdatesFromDate:[NSDate date] withHandler:^(CMPedometerData *pedometerData, NSError *error) {
|
||||
|
||||
BOOL success = NO;
|
||||
if (pedometerData) {
|
||||
success = [_logger append:[pedometerData ork_JSONDictionary] error:&error];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
__typeof(self) strongSelf = weakSelf;
|
||||
ORKStrongTypeOf(self) strongSelf = weakSelf;
|
||||
[strongSelf updateStatisticsWithData:pedometerData];
|
||||
});
|
||||
}
|
||||
if (!success || error) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
__typeof(self) strongSelf = weakSelf;
|
||||
ORKStrongTypeOf(self) strongSelf = weakSelf;
|
||||
[strongSelf finishRecordingWithError:error];
|
||||
});
|
||||
}
|
||||
|
||||
+12
-17
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
Copyright (c) 2016, Darren Levy. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
@@ -29,28 +29,23 @@
|
||||
*/
|
||||
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "ORKSignatureView.h"
|
||||
@import Foundation;
|
||||
#import <ResearchKit/ORKDefines.h>
|
||||
#import <ResearchKit/ORKActiveStep.h>
|
||||
#import <ResearchKit/ORKOrderedTask.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class ORKConsentSignatureController;
|
||||
/**
|
||||
The `ORKRangeOfMotionStep` class represents a step that takes a range of motion measurement.
|
||||
*/
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKRangeOfMotionStep : ORKActiveStep
|
||||
|
||||
@protocol ORKConsentSignatureControllerDelegate <NSObject>
|
||||
@property (nonatomic, assign) ORKPredefinedTaskLimbOption limbOption; //The left and/or right limb to be tested during the task
|
||||
|
||||
- (void)consentSignatureControllerDidSign:(ORKConsentSignatureController *)consentSignatureController;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface ORKConsentSignatureController : UIViewController<ORKSignatureViewDelegate>
|
||||
|
||||
@property (nonatomic, weak, nullable) id<ORKConsentSignatureControllerDelegate> delegate;
|
||||
|
||||
@property (nonatomic, strong, readonly, nullable) ORKSignatureView *signatureView;
|
||||
|
||||
@property (nonatomic, strong, nullable) NSString *localizedContinueButtonTitle;
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier limbOption:(ORKPredefinedTaskLimbOption)limbOption;
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
Copyright (c) 2016, Darren Levy. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKRangeOfMotionStep.h"
|
||||
#import "ORKRangeOfMotionStepViewController.h"
|
||||
#import "ORKHelpers_Internal.h"
|
||||
|
||||
|
||||
@implementation ORKRangeOfMotionStep
|
||||
|
||||
+ (Class)stepViewControllerClass {
|
||||
return [ORKRangeOfMotionStepViewController class];
|
||||
}
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier limbOption:(ORKPredefinedTaskLimbOption)limbOption {
|
||||
self = [super initWithIdentifier:identifier];
|
||||
if (self) {
|
||||
self.shouldVibrateOnStart = YES;
|
||||
self.shouldPlaySoundOnStart = YES;
|
||||
self.shouldVibrateOnFinish = YES;
|
||||
self.shouldPlaySoundOnFinish = YES;
|
||||
self.shouldContinueOnFinish = YES;
|
||||
self.shouldStartTimerAutomatically = YES;
|
||||
self.limbOption = limbOption;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)validateParameters {
|
||||
[super validateParameters];
|
||||
|
||||
if (self.limbOption != ORKPredefinedTaskLimbOptionLeft && self.limbOption != ORKPredefinedTaskLimbOptionRight) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException
|
||||
reason:ORKLocalizedString(@"LIMB_OPTION_LEFT_OR_RIGHT_ERROR", nil)
|
||||
userInfo:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)startsFinished {
|
||||
return NO;
|
||||
}
|
||||
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (instancetype)copyWithZone:(NSZone *)zone {
|
||||
ORKRangeOfMotionStep *step = [super copyWithZone:zone];
|
||||
step.limbOption = self.limbOption;
|
||||
return step;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [super initWithCoder:aDecoder];
|
||||
if (self) {
|
||||
ORK_DECODE_INTEGER(aDecoder, limbOption);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
[super encodeWithCoder:aCoder];
|
||||
ORK_ENCODE_INTEGER(aCoder, limbOption);
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object {
|
||||
BOOL isParentSame = [super isEqual:object];
|
||||
|
||||
__typeof(self) castObject = object;
|
||||
return (isParentSame && (self.limbOption == castObject.limbOption));
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
Copyright (c) 2016, Darren Levy. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import <ResearchKit/ResearchKit.h>
|
||||
#import <CoreMotion/CMDeviceMotion.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
This class is used by the `ORKRangeOfMotionStep.` Its result corresponds to the device's orientation
|
||||
as recorded by CoreMotion.
|
||||
*/
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKRangeOfMotionStepViewController : ORKActiveStepViewController {
|
||||
double _flexedAngle;
|
||||
double _rangeOfMotionAngle;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,216 @@
|
||||
/*
|
||||
Copyright (c) 2016, Darren Levy. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKRangeOfMotionStepViewController.h"
|
||||
#import "ORKCustomStepView_Internal.h"
|
||||
#import "ORKHelpers_Internal.h"
|
||||
#import "ORKActiveStepViewController_Internal.h"
|
||||
#import "ORKStepViewController_Internal.h"
|
||||
#import "ORKVerticalContainerView_Internal.h"
|
||||
#import "ORKDeviceMotionRecorder.h"
|
||||
#import "ORKActiveStepView.h"
|
||||
#import "ORKProgressView.h"
|
||||
#import "ORKSkin.h"
|
||||
|
||||
|
||||
#define radiansToDegrees(radians) ((radians) * 180.0 / M_PI)
|
||||
#define allOrientationsForPitch(x, w, y, z) (atan2(2.0 * (x*w + y*z), 1.0 - 2.0 * (x*x + z*z)))
|
||||
|
||||
@interface ORKRangeOfMotionContentView : ORKActiveStepCustomView {
|
||||
NSLayoutConstraint *_topConstraint;
|
||||
}
|
||||
|
||||
@property (nonatomic, strong, readonly) ORKProgressView *progressView;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKRangeOfMotionContentView
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
_progressView = [ORKProgressView new];
|
||||
_progressView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
|
||||
[self addSubview:_progressView];
|
||||
[self setUpConstraints];
|
||||
[self updateConstraintConstantsForWindow:self.window];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)willMoveToWindow:(UIWindow *)newWindow {
|
||||
[super willMoveToWindow:newWindow];
|
||||
[self updateConstraintConstantsForWindow:newWindow];
|
||||
}
|
||||
|
||||
- (void)setUpConstraints {
|
||||
NSMutableArray *constraints = [NSMutableArray new];
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(_progressView);
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_progressView]-(>=0)-|"
|
||||
options:NSLayoutFormatAlignAllCenterX
|
||||
metrics:nil
|
||||
views:views]];
|
||||
_topConstraint = [NSLayoutConstraint constraintWithItem:_progressView
|
||||
attribute:NSLayoutAttributeTop
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeTop
|
||||
multiplier:1.0
|
||||
constant:0.0]; // constant will be set in updateConstraintConstantsForWindow:
|
||||
[constraints addObject:_topConstraint];
|
||||
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_progressView
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
|
||||
[NSLayoutConstraint activateConstraints:constraints];
|
||||
}
|
||||
|
||||
- (void)updateConstraintConstantsForWindow:(UIWindow *)window {
|
||||
const CGFloat CaptionBaselineToProgressTop = 100;
|
||||
const CGFloat CaptionBaselineToStepViewTop = ORKGetMetricForWindow(ORKScreenMetricLearnMoreBaselineToStepViewTop, window);
|
||||
_topConstraint.constant = CaptionBaselineToProgressTop - CaptionBaselineToStepViewTop;
|
||||
}
|
||||
|
||||
- (void)updateConstraints {
|
||||
[self updateConstraintConstantsForWindow:self.window];
|
||||
[super updateConstraints];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface ORKRangeOfMotionStepViewController () <ORKDeviceMotionRecorderDelegate> {
|
||||
ORKRangeOfMotionContentView *_contentView;
|
||||
UITapGestureRecognizer *_gestureRecognizer;
|
||||
CMAttitude *_referenceAttitude;
|
||||
UIInterfaceOrientation _orientation;
|
||||
double _highestAngle;
|
||||
double _lowestAngle;
|
||||
double _lastAngle;
|
||||
}
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKRangeOfMotionStepViewController
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
_contentView = [ORKRangeOfMotionContentView new];
|
||||
_contentView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
self.activeStepView.activeCustomView = _contentView;
|
||||
_gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)];
|
||||
[self.activeStepView addGestureRecognizer:_gestureRecognizer];
|
||||
}
|
||||
|
||||
- (void)handleTap:(UIGestureRecognizer *)sender {
|
||||
[self calculateAndSetFlexedAndExtendedAngles];
|
||||
[self finish];
|
||||
}
|
||||
|
||||
- (void)calculateAndSetFlexedAndExtendedAngles {
|
||||
_flexedAngle = fabs([self getDeviceAngleInDegreesFromAttitude:_referenceAttitude]);
|
||||
|
||||
BOOL rangeOfMotionMoreThan180Degrees = _highestAngle > 175 && _lowestAngle < 175;
|
||||
if (rangeOfMotionMoreThan180Degrees) {
|
||||
_rangeOfMotionAngle = 360 - fabs(_lastAngle);
|
||||
} else {
|
||||
_rangeOfMotionAngle = fabs(_lastAngle);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - ORKDeviceMotionRecorderDelegate
|
||||
|
||||
- (void)deviceMotionRecorderDidUpdateWithMotion:(CMDeviceMotion *)motion {
|
||||
if (!_referenceAttitude) {
|
||||
_referenceAttitude = motion.attitude;
|
||||
}
|
||||
CMAttitude *currentAttitude = [motion.attitude copy];
|
||||
|
||||
[currentAttitude multiplyByInverseOfAttitude:_referenceAttitude];
|
||||
|
||||
double angle = [self getDeviceAngleInDegreesFromAttitude:currentAttitude];
|
||||
|
||||
if (angle > _highestAngle) {
|
||||
_highestAngle = angle;
|
||||
}
|
||||
if (angle < _lowestAngle) {
|
||||
_lowestAngle = angle;
|
||||
}
|
||||
_lastAngle = angle;
|
||||
}
|
||||
|
||||
/*
|
||||
When the device is in Portrait mode, we need to get the attitude's pitch
|
||||
to determine the device's angle. attitude.pitch doesn't return all
|
||||
orientations, so we use the attitude's quaternion to calculate the
|
||||
angle.
|
||||
*/
|
||||
- (double)getDeviceAngleInDegreesFromAttitude:(CMAttitude *)attitude {
|
||||
if (!_orientation) {
|
||||
_orientation = [UIApplication sharedApplication].statusBarOrientation;
|
||||
}
|
||||
double angle;
|
||||
if (UIInterfaceOrientationIsLandscape(_orientation)) {
|
||||
angle = radiansToDegrees(attitude.roll);
|
||||
} else {
|
||||
double x = attitude.quaternion.x;
|
||||
double w = attitude.quaternion.w;
|
||||
double y = attitude.quaternion.y;
|
||||
double z = attitude.quaternion.z;
|
||||
angle = radiansToDegrees(allOrientationsForPitch(x, w, y, z));
|
||||
}
|
||||
return angle;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - ORKActiveTaskViewController
|
||||
|
||||
- (ORKResult *)result {
|
||||
ORKStepResult *stepResult = [super result];
|
||||
|
||||
ORKRangeOfMotionResult *result = [[ORKRangeOfMotionResult alloc] initWithIdentifier:self.step.identifier];
|
||||
result.flexed = _flexedAngle;
|
||||
result.extended = result.flexed - _rangeOfMotionAngle;
|
||||
|
||||
stepResult.results = [self.addedResults arrayByAddingObject:result] ? : @[result];
|
||||
|
||||
return stepResult;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -29,9 +29,12 @@
|
||||
*/
|
||||
|
||||
|
||||
@import UIKit;
|
||||
#import "ORKCustomStepView_Internal.h"
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ORKReactionTimeContentView : ORKActiveStepCustomView
|
||||
|
||||
- (void)setStimulusHidden:(BOOL)hidden;
|
||||
@@ -43,3 +46,5 @@
|
||||
- (void)resetAfterDelay:(NSTimeInterval)delay completion:(nullable void (^)(void))completion;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -30,8 +30,9 @@
|
||||
|
||||
|
||||
#import "ORKReactionTimeContentView.h"
|
||||
#import "ORKReactionTimeStimulusView.h"
|
||||
|
||||
#import "ORKNavigationContainerView.h"
|
||||
#import "ORKReactionTimeStimulusView.h"
|
||||
|
||||
|
||||
@implementation ORKReactionTimeContentView {
|
||||
|
||||
@@ -26,11 +26,13 @@
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
*/
|
||||
|
||||
|
||||
#import <ResearchKit/ResearchKit.h>
|
||||
#import <AudioToolbox/AudioServices.h>
|
||||
@import Foundation;
|
||||
@import AudioToolbox;
|
||||
#import <ResearchKit/ORKDefines.h>
|
||||
#import <ResearchKit/ORKActiveStep.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user