Compare commits
1108 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ce3f17d45f | |||
| e1faf6587c | |||
| 939d298809 | |||
| f179e15e5c | |||
| 6ff53717a4 | |||
| d123688395 | |||
| ca4a954d08 | |||
| 57fbde9b46 | |||
| 0dd06a572b | |||
| a2e4d559bc | |||
| f32c069be5 | |||
| c55ce91fb9 | |||
| 60d2cbeb69 | |||
| 7bd76e6b5e | |||
| 9f198a12b6 | |||
| 86634b140c | |||
| b925584f9c | |||
| 6f0decb0ff | |||
| 812151c51d | |||
| 14715e2c32 | |||
| 1a7efab0e6 | |||
| 5df57bcbda | |||
| a6cea16fef | |||
| 76d4555f74 | |||
| bb50755b2e | |||
| 32b5e37486 | |||
| 419f209346 | |||
| 2338e097c4 | |||
| c6a2660a2e | |||
| 04a1ec81d7 | |||
| a786a661b2 | |||
| db5e374880 | |||
| d2fed86772 | |||
| a8b1c3658e | |||
| 85598e297b | |||
| 71b00ba7f6 | |||
| 82acd5aedb | |||
| 81f4b1e4cc | |||
| 7c80613f1b | |||
| d2c7ac3cd8 | |||
| 2f6247a0ed | |||
| 87034055b4 | |||
| 0093289888 | |||
| f8037c08df | |||
| 24a7d469da | |||
| 2507aa5804 | |||
| 43c544b4d0 | |||
| 38e9b14a87 | |||
| be6811d311 | |||
| fb609dba72 | |||
| 7445f7388a | |||
| 0edc9292b3 | |||
| 28e1b2092e | |||
| f8f30d681f | |||
| 35471350a2 | |||
| 2da1b843d0 | |||
| f10195edc4 | |||
| 770bec9c31 | |||
| 3eea308ed4 | |||
| fda79e945f | |||
| b20fd5755b | |||
| 1d5461a037 | |||
| fab251e623 | |||
| 0d914c493a | |||
| d4a20a79d5 | |||
| 58eaa1d182 | |||
| a6f5c39e2c | |||
| 9da358a1f5 | |||
| a1db939515 | |||
| 9462839950 | |||
| 29a6e21e49 | |||
| 5739faffe6 | |||
| e6e1f49c00 | |||
| 88ea180068 | |||
| fb50458d67 | |||
| 9fd9ea3783 | |||
| 9b4aeab0a1 | |||
| 6afa92169d | |||
| b2496438bf | |||
| 238d8a17fe | |||
| cea248f433 | |||
| 1b4569f316 | |||
| 1c5cab61aa | |||
| b01c7166c4 | |||
| 49166b553f | |||
| ddb815d2cb | |||
| 30aafbd50f | |||
| 6ce92f19e0 | |||
| 18adca5396 | |||
| 5e0b696fef | |||
| adacb612a4 | |||
| f19fd6187a | |||
| 82630bdbf5 | |||
| 68e3f11a74 | |||
| 0c268974f6 | |||
| 33927b17c7 | |||
| 9f00bb182d | |||
| e69751361a | |||
| 57447fb562 | |||
| 9c022c300e | |||
| 445d8af2e3 | |||
| 2769b268e8 | |||
| 5a4c9e172e | |||
| 1c0595f1dc | |||
| 61ba198cc2 | |||
| 54d0ab0e3f | |||
| b74048502f | |||
| e86f3b6491 | |||
| 64a68729a5 | |||
| a289453eaf | |||
| ceefc62cf0 | |||
| 738c6972a4 | |||
| e8ab35abb8 | |||
| efba2dabb3 | |||
| 64cb0d88bd | |||
| 4bfed38f92 | |||
| 96fd5872a7 | |||
| b53cdbc27a | |||
| ebdd27d251 | |||
| 7b97e2d9bb | |||
| afe1d8a8ee | |||
| 8833aa841c | |||
| b6f2ce945d | |||
| 9fc539ec59 | |||
| 14e96bce38 | |||
| 9b8ae3de92 | |||
| 445eb7deb1 | |||
| 149e341352 | |||
| 1350a20f15 | |||
| aaf9d7dde4 | |||
| 852102eb99 | |||
| c1f4392dca | |||
| 335f887932 | |||
| 3ec1a1385b | |||
| d35cfaacb0 | |||
| 1ce5cedd7e | |||
| 1dc55d1a0c | |||
| 6939af6599 | |||
| 6dd1a9d3f1 | |||
| ab281c75ce | |||
| 291a6fdd0f | |||
| 9ba1f76e53 | |||
| 5abdbd97dd | |||
| b0011de863 | |||
| be8ecb84f4 | |||
| 82eb38c815 | |||
| 6d9d488fb4 | |||
| 6f995d1092 | |||
| 02603ee8d7 | |||
| 70e6a7c58e | |||
| 8ea3ef21a9 | |||
| 5f627812f5 | |||
| 758988e1e5 | |||
| a988b4dcfb | |||
| 6784af13ed | |||
| b4e9f2ef81 | |||
| 825241001b | |||
| fae45fe36a | |||
| 8716c92a30 | |||
| ae2ae485db | |||
| 8d0c13d09d | |||
| b869eebe9a | |||
| 655ff5a50e | |||
| e3bc0ecc4e | |||
| b35fbae894 | |||
| 5cb8ad525f | |||
| 586f9950d2 | |||
| 14afba5f60 | |||
| 5b0dc979de | |||
| e270ac4c4d | |||
| 3afd7ed5e4 | |||
| e8d4a17265 | |||
| 6dc4f5f714 | |||
| f2447ad1dc | |||
| 6064dc8253 | |||
| 59cbbf845a | |||
| fac25d9a1f | |||
| 3a277a1658 | |||
| d23f8ee8da | |||
| 4dde26d7fd | |||
| c3bb0ec1f8 | |||
| a36035f4fa | |||
| b4ad468974 | |||
| 04ae04d928 | |||
| 24cd69c075 | |||
| 597d3c6433 | |||
| 3b588e39b3 | |||
| 60d273db6e | |||
| 7ee10516db | |||
| 9468b6e806 | |||
| 56d76705ff | |||
| 78593561d2 | |||
| 0075ae973c | |||
| 85384bbc1d | |||
| e322c39942 | |||
| 64e8560bca | |||
| ae99b8254e | |||
| c62274c103 | |||
| 70d449ba1c | |||
| a2987a4bea | |||
| 064af84e38 | |||
| 462543c95d | |||
| b1ff602e74 | |||
| 2d80083177 | |||
| f14d775a78 | |||
| f0083a3227 | |||
| a0011f8e90 | |||
| 04ce2a3be9 | |||
| a10816efd4 | |||
| d47d1cc221 | |||
| 79a2005505 | |||
| 5f7a28ff3a | |||
| 5029179b83 | |||
| 8d65a9e01e | |||
| e00fc4461c | |||
| 1bfb6f49e1 | |||
| 40db9f36d5 | |||
| c2f1c033c8 | |||
| d8764a7be6 | |||
| 33879662e8 | |||
| 6be21169c6 | |||
| 2f00fa57b2 | |||
| 7a8780a59a | |||
| 230e07cc83 | |||
| 5dd9e4ae24 | |||
| 3194d9fcc9 | |||
| 5145a38e8d | |||
| c32eff2947 | |||
| 4070552d36 | |||
| ad1c024455 | |||
| 66b3f9689d | |||
| 7598debcf6 | |||
| 41a24aeffd | |||
| 515bf1de51 | |||
| 014be09d5b | |||
| a5072c457c | |||
| dab245a415 | |||
| ad88999c7b | |||
| 1ab959d8a8 | |||
| 92028763ad | |||
| cd43c68dc6 | |||
| 714ba82c0c | |||
| fee323588e | |||
| d906b07c55 | |||
| 20739d6df5 | |||
| ffd404ff6b | |||
| b99ec7b5ca | |||
| ee66c66521 | |||
| 6e0ee1d079 | |||
| cda4216d0d | |||
| c2dd0a827c | |||
| 9a7d04b684 | |||
| d90cd52913 | |||
| ce7ff64000 | |||
| 5f37a4d360 | |||
| 4215498fbc | |||
| f15b095e15 | |||
| 1e0c22e364 | |||
| fa66666033 | |||
| b034f195d3 | |||
| 8b943730b4 | |||
| fae4cbfb80 | |||
| a4966c12e6 | |||
| 378befbf4f | |||
| c9292ca9c3 | |||
| 579917ff58 | |||
| 399da05c61 | |||
| 09d7f1f9e4 | |||
| 7e6b7ba6f4 | |||
| aad109b533 | |||
| 61f9470d84 | |||
| 311789b4dc | |||
| 6c52fec789 | |||
| 8b18c901f4 | |||
| a50ae54c6a | |||
| 6026246a4b | |||
| 51f9006a59 | |||
| 7c37a54bb2 | |||
| 537c0a03d5 | |||
| 6e5552d929 | |||
| 58edda4a9c | |||
| 78a32cacc9 | |||
| 2a77a97cc2 | |||
| 07f41ae7d1 | |||
| f222b97ca1 | |||
| 48387caea9 | |||
| 8832fc1a0c | |||
| da76a7a174 | |||
| e5165b8aa5 | |||
| 28b52a7114 | |||
| 8f4bf262b6 | |||
| c83351c201 | |||
| e9306c8965 | |||
| b0ca0c79db | |||
| 71912aeb1d | |||
| 62827cfefe | |||
| bfd209d238 | |||
| a6359e176a | |||
| 25378ac9e0 | |||
| 0fc9bc54af | |||
| d89734a682 | |||
| 318377993c | |||
| 97ed959b62 | |||
| de40dd7802 | |||
| a6d032db7b | |||
| 9453a0c4f7 | |||
| 39138f818b | |||
| ba62daa43c | |||
| c502a8a06d | |||
| 952b837a09 | |||
| 4aa8a67e99 | |||
| 0f1a234076 | |||
| 1b61c97a44 | |||
| 43424a4727 | |||
| c5809e2f71 | |||
| 632bbd399b | |||
| 966f8e737d | |||
| d6297d2300 | |||
| a85ce60704 | |||
| b17f01425c | |||
| 1573b011b6 | |||
| 584394be4f | |||
| 5f568e7516 | |||
| 188a96bb4b | |||
| f066f4ff4b | |||
| 285a04853f | |||
| 255f810b6c | |||
| 84b8501b63 | |||
| 3e51248aae | |||
| 2054b39cc7 | |||
| 979ba19958 | |||
| 00d469ca9b | |||
| 268367bfbe | |||
| f7f6143f34 | |||
| df7428f8c0 | |||
| 019742bfc7 | |||
| 6d20008857 | |||
| 86adf5093f | |||
| 7c9ebe1960 | |||
| ef606e38ac | |||
| b6dce69805 | |||
| c005814613 | |||
| 324048d709 | |||
| 12ef17ec90 | |||
| c856b29933 | |||
| 4e56c56985 | |||
| f85964f2f9 | |||
| 548b38924b | |||
| 5665d02cab | |||
| 5ae1252bff | |||
| 3cc9ddcb02 | |||
| 8a01f9c741 | |||
| 3ed95dec20 | |||
| 3d33bb4061 | |||
| 1838ebd9cd | |||
| e7058383cf | |||
| cafbd3efb4 | |||
| 02fade4c5c | |||
| ecdfacd8e4 | |||
| 4a37a54f24 | |||
| 82d1d32149 | |||
| ea189b85c5 | |||
| 75e5f6846f | |||
| d25ee99dd6 | |||
| ffef2ffef3 | |||
| eef7a42501 | |||
| 01231010cd | |||
| 90448fe9b4 | |||
| 8808da5225 | |||
| 679f8f7f6b | |||
| e258f01e0a | |||
| da117c191f | |||
| 4072b6e285 | |||
| dfda44c635 | |||
| 130c172a6e | |||
| 926d6f4a4b | |||
| 286c26359d | |||
| bf6c081869 | |||
| 981103e54c | |||
| 16c1b736bc | |||
| 6dabdf1ec1 | |||
| d4e88e28cb | |||
| 4fad188807 | |||
| 399ea5b117 | |||
| 6fd991a0d7 | |||
| 88b1559ad9 | |||
| 197e0205c6 | |||
| bdc136b184 | |||
| f98574a483 | |||
| b4e463ed21 | |||
| 36a4d7e200 | |||
| 892404fd59 | |||
| 73bb8b00a5 | |||
| 54442f56cc | |||
| bc3640c776 | |||
| c659231e85 | |||
| 2b7efb3d9b | |||
| 6e1200ec0f | |||
| 34f63d94e1 | |||
| 23c8f0c3a1 | |||
| b7b1cc74eb | |||
| a06c6488a4 | |||
| 80bed1dec2 | |||
| e31b8f21f8 | |||
| 85cd25741a | |||
| 3c01156ea9 | |||
| 45bd2f28e3 | |||
| 71de23ce43 | |||
| e2c081dd98 | |||
| 040cd69ecf | |||
| 6f44f74028 | |||
| c4bfa99597 | |||
| 57cb115ac4 | |||
| fa5214c4bc | |||
| 84ad0d4072 | |||
| 8b9cfb3082 | |||
| 9ed835007f | |||
| df71dd575e | |||
| 23d42e1883 | |||
| 6ee6c36483 | |||
| 1e67f88a7f | |||
| d616b26fc0 | |||
| c291842226 | |||
| 67bc2884bb | |||
| 3b3280bdda | |||
| 4fcb4b8cb6 | |||
| 7d7a25f77c | |||
| 544352a288 | |||
| 33e0f03c37 | |||
| 64f51aaa3c | |||
| f9b7b57e3a | |||
| 333053deb8 | |||
| 5a18717615 | |||
| 6719ad01f0 | |||
| 8155c96d61 | |||
| af6a62483a | |||
| 9d35a3842b | |||
| 5f73e4d1c2 | |||
| 0cca4e0852 | |||
| a823528eef | |||
| 3c413f3088 | |||
| d155da245a | |||
| 5908b3a07b | |||
| e88ed5d551 | |||
| cfeb443255 | |||
| b09c054c32 | |||
| 88c207d285 | |||
| 9813ddc17e | |||
| fa37135471 | |||
| 90f4cc472a | |||
| c3254ecc46 | |||
| 68623b97c2 | |||
| 442e55e14b | |||
| ad707bf2a3 | |||
| f9e5a898a2 | |||
| eef997cc1b | |||
| 4fac6c1c08 | |||
| 3be88f7d38 | |||
| 781d95ab88 | |||
| 69c5c63e7b | |||
| 955f1bede9 | |||
| ededc79153 | |||
| f36a059306 | |||
| 2593b7ae99 | |||
| 1f3c0bd922 | |||
| 143c29ea10 | |||
| 512bed7505 | |||
| 91e8a9a370 | |||
| fd330506f2 | |||
| 600c8f0898 | |||
| 17219a81a2 | |||
| c7a0f893a4 | |||
| fbe97c7c53 | |||
| 1f9ea2351c | |||
| 99db33cbc4 | |||
| 1c52f9421b | |||
| 2ff60bfc91 | |||
| 784b9c266c | |||
| 87d9a720c8 | |||
| 07005babf4 | |||
| 6a877cf70a | |||
| 4358bd903f | |||
| f711545f8f | |||
| 591ee60003 | |||
| 34d2fec94e | |||
| 41e239fa2e | |||
| c05f998007 | |||
| 0559b095af | |||
| 8d95cf9c19 | |||
| 94333e65da | |||
| 6c9312ffd8 | |||
| 9e498684b2 | |||
| 43359003ca | |||
| 8c2b897a8e | |||
| 61a67a4e26 | |||
| 0f22145f37 | |||
| 1bd8764245 | |||
| a6aa2fbab7 | |||
| 43bd4897cd | |||
| ce52ed22cc | |||
| cac4c8bbeb | |||
| 54c3374f93 | |||
| 34d80c8842 | |||
| 25dec24ebf | |||
| 1250da259d | |||
| 58b512172d | |||
| 3c49a02b1c | |||
| df31766f16 | |||
| bbfd809e84 | |||
| 2eee55747a | |||
| 06d1a23032 | |||
| e458ec5845 | |||
| 2bb1eed6f4 | |||
| f66aead4da | |||
| 614b1d7080 | |||
| 8649fa8223 | |||
| fb939304b9 | |||
| 2e549a3825 | |||
| 7aa352d1ca | |||
| 94f82683de | |||
| 3e23cd4b8b | |||
| 849b7cefa5 | |||
| f891adfaa9 | |||
| b29818cb96 | |||
| f8632a371d | |||
| 097ff8dd63 | |||
| 22696a6d38 | |||
| 81acc7bbd9 | |||
| 98980500ec | |||
| 21fdd1364c | |||
| 2ca06c44b2 | |||
| 06326d7ffe | |||
| bc7ea01397 | |||
| dbe906e7ec | |||
| bde087d3b3 | |||
| c3b353783f | |||
| 53bcccdc87 | |||
| 94b644ca6f | |||
| d25bdbe953 | |||
| 3af24d1828 | |||
| 117c84d06a | |||
| 966790eb47 | |||
| 6b3dd56772 | |||
| 180294ce71 | |||
| 10f89bd445 | |||
| 32f580eca9 | |||
| b858656fde | |||
| accc78709c | |||
| 6bc2d35a94 | |||
| 1fe84298a0 | |||
| 5e6442d3a0 | |||
| 8fbcb42170 | |||
| 63272ecbce | |||
| 6c4aafb174 | |||
| ea07a956b2 | |||
| 2ac8d2c5fd | |||
| be0df4d187 | |||
| 07f48c125b | |||
| b44cc8c072 | |||
| 5cd1562f6f | |||
| 2240780a5b | |||
| 01a2de8934 | |||
| fdd7a43d96 | |||
| fa966fa682 | |||
| 775e7f2520 | |||
| ff50ceb63e | |||
| b7b8b751f6 | |||
| fa5bbf3011 | |||
| 952fc8c7ff | |||
| e486bdef88 | |||
| 5047401252 | |||
| 26dd857f0a | |||
| 404e46f84b | |||
| 17679ff56e | |||
| b6b5f0accc | |||
| 6ce52bad33 | |||
| c3cb9e042a | |||
| 1194bafcad | |||
| 22124fc530 | |||
| 8bf354e741 | |||
| e725a6514a | |||
| bd4bf8ec38 | |||
| 0736e68401 | |||
| f85ed88df3 | |||
| b6f4dbc345 | |||
| 853103ef1c | |||
| 622af54829 | |||
| 52f3d906a4 | |||
| 72217721cd | |||
| cde2e256f6 | |||
| 05355b639c | |||
| 0842a0abb1 | |||
| 6cdd9b81b6 | |||
| f3c0bce4ff | |||
| 54de137765 | |||
| b3bae6eebd | |||
| d2f6e1f0d0 | |||
| ecce58e5a1 | |||
| 40062a22a7 | |||
| 9d47a69ccd | |||
| 0ef6913118 | |||
| 66d978b37f | |||
| 324192edef | |||
| 1df428f956 | |||
| 6bcffa2368 | |||
| d9fe0c38bb | |||
| 39d3e3e0f2 | |||
| 9ff14d9f08 | |||
| 65a7fa342c | |||
| ec8a764c06 | |||
| 291fa77606 | |||
| 58b34e98a1 | |||
| b7b7cd26a4 | |||
| a6cbe2a5d0 | |||
| 0ffb0fd90b | |||
| db346ea955 | |||
| 1529cd3a88 | |||
| e8bd33b710 | |||
| 339541a0c8 | |||
| 20ba6f7bf0 | |||
| dcdc1e6686 | |||
| e09ae87e63 | |||
| 1958f073db | |||
| 1cae0db303 | |||
| 4cf953ec0f | |||
| 46f5cf6fd8 | |||
| 977dab78dc | |||
| 0855cabddf | |||
| 38326f25f4 | |||
| d97c70dee6 | |||
| 3545133d51 | |||
| f3f0d5540d | |||
| 46d1e57d81 | |||
| 49e9181001 | |||
| e22e1144fb | |||
| ddb3d459f5 | |||
| 3c75a76ae2 | |||
| 14743d1868 | |||
| 462cf41773 | |||
| 9d1918b361 | |||
| fac2badb47 | |||
| 9c17ac09ac | |||
| bfaac8ad2f | |||
| cb001e2f4e | |||
| c7a91a19a9 | |||
| 164a7a4415 | |||
| df71c79fe5 | |||
| 88a2b86d3f | |||
| 93b5df5541 | |||
| 492ce201f1 | |||
| d8c2bc8b55 | |||
| 8481b66de4 | |||
| 3462909cd7 | |||
| 16477efe6a | |||
| 4fa8de266c | |||
| 38b6310d10 | |||
| 7d833c8262 | |||
| 14aa32074e | |||
| 67fee928e0 | |||
| 94db7d392c | |||
| 38135994ab | |||
| 23246cbcbb | |||
| 267ff6e563 | |||
| d7fe76008a | |||
| 4a494bda70 | |||
| c50120d64f | |||
| d2cf8eba0a | |||
| e0ca725a42 | |||
| ce735a1835 | |||
| fb316bb744 | |||
| cc9f194a2d | |||
| c787d2a134 | |||
| 536d28f6d9 | |||
| f20d81ee3f | |||
| 0518fd777d | |||
| 9c1cf6c6bf | |||
| 51dc62a780 | |||
| fb5a7a3f48 | |||
| 3d91a933b9 | |||
| f1e75f2ce7 | |||
| 7dc37149f3 | |||
| c50e88b3c7 | |||
| 90a1b8407c | |||
| 46990ba807 | |||
| 6795808555 | |||
| 3b6262c4fe | |||
| 9659fbb530 | |||
| 3b45807b87 | |||
| 8ff919dc79 | |||
| df3883988b | |||
| 63ce90d4d3 | |||
| 74d403b52d | |||
| 3541f2b253 | |||
| a923c426ed | |||
| 2c2e5254bc | |||
| ce28538a36 | |||
| 1de5fcc6c2 | |||
| ee90b17351 | |||
| f8211adf83 | |||
| f5b440fd1f | |||
| 619e9434a9 | |||
| 360ccab3df | |||
| 2e01d033fb | |||
| dab4bcc047 | |||
| b68ad7b906 | |||
| b7a70253d6 | |||
| df04bf8235 | |||
| a05adeb264 | |||
| f56f6312e6 | |||
| 912631f4d8 | |||
| 61e8746cbd | |||
| 51d666f1e8 | |||
| 6e8006b094 | |||
| 5aabe018f0 | |||
| 62745570c5 | |||
| 9a49196cd6 | |||
| 9603fb8461 | |||
| c14dbe8fd5 | |||
| 38cf7a4541 | |||
| 78cafb6a52 | |||
| 4725b15bcd | |||
| d929de736e | |||
| d4b73a8f42 | |||
| f0a9e7d443 | |||
| 55198e7dad | |||
| be0c887b85 | |||
| 9c1bbbbaac | |||
| d1126e228c | |||
| 623e33841f | |||
| 3dedf8c373 | |||
| c42f29fdad | |||
| 5d47c8e183 | |||
| a56b351593 | |||
| fd6e941159 | |||
| 115591c032 | |||
| 152557e5e4 | |||
| f16bba3076 | |||
| 21427d5795 | |||
| 972879a85e | |||
| ae4af75736 | |||
| b738688f00 | |||
| fc81624880 | |||
| a622726b1b | |||
| caec12f51c | |||
| d090892754 | |||
| e1f54278ca | |||
| 7a26e368e7 | |||
| ec63648083 | |||
| 806c7917d3 | |||
| 546b965c5b | |||
| efb78baf7d | |||
| 4cd9bd8122 | |||
| 37ca7a3704 | |||
| 236a4026ce | |||
| 3e8bf44aea | |||
| 978a585cde | |||
| aa5219cef6 | |||
| c20126a9ef | |||
| 466dbb98c5 | |||
| 9169f9730f | |||
| 94c902d721 | |||
| 29c2829df0 | |||
| 075dc1f7b6 | |||
| 8b4294f0dd | |||
| 27397735f4 | |||
| e3c596ddb1 | |||
| b948c08d8f | |||
| 5dd23fbb54 | |||
| 9ff7710b14 | |||
| 40e50fb0e6 | |||
| 5ba33f832d | |||
| c34852d677 | |||
| 46bd241ca3 | |||
| b45628d543 | |||
| 9ea4786e21 | |||
| 6b03670808 | |||
| d4d874e312 | |||
| 6abc57b566 | |||
| d5fd81fd50 | |||
| bfdebd914c | |||
| 19fce1ac39 | |||
| 117545160e | |||
| 703155eb72 | |||
| c86bc22f71 | |||
| 31b792a795 | |||
| 70c8ef2e30 | |||
| 833f81296a | |||
| bdf032553d | |||
| 03cb3606c5 | |||
| 4d5f3307e5 | |||
| 9498cda223 | |||
| b338926f56 | |||
| 75fdaf6b09 | |||
| 2959667c65 | |||
| db1f2a9ba5 | |||
| 1d4d307344 | |||
| ac399005ba | |||
| 0d45511b15 | |||
| 19b5b34cc4 | |||
| 2e7c58f48a | |||
| 2a99e7d54f | |||
| 378f5140de | |||
| 3027038744 | |||
| 09e8b1d494 | |||
| 4881a32004 | |||
| 9832cbc91f | |||
| 5bc04aca3f | |||
| 6030efaa96 | |||
| 0604f586df | |||
| e59576640a | |||
| 3d44ed80c6 | |||
| 3378ab16f2 | |||
| 8a5b6cc28d | |||
| cc959e266f | |||
| 5e222351dc | |||
| cf3ea70f77 | |||
| 71c97a72b6 | |||
| 4639a41eea | |||
| 965b39fe4a | |||
| 4a55ec228c | |||
| 14dd7a7c73 | |||
| 1b0d93e247 | |||
| 4047451e4d | |||
| f602e85b75 | |||
| 3ad592e27c | |||
| 7ab47f3a88 | |||
| fe82617dc5 | |||
| ed740fe7d3 | |||
| 621652b26f | |||
| a0adc6c557 | |||
| 227e5af903 | |||
| 01d2c72c2d | |||
| fcb9fb61b0 | |||
| 75c05d200b | |||
| 9e12bed600 | |||
| b3c64fc66b | |||
| edb78414ec | |||
| 1d89b53c78 | |||
| 706244f95d | |||
| 643be7ee8b | |||
| 0ba31ce525 | |||
| efc1394987 | |||
| 0997555a34 | |||
| b48ac27ec8 | |||
| dc35088d39 | |||
| 139a3f6d00 | |||
| 4c25cc50ee | |||
| a48796942c | |||
| 136471090e | |||
| a27ecaa20a | |||
| 31ea7908a2 | |||
| 2930653a98 | |||
| 9604154b17 | |||
| 709dec2841 | |||
| e3cca38a69 | |||
| 66f5452a35 | |||
| 9b4f0a404d | |||
| dbdabb49c2 | |||
| ac1cf8e16e | |||
| 226791c774 | |||
| 08a378b6b8 | |||
| 71008fcc05 | |||
| d50d647ffd | |||
| 1a8f9e81bd | |||
| a200c992ae | |||
| 91821ca5ae | |||
| a739df5f69 | |||
| 5cd75fc585 | |||
| 28a54aac7c | |||
| 35841e2ba8 | |||
| 77bb256cfa | |||
| 74ab1b3726 | |||
| 49256010d5 | |||
| e15f56ff6e | |||
| 3ed725399c | |||
| 3e48fc66a8 | |||
| ae6fdc65d2 | |||
| 74a3f4b589 | |||
| 2df6cc99e1 | |||
| 713b27060f | |||
| 8cb90d81c1 | |||
| 1f3ea67c73 | |||
| d18907ea56 | |||
| ef2eec6926 | |||
| 553c675eab | |||
| b8e24f456d | |||
| 4a518c8d9a | |||
| eb30a43ac6 | |||
| 47babbc318 | |||
| 711fed4ff5 | |||
| 0c2bdc488f | |||
| 4cbd40f0d7 | |||
| 0df2d9a98b | |||
| 6d1e80a583 | |||
| daa7f7cd52 | |||
| 4f792442b1 | |||
| 1e8002279d | |||
| 9eb1d445bb | |||
| 1b1954e9ca | |||
| 46236a4a08 | |||
| ee2127397b | |||
| ac1dd33c34 | |||
| 8586381fb6 | |||
| e79c7d25d4 | |||
| 21dba1f124 | |||
| 3251a5226c | |||
| ccf6b461bc | |||
| 45215ecb0e | |||
| 439df0718b | |||
| 3c3eb5cb02 | |||
| 6a895b4b5c | |||
| 720aed5388 | |||
| e2950decc2 | |||
| f9e119acad | |||
| bbda212a04 | |||
| 891c096bc1 | |||
| cec9c3acf6 | |||
| 2612aca236 | |||
| 2272d2e6db | |||
| ed8960f8b5 | |||
| 0f8a07fd8f | |||
| 93c9c8d85a | |||
| 25c543c468 | |||
| fb519862b8 | |||
| 681891b300 | |||
| 1c5b93cae6 | |||
| 8a7ea2d051 | |||
| 12148d0973 | |||
| acd1f4379c | |||
| 2a7d12381f | |||
| 2541fa2835 | |||
| ca43fbf341 | |||
| 745fb12b9b | |||
| edb33fa975 | |||
| 0984008494 | |||
| ca0b2b2a5c | |||
| a4fad3df74 | |||
| 0c6b420744 | |||
| 9aa69cd1ef | |||
| 6bf1e4aec5 | |||
| cfe35edfef | |||
| cdff825c7b | |||
| 881f01a2d5 | |||
| 793deeec7d | |||
| 3d5abb0b7a | |||
| ed88a71af0 | |||
| aed6935765 | |||
| 022fe854b0 | |||
| 4039555bbc | |||
| 0453c28748 | |||
| a8affe5e06 | |||
| e5e2f5810e | |||
| c9983cae47 | |||
| dcca7bf200 | |||
| a08f8ee42d | |||
| 2f81b9d31d | |||
| adfe0147ab | |||
| f750ccff78 | |||
| 95634467f6 | |||
| d9b0e5dfed | |||
| 59ac68442f | |||
| 483cf7cd11 | |||
| 3817139626 | |||
| 4ec792498e | |||
| ad87af34cf | |||
| 52550021a9 | |||
| 686fb2458a | |||
| 62cff57325 | |||
| 9877ec749e | |||
| 22d0de778b | |||
| 598b275a1a | |||
| f0675d2646 | |||
| 15be84da3b | |||
| 553b27735b | |||
| c7d27e2e0a | |||
| 83eb2a5766 | |||
| 5ecb0490a9 | |||
| ecac08f098 | |||
| 3db44a194d | |||
| fc0f58e664 | |||
| f9b46de2be | |||
| c6df59cfbb | |||
| 5f3c5ccd7d | |||
| f4c30882c7 | |||
| aa150c1fa7 | |||
| f4949f859d | |||
| 4fe283f294 | |||
| f78bbece03 | |||
| d77dc73201 | |||
| 3d39c73a37 | |||
| 7142e9e4eb | |||
| 94a47a4b3a | |||
| 18988376aa | |||
| 37673a096b | |||
| 44f6355977 | |||
| c0d84dc4a9 | |||
| d9739e0843 | |||
| 47261f181c | |||
| 447594d36c | |||
| 8c39fcf935 | |||
| fbbfac9a23 | |||
| 49e79ab9f4 | |||
| fc558417ac | |||
| d58785fcf7 | |||
| 4362e13ebc | |||
| 7d4853a197 | |||
| e0e216eecc | |||
| a2140aff06 | |||
| 99bc310088 | |||
| 5f52f13833 | |||
| 7b1725a955 | |||
| f349bd9b7f | |||
| b66fa14873 | |||
| 56bd6301b1 | |||
| 073d712324 | |||
| d3dba26b79 | |||
| 2fc27d7bda | |||
| a3d3fbb439 | |||
| f5e0594c75 | |||
| 96f83191dd | |||
| fe40138b12 | |||
| 4376582b4a | |||
| d313e3f7a5 | |||
| 6d39c5e744 | |||
| 96e22ba0c2 | |||
| 5a9bb2f8fd | |||
| 5de3d1ec8b | |||
| de7ec25b28 | |||
| 9849beded4 | |||
| 1503c05d6e | |||
| 6f517f89cf | |||
| d56fc3e1bd | |||
| e6b5a96c48 | |||
| 228da9c789 | |||
| 75ece11fc1 | |||
| 9044b794a3 | |||
| a2ff2bd393 | |||
| f2ada1a01c | |||
| 10ca52e63d | |||
| 627399a78c | |||
| 58b544d5df | |||
| 422d3afb41 | |||
| e5f5119bba | |||
| 44036cb73e | |||
| ac4f40a902 | |||
| 954a2a05a6 | |||
| 34b481f68c | |||
| 00aa3212b4 | |||
| b7824b1851 | |||
| 9d97a29104 | |||
| 4726d6fdd7 | |||
| bd1d1ca3e6 | |||
| 390c52e5e8 | |||
| 37b30537e9 | |||
| 4aac2f9010 | |||
| 2c29f029c4 | |||
| 175679468e | |||
| 5f288877d0 | |||
| 3faffe89ea | |||
| 648b382fe9 | |||
| 077e1bb94b | |||
| 17ae36d939 | |||
| ddcb2ce4f0 | |||
| 5953891a7c | |||
| fd2ab4b4a3 | |||
| b923899ced | |||
| 87fd1537d7 | |||
| 3cb3b72c2d | |||
| e270c9812d | |||
| 0b5f1d7082 | |||
| f7f7c73ccc | |||
| d4bd7b07d7 | |||
| bd89f14b7e | |||
| 9b5ee80420 | |||
| a2ab9874ab | |||
| c1593b312d | |||
| aeddc8fea9 | |||
| 9a03d12817 | |||
| 54e147c969 | |||
| 9f0bdfd2fd | |||
| 4ddf07f106 | |||
| eae5f56040 | |||
| 120d063bdf | |||
| 1d8182db32 | |||
| 946753ea91 | |||
| 4757edc130 | |||
| 19460998b6 | |||
| 1f0307aa63 | |||
| e477f4e38b | |||
| 06125bb24d | |||
| 4ff80ee4c2 | |||
| f61f4e66db | |||
| c11ae6d95e | |||
| 064d4d0a03 | |||
| fd84004c37 | |||
| 33880857a0 | |||
| 9b6decc643 | |||
| d81f303959 | |||
| e4f8af52eb | |||
| 208249a3b9 | |||
| 71373cc46f | |||
| aa8186e175 | |||
| cabe77ca67 | |||
| cdcda16714 | |||
| 9c75263ac5 | |||
| 3d08b355bd | |||
| c5b1640343 | |||
| f22c425b25 |
@@ -18,6 +18,4 @@ DerivedData
|
||||
*.xcuserstate
|
||||
|
||||
.DS_Store
|
||||
|
||||
StudyDemo/ResearchKit
|
||||
*.pyc
|
||||
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
Contributing to the ResearchKit Framework
|
||||
===========================
|
||||
|
||||
This page focuses on code contributions to the existing
|
||||
codebase. However, other types of contributions are welcome too, in
|
||||
keeping with the ResearchKit™ framework [best practices](../../wiki/best-practices). For example,
|
||||
contributions of original free-to-use survey content, back-end integrations,
|
||||
validation data, and analysis or processing tools are all welcome. Ask
|
||||
on [researchkit-dev](https://lists.apple.com/mailman/listinfo/researchkit-dev) or [contact us](https://developer.apple.com/contact/researchkit/) for guidance.
|
||||
|
||||
|
||||
Contributing software
|
||||
---------------------
|
||||
|
||||
This page assumes you already know how to check out and build the
|
||||
code. Contributions to the ResearchKit framework are expected to comply with the
|
||||
[ResearchKit Contribution Terms and License Policy](#contribution); please familiarize yourself
|
||||
with this policy prior to submitting a pull request. For any contribution, ensure that you own
|
||||
the rights or have permission from the copyright holder. (e.g. code, images, surveys, videos
|
||||
and other content you may include)
|
||||
|
||||
To contribute to ResearchKit:
|
||||
|
||||
1. [Choose or create an issue to work on.](#create)
|
||||
2. [Create a personal fork of the ResearchKit framework.](#fork)
|
||||
3. [Develop your changes in your fork.](#develop)
|
||||
4. [Run the tests.](#test)
|
||||
5. [Submit a pull request.](#request)
|
||||
6. Make any changes requested by the reviewer, and update your pull request as needed.
|
||||
7. Once accepted, your pull request will be merged into master.
|
||||
|
||||
Choosing an issue to work on<a name="create"></a>
|
||||
----------------------------
|
||||
|
||||
To find an issue to work on, either pick something that you need for
|
||||
your app, or select one of the issues from our [issue list](../../issues). Or,
|
||||
consider one of the areas where we'd like to extend ResearchKit:
|
||||
|
||||
* Faster 'get started' to a useful app
|
||||
* More active tasks
|
||||
* Data analysis for active tasks
|
||||
* More consent sections
|
||||
* Back end integrations
|
||||
|
||||
If in doubt, bring your idea up on [researchkit-dev](https://lists.apple.com/mailman/listinfo/researchkit-dev).
|
||||
|
||||
|
||||
Creating a personal fork<a name="fork"></a>
|
||||
------------------------
|
||||
|
||||
On GitHub, it's easy to create a personal fork. Just tap the "Fork"
|
||||
button on the top right, and clone your new repository.
|
||||
|
||||
|
||||
Develop your changes in your fork<a name="develop"></a>
|
||||
---------------------------------
|
||||
|
||||
Develop your changes using your normal development process. If you
|
||||
already have code from an existing project, you may need to adjust its
|
||||
style to more closely match the [ResearchKit framework coding style](./docs-standalone/coding-style-guide.md).
|
||||
|
||||
New components may need to expose new Public or Private
|
||||
headers. Public headers are for APIs that are likely to be a stable
|
||||
part of the interface of the ResearchKit framework. Private headers are for APIs that
|
||||
may need to be accessed from app-side unit tests, or that are more
|
||||
subject to change than the public interface. All other headers should
|
||||
be internal, "Project" headers.
|
||||
|
||||
Please review and ensure that any contributions you make comply with
|
||||
the [ResearchKit Contribution Terms and License Policy](#contribution).
|
||||
|
||||
Add automated tests for your feature, where it is possible to do
|
||||
so. For UI driven components where it is harder to write automated
|
||||
tests, add UI to at least one test application so that the new
|
||||
features can be reviewed and tested. Consider also whether to add new
|
||||
code to other existing demo apps to exercise your feature.
|
||||
|
||||
Keep changes that fix different issues separate. For bug fixes,
|
||||
separate bugs should be submitted as separate pull requests. A good
|
||||
way to do this is to create a new branch in your fork for each new
|
||||
bug work on.
|
||||
|
||||
Any new user-visible strings should be included in the English
|
||||
`Localizable.strings` table so that they can be picked up and
|
||||
localized in the next release cycle.
|
||||
|
||||
|
||||
Run the tests<a name="test"></a>
|
||||
-------------
|
||||
|
||||
All unit tests should pass, and there should be no warnings. Also
|
||||
verify that test apps run on both device and simulator.
|
||||
|
||||
Where your code affects UI presentation, also test:
|
||||
|
||||
* Multiple device form factors (for instance, iPhone 4S, iPhone 5, iPhone 6, iPhone 6 Plus).
|
||||
* Dynamic text, especially at the "Large" setting.
|
||||
* Rotation between portrait and landscape, where appropriate.
|
||||
|
||||
You can use the apps in the `Testing` and `samples` directories to
|
||||
test your changes.
|
||||
|
||||
Submit a pull request<a name="request"></a>
|
||||
---------------------
|
||||
|
||||
The reviewers may request changes. Make the changes, and update your
|
||||
pull request as needed. Reviews will focus on coding style,
|
||||
correctness, and design consistency.
|
||||
|
||||
This process does not take the place of an ethical review, for example
|
||||
by an institutional review board (IRB) or ethics committee.
|
||||
|
||||
After acceptance<a name="after"></a>
|
||||
----------------
|
||||
|
||||
Once your pull request has been accepted, your changes will be merged
|
||||
to master. You are still responsible for your change after it is
|
||||
accepted. Stay in contact, in case bugs are detected that may require
|
||||
your attention.
|
||||
|
||||
When the project is next branched for release, your changes will be
|
||||
incorporated. Queries may come back to you regarding localization,
|
||||
documentation, or other issues during this process.
|
||||
|
||||
|
||||
|
||||
|
||||
Release process
|
||||
-----------------
|
||||
|
||||
The `master` branch is used for work in progress. On `master`:
|
||||
|
||||
* All test apps should build and run error free.
|
||||
* Unit tests should all pass.
|
||||
* Everything should be continuously in working order in English (the
|
||||
base language).
|
||||
|
||||
The project will make periodic releases. When preparing a stable release, we
|
||||
will branch from `master` to a convergence branch. During this process,
|
||||
changes will be made first to the convergence branch, and then merged into
|
||||
`master`. On the convergence branch, changes will be made only to:
|
||||
|
||||
* Fix high priority issues.
|
||||
* Update documentation.
|
||||
* Bring localization up to date.
|
||||
* Ensure good behavior across all supported devices.
|
||||
|
||||
After the converging process is completed, we will merge everything to the
|
||||
`stable` branch and tag with a new release number. The most recent release
|
||||
will be highlighted in the [README](../..).
|
||||
|
||||
|
||||
ResearchKit Contribution Terms and License Policy<a name="contribution"></a>
|
||||
=======================================
|
||||
|
||||
Thank you for your interest in contributing to the ResearchKit
|
||||
community. In order to maintain consistency and license compatibility
|
||||
throughout the project, all contributions must comply with our
|
||||
licensing policy and terms for contributing code to the ResearchKit
|
||||
project:
|
||||
|
||||
1. If you are submitting a patch to the existing codebase, you
|
||||
represent that you have the right to license the patch, including
|
||||
all code and content, to Apple and the community, and agree by
|
||||
submitting the patch that your changes are
|
||||
licensed under the existing license terms of the file you are
|
||||
modifying (i.e., [ResearchKit BSD license](LICENSE)).
|
||||
You confirm that you have added your copyright (name and year) to
|
||||
the relevant files for changes that are more than 10 lines of code.
|
||||
2. If you are submitting a new file for inclusion in the ResearchKit
|
||||
framework (no code or other content is copied from another source), you
|
||||
have included your copyright (name and year) and a copy of the ResearchKit
|
||||
BSD license. By submitting your new file you represent that you have the
|
||||
right to license your file to Apple and the community, and agree that your
|
||||
file submission is licensed under the ResearchKit BSD license.
|
||||
3. If you aren't the author of the patch, you agree that you have
|
||||
the right to submit the patch, and have included the original copyright
|
||||
notices and licensing terms with it, to the extent that they exist.
|
||||
If there wasn't a copyright notice or license, please make a note of it
|
||||
in your response. Generally we can only take in patches that are
|
||||
BSD-licensed in order to maintain license compatibility within the project.
|
||||
@@ -5,10 +5,10 @@ The ResearchKit™ framework is an open source software framework that makes it
|
||||
create apps for medical research or for other research projects.
|
||||
|
||||
* Getting Started: [Getting Started](#gettingstarted)
|
||||
* Documentation: ([Programming Guide](http://researchkit.org/docs/docs/Overview/GuideOverview.html)) ([API](http://researchkit.org/docs/index.html))
|
||||
* Best practices: [Best Practices](../../wiki/best_practices)
|
||||
* Contributing to ResearchKit: [Contributing](../../wiki/contributing)
|
||||
* Website and blog: ([researchkit.org](http://researchkit.org/index.html)) ([Blog](http://researchkit.org/blog.html))
|
||||
* Documentation: ([Programming Guide](http://researchkit.github.io/docs/docs/Overview/GuideOverview.html)) ([API](http://researchkit.github.io/docs/index.html))
|
||||
* Best practices: [Best Practices](../../wiki/best-practices)
|
||||
* Contributing to ResearchKit: [Contributing](CONTRIBUTING.md)
|
||||
* Website and blog: ([researchkit.org](http://researchkit.github.io/index.html)) ([Blog](http://researchkit.github.io/blog.html))
|
||||
* ResearchKit BSD License: [License](#license)
|
||||
|
||||
Getting More Information
|
||||
@@ -18,7 +18,7 @@ Getting More Information
|
||||
* Join [researchkit-dev](https://lists.apple.com/mailman/listinfo/researchkit-dev) for discussing ongoing work to improve and expand the framework.
|
||||
* Or [contact us](https://developer.apple.com/contact/researchkit/)
|
||||
|
||||
Use cases
|
||||
Use Cases
|
||||
===========
|
||||
|
||||
A task in the ResearchKit framework contains a set of steps to present to a
|
||||
@@ -29,14 +29,14 @@ Surveys
|
||||
-------
|
||||
|
||||
The ResearchKit framework provides a pre-built user interface for surveys, which can be
|
||||
presented modally on an iPhone, iPod Touch, or iPad. [Surveys](http://researchkit.org/docs/docs/Survey/CreatingSurveys.html)
|
||||
presented modally on an iPhone, iPod Touch, or iPad. [Surveys](http://researchkit.github.io/docs/docs/Survey/CreatingSurveys.html)
|
||||
|
||||
|
||||
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. [Consent](http://researchkit.org/docs/docs/InformedConsent/InformedConsent.html)
|
||||
explain the details of your research study and obtain a signature if needed. [Consent](http://researchkit.github.io/docs/docs/InformedConsent/InformedConsent.html)
|
||||
|
||||
|
||||
Active Tasks
|
||||
@@ -45,17 +45,17 @@ 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. [Active Tasks](http://researchkit.org/docs/docs/ActiveTasks/ActiveTasks.html)
|
||||
under semi-controlled conditions, while iPhone sensors actively collect data. [Active Tasks](http://researchkit.github.io/docs/docs/ActiveTasks/ActiveTasks.html)
|
||||
|
||||
|
||||
Getting started<a name="gettingstarted"></a>
|
||||
Getting Started<a name="gettingstarted"></a>
|
||||
===============
|
||||
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
The primary ResearchKit framework codebase supports iOS and requires Xcode 6.3
|
||||
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.
|
||||
|
||||
@@ -63,7 +63,7 @@ using the ResearchKit framework can run on devices with iOS 8.0 or newer.
|
||||
Installation
|
||||
------------
|
||||
|
||||
The lastest 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
|
||||
@@ -110,6 +110,8 @@ target as shown in the figure below.
|
||||
</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.
|
||||
|
||||
###2. Create a Step
|
||||
|
||||
In this walk-through, we will use the ResearchKit framework to modally present a
|
||||
@@ -120,24 +122,41 @@ Create a step for your task by adding some code, perhaps in
|
||||
simple, we'll use an instruction step (`ORKInstructionStep`) and name
|
||||
the step `myStep`.
|
||||
|
||||
*Objective-C*
|
||||
|
||||
```objc
|
||||
ORKInstructionStep *myStep =
|
||||
[[ORKInstructionStep alloc] initWithIdentifier:@"intro"];
|
||||
myStep.title = @"Welcome to ResearchKit";
|
||||
```
|
||||
|
||||
*Swift*
|
||||
|
||||
```swift
|
||||
let myStep = ORKInstructionStep(identifier: "intro")
|
||||
myStep.title = "Welcome to ResearchKit"
|
||||
```
|
||||
|
||||
###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
|
||||
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*
|
||||
|
||||
```objc
|
||||
ORKOrderedTask *task =
|
||||
[[ORKOrderedTask alloc] initWithIdentifier:@"task" steps:@[myStep]];
|
||||
```
|
||||
|
||||
*Swift*
|
||||
|
||||
```swift
|
||||
let task = ORKOrderedTask(identifier: "task", steps: [myStep])
|
||||
```
|
||||
|
||||
###4. Present the Task
|
||||
|
||||
Create a task view controller (`ORKTaskViewController`) and initialize
|
||||
@@ -145,6 +164,8 @@ 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*
|
||||
|
||||
```objc
|
||||
ORKTaskViewController *taskViewController =
|
||||
[[ORKTaskViewController alloc] initWithTask:task taskRunUUID:nil];
|
||||
@@ -152,10 +173,20 @@ taskViewController.delegate = self;
|
||||
[self presentViewController:taskViewController animated:YES completion:nil];
|
||||
```
|
||||
|
||||
*Swift*
|
||||
|
||||
```swift
|
||||
let taskViewController = ORKTaskViewController(task: task, taskRunUUID: nil)
|
||||
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:
|
||||
|
||||
*Objective-C*
|
||||
|
||||
```objc
|
||||
- (void)taskViewController:(ORKTaskViewController *)taskViewController
|
||||
didFinishWithReason:(ORKTaskViewControllerFinishReason)reason
|
||||
@@ -169,6 +200,20 @@ 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.
|
||||
|
||||
// Then, dismiss the task view controller.
|
||||
dismissViewControllerAnimated(true, completion: nil)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
If you now run your app, you should see your first ResearchKit framework
|
||||
instruction step:
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'ResearchKit'
|
||||
s.version = '1.2.1'
|
||||
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/'
|
||||
s.license = { :type => 'BSD', :file => 'LICENSE' }
|
||||
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.resources = 'ResearchKit/**/*.{fsh,vsh}', 'ResearchKit/Animations/**/*.m4v', 'ResearchKit/Artwork.xcassets', 'ResearchKit/Localized/*.lproj'
|
||||
s.platform = :ios, '8.0'
|
||||
s.requires_arc = true
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0630"
|
||||
version = "1.8">
|
||||
LastUpgradeVersion = "0700"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
@@ -23,10 +23,10 @@
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
buildConfiguration = "Debug">
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
@@ -48,13 +48,15 @@
|
||||
ReferencedContainer = "container:ResearchKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Debug"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
@@ -72,10 +74,10 @@
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Release"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0630"
|
||||
version = "1.8">
|
||||
LastUpgradeVersion = "0700"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
@@ -23,19 +23,21 @@
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
buildConfiguration = "Debug">
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Debug"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
@@ -53,10 +55,10 @@
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Release"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
|
||||
@@ -28,8 +28,8 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
// Shared header for accessibility functionality.
|
||||
|
||||
// Shared header for accessibility functionality.
|
||||
#import "UIView+ORKAccessibility.h"
|
||||
#import "ORKAccessibilityFunctions.h"
|
||||
|
||||
#import "ORKLineGraphAccessibilityElement.h"
|
||||
|
||||
@@ -28,9 +28,11 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKDefines.h"
|
||||
#import "ORKHelpers.h"
|
||||
|
||||
|
||||
@class ORKScaleSlider;
|
||||
|
||||
// Used to properly format values from the ORKScaleSlider.
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "ORKAccessibilityFunctions.h"
|
||||
#import "ORKAnswerFormat_Internal.h"
|
||||
@@ -35,6 +36,7 @@
|
||||
#import "ORKScaleSliderView.h"
|
||||
#import "UIView+ORKAccessibility.h"
|
||||
|
||||
|
||||
NSString *ORKAccessibilityFormatScaleSliderValue(CGFloat value, ORKScaleSlider *slider) {
|
||||
ORKScaleSliderView *sliderView = (ORKScaleSliderView *)[slider ork_superviewOfType:[ORKScaleSliderView class]];
|
||||
if (!slider || !sliderView) {
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
Copyright (c) 2015, 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:
|
||||
|
||||
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 <UIKit/UIKit.h>
|
||||
|
||||
@interface ORKLineGraphAccessibilityElement : UIAccessibilityElement
|
||||
|
||||
- (nonnull instancetype)initWithAccessibilityContainer:(nonnull UIView *)container index:(NSInteger)index maxIndex:(NSInteger)maxIndex;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
Copyright (c) 2015, 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:
|
||||
|
||||
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 "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 {
|
||||
self = [super initWithAccessibilityContainer:container];
|
||||
if (self) {
|
||||
self.index = index;
|
||||
self.maxIndex = maxIndex;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CGRect)accessibilityFrame {
|
||||
if (self.maxIndex == 0) {
|
||||
return [super accessibilityFrame];
|
||||
}
|
||||
|
||||
CGRect containerFrame = [self.accessibilityContainer frame];
|
||||
CGFloat height = CGRectGetHeight(containerFrame);
|
||||
CGFloat width = CGRectGetWidth(containerFrame) / self.maxIndex;
|
||||
CGFloat x = self.index * width;
|
||||
|
||||
return UIAccessibilityConvertFrameToScreenCoordinates(CGRectMake(x, 0, width, height), self.accessibilityContainer);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -28,9 +28,11 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <ResearchKit/ResearchKit.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface UIView (ORKAccessibility)
|
||||
|
||||
@@ -28,8 +28,10 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "UIView+ORKAccessibility.h"
|
||||
|
||||
|
||||
@implementation UIView (ORKAccessibility)
|
||||
|
||||
- (UIView *)ork_superviewOfType:(Class)aClass {
|
||||
@@ -40,8 +42,7 @@
|
||||
id superview = [self superview];
|
||||
if (superview == nil) {
|
||||
return nil;
|
||||
}
|
||||
else if ([superview isKindOfClass:aClass]) {
|
||||
} else if ([superview isKindOfClass:aClass]) {
|
||||
return superview;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,9 +28,11 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import <CoreLocation/CoreLocation.h>
|
||||
#import <ResearchKit/ResearchKit.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface CLLocation (ORKJSONDictionary)
|
||||
|
||||
@@ -28,13 +28,14 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "CLLocation+ORKJSONDictionary.h"
|
||||
#import "ORKHelpers.h"
|
||||
|
||||
|
||||
@implementation CLLocation (ORKJSONDictionary)
|
||||
|
||||
- (NSDictionary *)ork_JSONDictionary
|
||||
{
|
||||
- (NSDictionary *)ork_JSONDictionary {
|
||||
CLLocationCoordinate2D coord = self.coordinate;
|
||||
CLLocationDistance altitude = self.altitude;
|
||||
CLLocationAccuracy horizAccuracy = self.horizontalAccuracy;
|
||||
@@ -44,32 +45,27 @@
|
||||
NSDate *timestamp = self.timestamp;
|
||||
CLFloor *floor = self.floor;
|
||||
|
||||
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObject:ORKStringFromDateISO8601(timestamp) forKey:@"timestamp"];
|
||||
NSMutableDictionary *dictionary = [@{@"timestamp" : ORKStringFromDateISO8601(timestamp)} mutableCopy];
|
||||
|
||||
if (horizAccuracy >= 0)
|
||||
{
|
||||
dict[@"coordinate"] = @{ @"latitude" : [NSDecimalNumber numberWithDouble:coord.latitude], @"longitude" : [NSDecimalNumber numberWithDouble:coord.longitude]};
|
||||
dict[@"horizontalAccuracy"] = [NSDecimalNumber numberWithDouble:horizAccuracy];
|
||||
if (horizAccuracy >= 0) {
|
||||
dictionary[@"coordinate"] = @{ @"latitude" : [NSDecimalNumber numberWithDouble:coord.latitude], @"longitude" : [NSDecimalNumber numberWithDouble:coord.longitude]};
|
||||
dictionary[@"horizontalAccuracy"] = [NSDecimalNumber numberWithDouble:horizAccuracy];
|
||||
}
|
||||
if (vertAccuracy >= 0)
|
||||
{
|
||||
dict[@"altitude"] = [NSDecimalNumber numberWithDouble:altitude];
|
||||
dict[@"verticalAccuracy"] = [NSDecimalNumber numberWithDouble:vertAccuracy];
|
||||
if (vertAccuracy >= 0) {
|
||||
dictionary[@"altitude"] = [NSDecimalNumber numberWithDouble:altitude];
|
||||
dictionary[@"verticalAccuracy"] = [NSDecimalNumber numberWithDouble:vertAccuracy];
|
||||
}
|
||||
if (course >= 0)
|
||||
{
|
||||
dict[@"course"] = [NSDecimalNumber numberWithDouble:course];
|
||||
if (course >= 0) {
|
||||
dictionary[@"course"] = [NSDecimalNumber numberWithDouble:course];
|
||||
}
|
||||
if (speed >= 0)
|
||||
{
|
||||
dict[@"speed"] = [NSDecimalNumber numberWithDouble:speed];
|
||||
if (speed >= 0) {
|
||||
dictionary[@"speed"] = [NSDecimalNumber numberWithDouble:speed];
|
||||
}
|
||||
if (floor)
|
||||
{
|
||||
dict[@"floor"] = @(floor.level);
|
||||
if (floor) {
|
||||
dictionary[@"floor"] = @(floor.level);
|
||||
}
|
||||
|
||||
return dict;
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -28,9 +28,11 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import <CoreMotion/CoreMotion.h>
|
||||
#import <ResearchKit/ResearchKit.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface CMAccelerometerData (ORKJSONDictionary)
|
||||
|
||||
@@ -31,16 +31,16 @@
|
||||
|
||||
#import "CMAccelerometerData+ORKJSONDictionary.h"
|
||||
|
||||
|
||||
@implementation CMAccelerometerData (ORKJSONDictionary)
|
||||
|
||||
- (NSDictionary *)ork_JSONDictionary
|
||||
{
|
||||
NSDictionary *dict = @{@"timestamp": [NSDecimalNumber numberWithDouble:self.timestamp],
|
||||
@"x" : [NSDecimalNumber numberWithDouble:self.acceleration.x],
|
||||
@"y" : [NSDecimalNumber numberWithDouble:self.acceleration.y],
|
||||
@"z" : [NSDecimalNumber numberWithDouble:self.acceleration.z]
|
||||
};
|
||||
return dict;
|
||||
- (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]
|
||||
};
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#import <CoreMotion/CoreMotion.h>
|
||||
#import <ResearchKit/ResearchKit.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface CMDeviceMotion (ORKJSONDictionary)
|
||||
|
||||
@@ -31,17 +31,17 @@
|
||||
|
||||
#import "CMDeviceMotion+ORKJSONDictionary.h"
|
||||
|
||||
|
||||
@implementation CMDeviceMotion (ORKJSONDictionary)
|
||||
|
||||
- (NSDictionary *)ork_JSONDictionary
|
||||
{
|
||||
- (NSDictionary *)ork_JSONDictionary {
|
||||
CMQuaternion attitude = self.attitude.quaternion;
|
||||
CMRotationRate rotationRate = self.rotationRate;
|
||||
CMAcceleration gravity = self.gravity;
|
||||
CMAcceleration userAccel = self.userAcceleration;
|
||||
CMCalibratedMagneticField field = self.magneticField;
|
||||
|
||||
NSDictionary *dict = @{@"timestamp": [NSDecimalNumber numberWithDouble:self.timestamp],
|
||||
NSDictionary *dictionary = @{@"timestamp": [NSDecimalNumber numberWithDouble:self.timestamp],
|
||||
@"attitude" : @{
|
||||
@"x" : [NSDecimalNumber numberWithDouble:attitude.x],
|
||||
@"y" : [NSDecimalNumber numberWithDouble:attitude.y],
|
||||
@@ -70,7 +70,7 @@
|
||||
@"accuracy" : [NSDecimalNumber numberWithDouble:field.accuracy]
|
||||
}
|
||||
};
|
||||
return dict;
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#import <CoreMotion/CoreMotion.h>
|
||||
#import <ResearchKit/ResearchKit.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface CMMotionActivity (ORKJSONDictionary)
|
||||
|
||||
@@ -32,57 +32,49 @@
|
||||
#import "CMMotionActivity+ORKJSONDictionary.h"
|
||||
#import "ORKHelpers.h"
|
||||
|
||||
static NSString * const kActivityUnknown = @"unknown";
|
||||
static NSString * const kActivityStationary = @"stationary";
|
||||
static NSString * const kActivityWalking = @"walking";
|
||||
static NSString * const kActivityRunning = @"running";
|
||||
static NSString * const kActivityAutomotive = @"automotive";
|
||||
static NSString * const kStartDateKey = @"startDate";
|
||||
static NSString * const kEndDateKey = @"endDate";
|
||||
|
||||
static NSString *stringFromActivityConfidence(CMMotionActivityConfidence confidence)
|
||||
{
|
||||
static NSString *const kActivityUnknown = @"unknown";
|
||||
static NSString *const kActivityStationary = @"stationary";
|
||||
static NSString *const kActivityWalking = @"walking";
|
||||
static NSString *const kActivityRunning = @"running";
|
||||
static NSString *const kActivityAutomotive = @"automotive";
|
||||
static NSString *const kStartDateKey = @"startDate";
|
||||
static NSString *const kEndDateKey = @"endDate";
|
||||
|
||||
static NSString *stringFromActivityConfidence(CMMotionActivityConfidence confidence) {
|
||||
NSDictionary *confidences = @{@(CMMotionActivityConfidenceHigh) : @"high",
|
||||
@(CMMotionActivityConfidenceMedium) : @"medium",
|
||||
@(CMMotionActivityConfidenceLow) : @"low"};
|
||||
return confidences[@(confidence)];
|
||||
}
|
||||
|
||||
static NSArray *activityArray(CMMotionActivity *activity)
|
||||
{
|
||||
NSMutableArray *ret = [NSMutableArray array];
|
||||
if (activity.unknown)
|
||||
{
|
||||
[ret addObject:kActivityUnknown];
|
||||
static NSArray *activityArray(CMMotionActivity *activity) {
|
||||
NSMutableArray *array = [NSMutableArray array];
|
||||
if (activity.unknown) {
|
||||
[array addObject:kActivityUnknown];
|
||||
}
|
||||
if (activity.stationary)
|
||||
{
|
||||
[ret addObject:kActivityStationary];
|
||||
if (activity.stationary) {
|
||||
[array addObject:kActivityStationary];
|
||||
}
|
||||
if (activity.walking)
|
||||
{
|
||||
[ret addObject:kActivityWalking];
|
||||
if (activity.walking) {
|
||||
[array addObject:kActivityWalking];
|
||||
}
|
||||
if (activity.running)
|
||||
{
|
||||
[ret addObject:kActivityRunning];
|
||||
if (activity.running) {
|
||||
[array addObject:kActivityRunning];
|
||||
}
|
||||
if (activity.automotive)
|
||||
{
|
||||
[ret addObject:kActivityAutomotive];
|
||||
if (activity.automotive) {
|
||||
[array addObject:kActivityAutomotive];
|
||||
}
|
||||
return ret;
|
||||
return array;
|
||||
}
|
||||
|
||||
|
||||
static NSString * const kActivityKey = @"activity";
|
||||
static NSString *const kActivityKey = @"activity";
|
||||
|
||||
static NSString * const kConfidenceKey = @"confidence";
|
||||
static NSString *const kConfidenceKey = @"confidence";
|
||||
|
||||
@implementation CMMotionActivity (ORKJSONDictionary)
|
||||
|
||||
- (NSDictionary *)ork_JSONDictionary
|
||||
{
|
||||
- (NSDictionary *)ork_JSONDictionary {
|
||||
return @{kConfidenceKey : stringFromActivityConfidence(self.confidence),
|
||||
kActivityKey : activityArray(self),
|
||||
kStartDateKey : ORKStringFromDateISO8601(self.startDate)};
|
||||
|
||||
@@ -32,13 +32,13 @@
|
||||
#import <CoreMotion/CoreMotion.h>
|
||||
#import <ResearchKit/ResearchKit.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface CMPedometerData (ORKJSONDictionary)
|
||||
|
||||
- (NSDictionary *)ork_JSONDictionary;
|
||||
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -33,18 +33,17 @@
|
||||
#import "ORKHelpers.h"
|
||||
#import <CoreMotion/CoreMotion.h>
|
||||
|
||||
|
||||
@implementation CMPedometerData (ORKJSONDictionary)
|
||||
|
||||
- (NSDictionary *)ork_JSONDictionary
|
||||
{
|
||||
NSMutableDictionary *dict = [@{@"startDate": ORKStringFromDateISO8601(self.startDate),
|
||||
@"endDate": ORKStringFromDateISO8601(self.endDate)
|
||||
} mutableCopy];
|
||||
for (NSString *key in @[@"numberOfSteps", @"distance", @"floorsAscended", @"floorsDescended"])
|
||||
{
|
||||
[dict setValue:[self valueForKey:key] forKey:key];
|
||||
- (NSDictionary *)ork_JSONDictionary {
|
||||
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 dict;
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#import <HealthKit/HealthKit.h>
|
||||
#import <ResearchKit/ResearchKit.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef NS_OPTIONS(NSInteger, ORKSampleJSONOptions) {
|
||||
@@ -41,8 +42,7 @@ typedef NS_OPTIONS(NSInteger, ORKSampleJSONOptions) {
|
||||
};
|
||||
|
||||
/**
|
||||
* JSON serialization aid for HKSample.
|
||||
*
|
||||
JSON serialization aid for HKSample.
|
||||
*/
|
||||
@interface HKSample (ORKJSONDictionary)
|
||||
|
||||
@@ -52,8 +52,7 @@ typedef NS_OPTIONS(NSInteger, ORKSampleJSONOptions) {
|
||||
|
||||
|
||||
/**
|
||||
* JSON serialization aid for HKCorrelation.
|
||||
*
|
||||
JSON serialization aid for HKCorrelation.
|
||||
*/
|
||||
@interface HKCorrelation (ORKJSONDictionary)
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#import "HKSample+ORKJSONDictionary.h"
|
||||
#import "ORKHelpers.h"
|
||||
|
||||
|
||||
static NSString *const kHKSampleIdentifierKey = @"type"; // For compatibility with Health XML export
|
||||
static NSString *const kHKUUIDKey = @"uuid";
|
||||
static NSString *const kHKSampleStartDateKey = @"startDate";
|
||||
@@ -43,108 +44,102 @@ static NSString *const kHKUnitKey = @"unit";
|
||||
static NSString *const kHKCorrelatedObjectsKey = @"objects";
|
||||
// static NSString *const kHKSourceIdentifierKey = @"sourceBundleIdentifier";
|
||||
|
||||
@interface HKCategorySample (ORKJSONDictionary)
|
||||
|
||||
@end
|
||||
|
||||
@interface HKQuantitySample (ORKJSONDictionary)
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation HKSample (ORKJSONDictionary)
|
||||
|
||||
- (NSDictionary *)ork_JSONDictionaryWithOptions:(ORKSampleJSONOptions)options unit:(HKUnit *)unit
|
||||
{
|
||||
NSMutableDictionary *mdict = [NSMutableDictionary dictionaryWithCapacity:12];
|
||||
- (NSMutableDictionary *)ork_JSONMutableDictionaryWithOptions:(ORKSampleJSONOptions)options unit:(HKUnit *)unit {
|
||||
NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionaryWithCapacity:12];
|
||||
|
||||
// Type identification
|
||||
HKSampleType *sampleType = [self sampleType];
|
||||
mdict[kHKSampleIdentifierKey] = [sampleType identifier];
|
||||
mutableDictionary[kHKSampleIdentifierKey] = [sampleType identifier];
|
||||
|
||||
// consider adding @"class" : NSStringFromClass(sampleType) ?
|
||||
|
||||
// Start and end dates
|
||||
NSDate *startDate = [self startDate];
|
||||
if (startDate)
|
||||
{
|
||||
mdict[kHKSampleStartDateKey] = ORKStringFromDateISO8601(startDate);
|
||||
if (startDate) {
|
||||
mutableDictionary[kHKSampleStartDateKey] = ORKStringFromDateISO8601(startDate);
|
||||
}
|
||||
|
||||
NSDate *endDate = [self endDate];
|
||||
if (endDate)
|
||||
{
|
||||
mdict[kHKSampleEndDateKey] = ORKStringFromDateISO8601(endDate);
|
||||
if (endDate) {
|
||||
mutableDictionary[kHKSampleEndDateKey] = ORKStringFromDateISO8601(endDate);
|
||||
}
|
||||
if (unit)
|
||||
{
|
||||
mdict[kHKUnitKey] = [unit unitString];
|
||||
if (unit) {
|
||||
mutableDictionary[kHKUnitKey] = [unit unitString];
|
||||
}
|
||||
if ((options & ORKSampleIncludeUUID))
|
||||
{
|
||||
if ((options & ORKSampleIncludeUUID)) {
|
||||
NSUUID *uuid = [self UUID];
|
||||
if (uuid)
|
||||
{
|
||||
mdict[kHKUUIDKey] = [uuid UUIDString];
|
||||
if (uuid) {
|
||||
mutableDictionary[kHKUUIDKey] = uuid.UUIDString;
|
||||
}
|
||||
}
|
||||
|
||||
if ( (options & ORKSampleIncludeMetadata) && [self.metadata count] > 0)
|
||||
{
|
||||
if ( (options & ORKSampleIncludeMetadata) && [self.metadata count] > 0) {
|
||||
NSMutableDictionary *metadata = [self.metadata mutableCopy];
|
||||
for (NSString *k in metadata)
|
||||
{
|
||||
for (NSString *k in metadata) {
|
||||
id obj = metadata[k];
|
||||
if ([obj isKindOfClass:[NSDate class]])
|
||||
{
|
||||
if ([obj isKindOfClass:[NSDate class]]) {
|
||||
metadata[k] = ORKStringFromDateISO8601(obj);
|
||||
}
|
||||
}
|
||||
|
||||
mdict[kHKMetadataKey] = metadata;
|
||||
mutableDictionary[kHKMetadataKey] = metadata;
|
||||
}
|
||||
|
||||
if (options & ORKSampleIncludeSource)
|
||||
{
|
||||
if (options & ORKSampleIncludeSource) {
|
||||
HKSource *source = [self source];
|
||||
if (source.name)
|
||||
{
|
||||
mdict[kHKSourceKey] = source.name;
|
||||
if (source.name) {
|
||||
mutableDictionary[kHKSourceKey] = source.name;
|
||||
}
|
||||
}
|
||||
|
||||
return mdict;
|
||||
return mutableDictionary;
|
||||
}
|
||||
|
||||
- (NSDictionary *)ork_JSONDictionaryWithOptions:(ORKSampleJSONOptions)options unit:(HKUnit *)unit {
|
||||
return [self ork_JSONMutableDictionaryWithOptions:options unit:unit];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface HKCategorySample (ORKJSONDictionary)
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation HKCategorySample (ORKJSONDictionary)
|
||||
|
||||
- (NSDictionary *)ork_JSONDictionaryWithOptions:(ORKSampleJSONOptions)options unit:(HKUnit *)unit
|
||||
{
|
||||
NSMutableDictionary *dict = [[super ork_JSONDictionaryWithOptions:options unit:unit] mutableCopy];
|
||||
- (NSDictionary *)ork_JSONDictionaryWithOptions:(ORKSampleJSONOptions)options unit:(HKUnit *)unit {
|
||||
NSMutableDictionary *dictionary = [self ork_JSONMutableDictionaryWithOptions:options unit:unit];
|
||||
|
||||
NSInteger value = [self value];
|
||||
dict[kHKSampleValue] = @(value);
|
||||
dictionary[kHKSampleValue] = @(value);
|
||||
|
||||
return dict;
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface HKQuantitySample (ORKJSONDictionary)
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation HKQuantitySample (ORKJSONDictionary)
|
||||
|
||||
- (NSDictionary *)ork_JSONDictionaryWithOptions:(ORKSampleJSONOptions)options unit:(HKUnit *)unit
|
||||
{
|
||||
NSMutableDictionary *dict = [[super ork_JSONDictionaryWithOptions:options unit:unit] mutableCopy];
|
||||
- (NSDictionary *)ork_JSONDictionaryWithOptions:(ORKSampleJSONOptions)options unit:(HKUnit *)unit {
|
||||
NSMutableDictionary *dictionary = [self ork_JSONMutableDictionaryWithOptions:options unit:unit];
|
||||
|
||||
HKQuantity *quantity = [self quantity];
|
||||
double value = [quantity doubleValueForUnit:unit];
|
||||
dict[kHKSampleValue] = @(value);
|
||||
dictionary[kHKSampleValue] = @(value);
|
||||
|
||||
|
||||
return dict;
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -152,29 +147,22 @@ static NSString *const kHKCorrelatedObjectsKey = @"objects";
|
||||
|
||||
@implementation HKCorrelation (ORKJSONDictionary)
|
||||
|
||||
|
||||
- (NSDictionary *)ork_JSONDictionaryWithOptions:(ORKSampleJSONOptions)options sampleTypes:(NSArray *)sampleTypes units:(NSArray *)units
|
||||
{
|
||||
NSMutableDictionary *mdict = (NSMutableDictionary *)[self ork_JSONDictionaryWithOptions:options unit:nil];
|
||||
- (NSDictionary *)ork_JSONDictionaryWithOptions:(ORKSampleJSONOptions)options sampleTypes:(NSArray *)sampleTypes units:(NSArray *)units {
|
||||
NSMutableDictionary *mutableDictionary = [self ork_JSONMutableDictionaryWithOptions:options unit:nil];
|
||||
|
||||
// The correlated objects
|
||||
NSMutableArray *correlatedObjects = [NSMutableArray arrayWithCapacity:[sampleTypes count]];
|
||||
for (HKSample *sample in self.objects)
|
||||
{
|
||||
for (HKSample *sample in self.objects) {
|
||||
NSUInteger idx = [sampleTypes indexOfObject:sample.sampleType];
|
||||
if (idx == NSNotFound)
|
||||
{
|
||||
if (idx == NSNotFound) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[correlatedObjects addObject:[sample ork_JSONDictionaryWithOptions:options unit:units[idx]]];
|
||||
}
|
||||
mdict[kHKCorrelatedObjectsKey] = correlatedObjects;
|
||||
mutableDictionary[kHKCorrelatedObjectsKey] = correlatedObjects;
|
||||
|
||||
|
||||
return mdict;
|
||||
return mutableDictionary;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@@ -28,8 +28,10 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import <ResearchKit/ORKRecorder.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
@@ -49,16 +51,18 @@ ORK_CLASS_AVAILABLE
|
||||
/**
|
||||
Returns an initialized accelerometer recorder using the specified frequency.
|
||||
|
||||
@param frequency The frequency of accelerometer data collected from CoreMotion, in hertz (Hz).
|
||||
@param identifier The unique identifier of the recorder (assigned by the recorder configuration).
|
||||
@param frequency The frequency of accelerometer data collected from CoreMotion, in hertz (Hz).
|
||||
@param step The step that requested this recorder.
|
||||
@param outputDirectory The directory in which the accelerometer data should be stored.
|
||||
|
||||
@return An initialized accelerometer recorder.
|
||||
*/
|
||||
- (instancetype)initWithFrequency:(double)frequency
|
||||
step:(ORKStep *)step
|
||||
outputDirectory:(NSURL *)outputDirectory;
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifer
|
||||
frequency:(double)frequency
|
||||
step:(nullable ORKStep *)step
|
||||
outputDirectory:(nullable NSURL *)outputDirectory;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKAccelerometerRecorder.h"
|
||||
#import "ORKDataLogger.h"
|
||||
#import "CMAccelerometerData+ORKJSONDictionary.h"
|
||||
@@ -37,36 +38,34 @@
|
||||
#import "ORKHelpers.h"
|
||||
|
||||
|
||||
@interface ORKAccelerometerRecorder()
|
||||
{
|
||||
@interface ORKAccelerometerRecorder () {
|
||||
ORKDataLogger *_logger;
|
||||
NSError *_recordingError;
|
||||
}
|
||||
|
||||
@property (nonatomic, strong) CMMotionManager *motionManager;
|
||||
|
||||
@property (nonatomic) NSTimeInterval uptime;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKAccelerometerRecorder
|
||||
|
||||
- (instancetype)initWithFrequency:(double)frequency step:(ORKStep *)step outputDirectory:(NSURL *)outputDirectory
|
||||
{
|
||||
self = [super initWithStep:step outputDirectory:outputDirectory];
|
||||
if (self)
|
||||
{
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier frequency:(double)frequency step:(ORKStep *)step outputDirectory:(NSURL *)outputDirectory {
|
||||
self = [super initWithIdentifier:identifier step:step outputDirectory:outputDirectory];
|
||||
if (self) {
|
||||
self.frequency = frequency;
|
||||
self.continuesInBackground = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
- (void)dealloc {
|
||||
[_logger finishCurrentLog];
|
||||
}
|
||||
|
||||
- (NSString *)recorderType
|
||||
{
|
||||
- (NSString *)recorderType {
|
||||
return @"accel";
|
||||
}
|
||||
|
||||
@@ -83,8 +82,8 @@
|
||||
}
|
||||
|
||||
- (void)start {
|
||||
|
||||
[super start];
|
||||
|
||||
self.motionManager = [self createMotionManager];
|
||||
|
||||
if (! _logger) {
|
||||
@@ -96,8 +95,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
if (! self.motionManager || ! self.motionManager.accelerometerAvailable)
|
||||
{
|
||||
if (! self.motionManager || ! self.motionManager.accelerometerAvailable) {
|
||||
NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain
|
||||
code:NSFeatureUnsupportedError
|
||||
userInfo:@{@"recorder" : self}];
|
||||
@@ -111,17 +109,12 @@
|
||||
|
||||
[self.motionManager stopAccelerometerUpdates];
|
||||
|
||||
[self.motionManager
|
||||
startAccelerometerUpdatesToQueue:[[NSOperationQueue alloc] init]
|
||||
withHandler:^(CMAccelerometerData *data, NSError *error)
|
||||
{
|
||||
[self.motionManager startAccelerometerUpdatesToQueue:[[NSOperationQueue alloc] init] withHandler:^(CMAccelerometerData *data, NSError *error) {
|
||||
BOOL success = NO;
|
||||
if (data)
|
||||
{
|
||||
if (data) {
|
||||
success = [_logger append:[data ork_JSONDictionary] error:&error];
|
||||
}
|
||||
if (!success)
|
||||
{
|
||||
if (!success) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
_recordingError = error;
|
||||
[self stop];
|
||||
@@ -131,7 +124,7 @@
|
||||
}
|
||||
|
||||
- (NSDictionary *)userInfo {
|
||||
return @{ @"frequency" : @(self.frequency) };;
|
||||
return @{ @"frequency" : @(self.frequency) };
|
||||
}
|
||||
|
||||
- (void)stop {
|
||||
@@ -150,28 +143,24 @@
|
||||
[super stop];
|
||||
}
|
||||
|
||||
- (void)doStopRecording
|
||||
{
|
||||
- (void)doStopRecording {
|
||||
if (self.isRecording) {
|
||||
[self.motionManager stopAccelerometerUpdates];
|
||||
self.motionManager = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)finishRecordingWithError:(NSError *)error
|
||||
{
|
||||
- (void)finishRecordingWithError:(NSError *)error {
|
||||
[self doStopRecording];
|
||||
[super finishRecordingWithError:nil];
|
||||
}
|
||||
|
||||
- (void)reset
|
||||
{
|
||||
- (void)reset {
|
||||
[super reset];
|
||||
|
||||
_logger = nil;
|
||||
}
|
||||
|
||||
|
||||
- (BOOL)isRecording {
|
||||
return self.motionManager.accelerometerActive;
|
||||
}
|
||||
@@ -180,67 +169,65 @@
|
||||
return @"application/json";
|
||||
}
|
||||
|
||||
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface ORKAccelerometerRecorderConfiguration()
|
||||
@interface ORKAccelerometerRecorderConfiguration ()
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKAccelerometerRecorderConfiguration
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wobjc-designated-initializers"
|
||||
- (instancetype)initWithFrequency:(double)freq {
|
||||
self = [self ork_init];
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
||||
@throw [NSException exceptionWithName:NSGenericException reason:@"Use subclass designated initializer" userInfo:nil];
|
||||
}
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier frequency:(double)frequency {
|
||||
self = [super initWithIdentifier:identifier];
|
||||
if (self) {
|
||||
_frequency = freq;
|
||||
_frequency = frequency;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
- (ORKRecorder *)recorderForStep:(ORKStep *)step outputDirectory:(NSURL *)outputDirectory
|
||||
{
|
||||
return [[ORKAccelerometerRecorder alloc] initWithFrequency:self.frequency step:step outputDirectory:outputDirectory];
|
||||
- (ORKRecorder *)recorderForStep:(ORKStep *)step outputDirectory:(NSURL *)outputDirectory {
|
||||
return [[ORKAccelerometerRecorder alloc] initWithIdentifier:self.identifier
|
||||
frequency:self.frequency
|
||||
step:step
|
||||
outputDirectory:outputDirectory];
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder
|
||||
{
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [super initWithCoder:aDecoder];
|
||||
if (self)
|
||||
{
|
||||
if (self) {
|
||||
ORK_DECODE_DOUBLE(aDecoder, frequency);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder
|
||||
{
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
[super encodeWithCoder:aCoder];
|
||||
ORK_ENCODE_DOUBLE(aCoder, frequency);
|
||||
}
|
||||
|
||||
+ (BOOL)supportsSecureCoding
|
||||
{
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
- (BOOL)isEqual:(id)object {
|
||||
BOOL isParentSame = [super isEqual:object];
|
||||
|
||||
__typeof(self) castObject = object;
|
||||
return (isParentSame &&
|
||||
(self.frequency == castObject.frequency)) ;
|
||||
(self.frequency == castObject.frequency));
|
||||
}
|
||||
|
||||
|
||||
- (ORKPermissionMask)requestedPermissionMask {
|
||||
return ORKPermissionCoreMotionAccelerometer;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@@ -32,6 +32,9 @@
|
||||
#import <ResearchKit/ORKDefines.h>
|
||||
#import <ResearchKit/ORKStep.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <HealthKit/HealthKit.h>
|
||||
|
||||
@class ORKRecorderConfiguration;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@@ -100,7 +103,6 @@ automatically navigates forward when the timer expires.
|
||||
*/
|
||||
@property (nonatomic) BOOL shouldSpeakCountDown;
|
||||
|
||||
|
||||
/**
|
||||
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.
|
||||
@@ -191,8 +193,7 @@ The default value of this property is `NO`.
|
||||
|
||||
See also: `ORKRecorderConfiguration` and `ORKRecorder`.
|
||||
*/
|
||||
@property (nonatomic, copy, nullable) NSArray *recorderConfigurations;
|
||||
|
||||
@property (nonatomic, copy, nullable) NSArray<ORKRecorderConfiguration *> *recorderConfigurations;
|
||||
|
||||
/**
|
||||
The set of HealthKit types the step requests for reading. (read-only)
|
||||
@@ -204,21 +205,7 @@ The default value of this property is `NO`.
|
||||
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 *requestedHealthKitTypesForReading;
|
||||
|
||||
/**
|
||||
The set of access permissions required for the step. (read-only)
|
||||
|
||||
The permission mask is used by the task view controller to determine the types of
|
||||
access to request from users when they complete the initial instruction steps
|
||||
in a task. If your step requires access to APIs that limit access, include
|
||||
the permissions you require in this mask.
|
||||
|
||||
By default, the property scans the recorders and collates the permissions
|
||||
required by the recorders. Subclasses may override this implementation.
|
||||
*/
|
||||
@property (nonatomic, readonly) ORKPermissionMask requestedPermissions;
|
||||
|
||||
@property (nonatomic, readonly, nullable) NSSet<HKObjectType *> *requestedHealthKitTypesForReading;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -36,10 +36,10 @@
|
||||
#import "ORKActiveStepViewController.h"
|
||||
#import "ORKRecorder_Private.h"
|
||||
|
||||
|
||||
@implementation ORKActiveStep
|
||||
|
||||
+ (Class)stepViewControllerClass
|
||||
{
|
||||
+ (Class)stepViewControllerClass {
|
||||
return [ORKActiveStepViewController class];
|
||||
}
|
||||
|
||||
@@ -53,25 +53,23 @@
|
||||
|
||||
- (BOOL)hasTitle {
|
||||
NSString *title = self.title;
|
||||
return ( title != nil && title.length > 0);
|
||||
return (title != nil && title.length > 0);
|
||||
}
|
||||
|
||||
- (BOOL)hasText {
|
||||
NSString *text = self.text;
|
||||
return ( text != nil && text.length > 0);
|
||||
return (text != nil && text.length > 0);
|
||||
}
|
||||
|
||||
- (BOOL)hasVoice {
|
||||
return ( _spokenInstruction != nil && _spokenInstruction.length > 0);
|
||||
return (_spokenInstruction != nil && _spokenInstruction.length > 0);
|
||||
}
|
||||
|
||||
- (BOOL)isRestorable {
|
||||
return NO;
|
||||
}
|
||||
|
||||
|
||||
+ (BOOL)supportsSecureCoding
|
||||
{
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
@@ -83,8 +81,7 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
- (instancetype)copyWithZone:(NSZone *)zone {
|
||||
ORKActiveStep *step = [super copyWithZone:zone];
|
||||
step.stepDuration = self.stepDuration;
|
||||
step.shouldStartTimerAutomatically = self.shouldStartTimerAutomatically;
|
||||
@@ -102,12 +99,9 @@
|
||||
return step;
|
||||
}
|
||||
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder
|
||||
{
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [super initWithCoder:aDecoder];
|
||||
if (self)
|
||||
{
|
||||
if (self ) {
|
||||
ORK_DECODE_DOUBLE(aDecoder, stepDuration);
|
||||
ORK_DECODE_BOOL(aDecoder, shouldStartTimerAutomatically);
|
||||
ORK_DECODE_BOOL(aDecoder, shouldSpeakCountDown);
|
||||
@@ -125,8 +119,7 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder
|
||||
{
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
[super encodeWithCoder:aCoder];
|
||||
ORK_ENCODE_DOUBLE(aCoder, stepDuration);
|
||||
ORK_ENCODE_BOOL(aCoder, shouldStartTimerAutomatically);
|
||||
@@ -143,8 +136,6 @@
|
||||
ORK_ENCODE_OBJ(aCoder, recorderConfigurations);
|
||||
}
|
||||
|
||||
|
||||
|
||||
- (BOOL)isEqual:(id)object {
|
||||
BOOL isParentSame = [super isEqual:object];
|
||||
|
||||
@@ -162,27 +153,22 @@
|
||||
(self.shouldVibrateOnStart == castObject.shouldVibrateOnStart) &&
|
||||
(self.shouldVibrateOnFinish == castObject.shouldVibrateOnFinish) &&
|
||||
(self.shouldContinueOnFinish == castObject.shouldContinueOnFinish) &&
|
||||
(self.shouldUseNextAsSkipButton == castObject.shouldUseNextAsSkipButton)) ;
|
||||
(self.shouldUseNextAsSkipButton == castObject.shouldUseNextAsSkipButton));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
- (NSSet *)requestedHealthKitTypesForReading {
|
||||
NSMutableSet *set = [NSMutableSet set];
|
||||
- (NSSet<HKObjectType *> *)requestedHealthKitTypesForReading {
|
||||
NSMutableSet<HKObjectType *> *set = [NSMutableSet set];
|
||||
for (ORKRecorderConfiguration *config in self.recorderConfigurations) {
|
||||
NSSet *subset = [config requestedHealthKitTypesForReading];
|
||||
NSSet<HKObjectType *> *subset = [config requestedHealthKitTypesForReading];
|
||||
if (subset) {
|
||||
[set unionSet:subset];
|
||||
}
|
||||
}
|
||||
return set;
|
||||
|
||||
}
|
||||
|
||||
- (ORKPermissionMask)requestedPermissions {
|
||||
ORKPermissionMask mask = ORKPermissionNone;
|
||||
ORKPermissionMask mask = [super requestedPermissions];
|
||||
for (ORKRecorderConfiguration *config in self.recorderConfigurations) {
|
||||
mask |= [config requestedPermissionMask];
|
||||
}
|
||||
|
||||
@@ -28,15 +28,18 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import <ResearchKit/ResearchKit_Private.h>
|
||||
#import "ORKLabel.h"
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ORKQuantityLabel : ORKLabel
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface ORKActiveStepQuantityView : UIView
|
||||
|
||||
@property (nonatomic, strong, nullable) NSString *title;
|
||||
@@ -44,13 +47,12 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@property (nonatomic, strong, nullable) UIImage *image;
|
||||
@property (nonatomic) BOOL enabled;
|
||||
|
||||
|
||||
@property (nonatomic, readonly, strong, nullable) UILabel *titleLabel;
|
||||
@property (nonatomic, readonly, strong, nullable) UILabel *valueLabel;
|
||||
|
||||
@property (nonatomic, strong, readonly, nullable) UILabel *titleLabel;
|
||||
@property (nonatomic, strong, readonly, nullable) UILabel *valueLabel;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface ORKQuantityPairView : UIView
|
||||
|
||||
@property (nonatomic, strong, nullable) ORKActiveStepQuantityView *leftView;
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKActiveStepQuantityView.h"
|
||||
#import "ORKHelpers.h"
|
||||
#import "ORKSkin.h"
|
||||
@@ -44,12 +45,13 @@
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKActiveStepQuantityView
|
||||
{
|
||||
@implementation ORKActiveStepQuantityView {
|
||||
ORKSubheadlineLabel *_titleLabel;
|
||||
ORKQuantityLabel *_valueLabel;
|
||||
ORKTintedImageView *_imageView;
|
||||
UIView *_valueHolder;
|
||||
|
||||
NSLayoutConstraint *_zeroWidthConstraint;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
@@ -82,8 +84,8 @@
|
||||
view.isAccessibilityElement = NO;
|
||||
}
|
||||
|
||||
[self setupConstraints];
|
||||
[self setNeedsUpdateConstraints];
|
||||
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -109,69 +111,95 @@
|
||||
_imageView.image = image;
|
||||
}
|
||||
|
||||
- (void)updateConstraints {
|
||||
[self removeConstraints:[self constraints]];
|
||||
- (void)setupConstraints {
|
||||
|
||||
const CGFloat TitleBaselineToValueBaseline = 40;
|
||||
const CGFloat ValueBaselineToBottom = 36;
|
||||
|
||||
if (! _enabled) {
|
||||
NSLayoutConstraint *zeroWidthConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:0];
|
||||
zeroWidthConstraint.priority = UILayoutPriorityRequired-1;
|
||||
[self addConstraint:zeroWidthConstraint];
|
||||
}
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(_titleLabel, _valueLabel, _imageView);
|
||||
NSMutableArray *additionalConstraints = [NSMutableArray array];
|
||||
[additionalConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_titleLabel]" options:0 metrics:nil views:views]];
|
||||
[additionalConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_titleLabel]|" options:0 metrics:nil views:views]];
|
||||
[additionalConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_imageView]-10-[_valueLabel]|" options:NSLayoutFormatAlignAllCenterY metrics:nil views:views]];
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(_titleLabel, _valueLabel, _imageView);
|
||||
[additionalConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_titleLabel]"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil
|
||||
views:views]];
|
||||
[additionalConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_titleLabel]|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil
|
||||
views:views]];
|
||||
[additionalConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_imageView]-10-[_valueLabel]|"
|
||||
options:NSLayoutFormatAlignAllCenterY
|
||||
metrics:nil
|
||||
views:views]];
|
||||
[additionalConstraints addObject:[NSLayoutConstraint constraintWithItem:_valueLabel
|
||||
attribute:NSLayoutAttributeFirstBaseline
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_titleLabel
|
||||
attribute:NSLayoutAttributeLastBaseline
|
||||
multiplier:1 constant:TitleBaselineToValueBaseline]];
|
||||
multiplier:1.0
|
||||
constant:TitleBaselineToValueBaseline]];
|
||||
[additionalConstraints addObject:[NSLayoutConstraint constraintWithItem:self
|
||||
attribute:NSLayoutAttributeBottom
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_valueLabel
|
||||
attribute:NSLayoutAttributeLastBaseline
|
||||
multiplier:1 constant:ValueBaselineToBottom]];
|
||||
multiplier:1.0
|
||||
constant:ValueBaselineToBottom]];
|
||||
[additionalConstraints addObject:[NSLayoutConstraint constraintWithItem:_valueHolder
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
multiplier:1 constant:0]];
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
[additionalConstraints addObject:[NSLayoutConstraint constraintWithItem:_valueLabel
|
||||
attribute:NSLayoutAttributeTop
|
||||
relatedBy:NSLayoutRelationGreaterThanOrEqual
|
||||
toItem:_valueHolder
|
||||
attribute:NSLayoutAttributeTop
|
||||
multiplier:1 constant:0]];
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
[additionalConstraints addObject:[NSLayoutConstraint constraintWithItem:_valueLabel
|
||||
attribute:NSLayoutAttributeBottom
|
||||
relatedBy:NSLayoutRelationLessThanOrEqual
|
||||
toItem:_valueHolder
|
||||
attribute:NSLayoutAttributeBottom
|
||||
multiplier:1 constant:0]];
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
[additionalConstraints addObject:[NSLayoutConstraint constraintWithItem:_valueHolder
|
||||
attribute:NSLayoutAttributeLeft
|
||||
relatedBy:NSLayoutRelationGreaterThanOrEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeLeft
|
||||
multiplier:1 constant:0]];
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
[additionalConstraints addObject:[NSLayoutConstraint constraintWithItem:_valueHolder
|
||||
attribute:NSLayoutAttributeRight
|
||||
relatedBy:NSLayoutRelationLessThanOrEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeRight
|
||||
multiplier:1 constant:0]];
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
for (NSLayoutConstraint *constraint in additionalConstraints) {
|
||||
constraint.priority = UILayoutPriorityDefaultHigh;
|
||||
constraint.priority = UILayoutPriorityRequired-2;
|
||||
}
|
||||
[self addConstraints:additionalConstraints];
|
||||
|
||||
[NSLayoutConstraint activateConstraints:additionalConstraints];
|
||||
|
||||
|
||||
NSLayoutConstraint *zeroWidthConstraint = [NSLayoutConstraint constraintWithItem:self
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1.0
|
||||
constant:0.0];
|
||||
zeroWidthConstraint.priority = UILayoutPriorityRequired-1;
|
||||
_zeroWidthConstraint = zeroWidthConstraint;
|
||||
_zeroWidthConstraint.active = !_enabled;
|
||||
}
|
||||
|
||||
- (void)updateConstraints {
|
||||
_zeroWidthConstraint.active = !_enabled;
|
||||
[super updateConstraints];
|
||||
}
|
||||
|
||||
@@ -195,10 +223,9 @@
|
||||
|
||||
@end
|
||||
|
||||
@implementation ORKQuantityPairView
|
||||
{
|
||||
|
||||
@implementation ORKQuantityPairView {
|
||||
UIView *_metricKeyline;
|
||||
NSArray *_constraints;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
@@ -215,59 +242,88 @@
|
||||
[self setKeylineHidden:NO];
|
||||
_metricKeyline.backgroundColor = [UIColor ork_midGrayTintColor];
|
||||
|
||||
|
||||
[self addSubview:_leftView];
|
||||
[self addSubview:_rightView];
|
||||
[self addSubview:_metricKeyline];
|
||||
[self setNeedsUpdateConstraints];
|
||||
|
||||
[self setupConstraints];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)updateConstraints {
|
||||
- (void)setupConstraints {
|
||||
|
||||
|
||||
if (_constraints) {
|
||||
[self removeConstraints:_constraints];
|
||||
_constraints = nil;
|
||||
}
|
||||
NSMutableArray *constraints = [NSMutableArray array];
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(_leftView, _rightView, _metricKeyline);
|
||||
|
||||
// Leave space for the keyline between these views, and then constrain it to be 1px wide and go from top to bottom baseline of metric views.
|
||||
CGFloat scale = [[UIScreen mainScreen] scale];
|
||||
NSArray *vertConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_leftView]|" options:(NSLayoutFormatOptions)0 metrics:nil views:views];
|
||||
NSArray *vertConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_leftView]|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil
|
||||
views:views];
|
||||
[constraints addObjectsFromArray:vertConstraints];
|
||||
|
||||
NSArray *horizConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_leftView]-s-[_rightView]-|" options:NSLayoutFormatAlignAllTop|NSLayoutFormatAlignAllBottom|NSLayoutFormatDirectionLeftToRight metrics:@{@"s":@(1/scale)} views:views];
|
||||
NSArray *horizConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_leftView]-s-[_rightView]-|"
|
||||
options:NSLayoutFormatAlignAllTop|NSLayoutFormatAlignAllBottom|NSLayoutFormatDirectionLeftToRight
|
||||
metrics:@{ @"s": @(1/scale) }
|
||||
views:views];
|
||||
for (NSLayoutConstraint *constraint in horizConstraints) {
|
||||
constraint.priority = UILayoutPriorityDefaultHigh+1;
|
||||
}
|
||||
[constraints addObjectsFromArray:horizConstraints];
|
||||
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[_leftView][_metricKeyline(==s)]" options:NSLayoutFormatAlignAllTop|NSLayoutFormatDirectionLeftToRight metrics:@{@"s":@(1/scale)} views:views]];
|
||||
// Ensure baseline alignment of title and value
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_leftView.titleLabel
|
||||
attribute:NSLayoutAttributeFirstBaseline
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_rightView.titleLabel
|
||||
attribute:NSLayoutAttributeFirstBaseline
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_leftView.valueLabel
|
||||
attribute:NSLayoutAttributeFirstBaseline
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_rightView.valueLabel
|
||||
attribute:NSLayoutAttributeFirstBaseline
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[_leftView][_metricKeyline(==s)]"
|
||||
options:NSLayoutFormatAlignAllTop|NSLayoutFormatDirectionLeftToRight
|
||||
metrics:@{ @"s": @(1/scale) }
|
||||
views:views]];
|
||||
NSLayoutConstraint *keylineBottom = [NSLayoutConstraint constraintWithItem:_metricKeyline
|
||||
attribute:NSLayoutAttributeBottom
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_leftView.valueLabel
|
||||
attribute:NSLayoutAttributeLastBaseline
|
||||
multiplier:1 constant:0];
|
||||
multiplier:1.0
|
||||
constant:0.0];
|
||||
[constraints addObject:keylineBottom];
|
||||
|
||||
NSLayoutConstraint *maxWidthConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:10000];
|
||||
NSLayoutConstraint *maxWidthConstraint = [NSLayoutConstraint constraintWithItem:self
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1.0
|
||||
constant:ORKScreenMetricMaxDimension];
|
||||
maxWidthConstraint.priority = UILayoutPriorityRequired-2;
|
||||
[constraints addObject:maxWidthConstraint];
|
||||
|
||||
|
||||
// This constraint should be beaten out by the full-width-coverage and zero-width constraints if only one of the views is enabled.
|
||||
NSLayoutConstraint *equalWidthConstraint = [NSLayoutConstraint constraintWithItem:_leftView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:_rightView attribute:NSLayoutAttributeWidth multiplier:1 constant:0];
|
||||
NSLayoutConstraint *equalWidthConstraint = [NSLayoutConstraint constraintWithItem:_leftView
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_rightView
|
||||
attribute:NSLayoutAttributeWidth
|
||||
multiplier:1.0
|
||||
constant:0.0];
|
||||
equalWidthConstraint.priority = UILayoutPriorityDefaultLow;
|
||||
[constraints addObject:equalWidthConstraint];
|
||||
|
||||
[self addConstraints:constraints];
|
||||
_constraints = constraints;
|
||||
[super updateConstraints];
|
||||
[NSLayoutConstraint activateConstraints:constraints];
|
||||
}
|
||||
|
||||
- (void)setKeylineHidden:(BOOL)keylineHidden {
|
||||
@@ -276,4 +332,3 @@
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -28,16 +28,17 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <ResearchKit/ResearchKit.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class ORKActiveStepTimer;
|
||||
|
||||
typedef void (^ORKActiveStepTimerHandler)(ORKActiveStepTimer *timer, BOOL finished);
|
||||
|
||||
|
||||
@interface ORKActiveStepTimer : NSObject
|
||||
|
||||
- (instancetype)initWithDuration:(NSTimeInterval)duration interval:(NSTimeInterval)interval runtime:(NSTimeInterval)runtime handler:(ORKActiveStepTimerHandler)handler;
|
||||
|
||||
@@ -28,24 +28,23 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKActiveStepTimer.h"
|
||||
#include <mach/mach.h>
|
||||
#include <mach/mach_time.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
|
||||
static NSTimeInterval timeIntervalFromMachTime(uint64_t delta) {
|
||||
|
||||
static mach_timebase_info_data_t sTimebaseInfo;
|
||||
if ( sTimebaseInfo.denom == 0 ) {
|
||||
(void) mach_timebase_info(&sTimebaseInfo);
|
||||
}
|
||||
uint64_t elapsedNano = delta * sTimebaseInfo.numer / sTimebaseInfo.denom;
|
||||
|
||||
return elapsedNano * 1.0 / NSEC_PER_SEC;
|
||||
}
|
||||
|
||||
@implementation ORKActiveStepTimer
|
||||
{
|
||||
@implementation ORKActiveStepTimer {
|
||||
uint64_t _startTime;
|
||||
NSTimeInterval _preExistingRuntime;
|
||||
dispatch_queue_t _queue;
|
||||
@@ -54,7 +53,6 @@ static NSTimeInterval timeIntervalFromMachTime(uint64_t delta) {
|
||||
uint32_t _isRunning;
|
||||
}
|
||||
|
||||
|
||||
- (instancetype)initWithDuration:(NSTimeInterval)duration interval:(NSTimeInterval)interval runtime:(NSTimeInterval)runtime handler:(ORKActiveStepTimerHandler)handler {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
@@ -158,10 +156,10 @@ static NSTimeInterval timeIntervalFromMachTime(uint64_t delta) {
|
||||
if (_backgroundTaskIdentifier == UIBackgroundTaskInvalid) {
|
||||
return;
|
||||
}
|
||||
UIBackgroundTaskIdentifier ident = _backgroundTaskIdentifier;
|
||||
UIBackgroundTaskIdentifier identifier = _backgroundTaskIdentifier;
|
||||
_backgroundTaskIdentifier = UIBackgroundTaskInvalid;
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[[UIApplication sharedApplication] endBackgroundTask:ident];
|
||||
[[UIApplication sharedApplication] endBackgroundTask:identifier];
|
||||
});
|
||||
}
|
||||
|
||||
@@ -178,7 +176,6 @@ static NSTimeInterval timeIntervalFromMachTime(uint64_t delta) {
|
||||
}
|
||||
|
||||
- (void)queue_resume {
|
||||
|
||||
if (_timer != NULL) {
|
||||
// Already resumed
|
||||
return;
|
||||
@@ -211,7 +208,7 @@ static NSTimeInterval timeIntervalFromMachTime(uint64_t delta) {
|
||||
_startTime = mach_absolute_time();
|
||||
dispatch_source_set_timer(_timer,
|
||||
dispatch_time(DISPATCH_TIME_NOW, timeUntilNextFire * NSEC_PER_SEC),
|
||||
_interval * NSEC_PER_SEC ,
|
||||
_interval * NSEC_PER_SEC,
|
||||
0.05 * NSEC_PER_SEC);
|
||||
dispatch_resume(_timer);
|
||||
}
|
||||
@@ -239,5 +236,4 @@ static NSTimeInterval timeIntervalFromMachTime(uint64_t delta) {
|
||||
[self queue_releaseBackgroundTask];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@@ -28,13 +28,14 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <ResearchKit/ResearchKit_Private.h>
|
||||
#import "ORKCustomStepView_Internal.h"
|
||||
#import "ORKCountdownLabel.h"
|
||||
#import "ORKTextButton.h"
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ORKActiveStepTimerView : ORKActiveStepCustomView
|
||||
@@ -46,4 +47,4 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKActiveStepTimerView.h"
|
||||
#import "ORKActiveStepTimer.h"
|
||||
#import "ORKHelpers.h"
|
||||
@@ -41,18 +42,14 @@
|
||||
#import "ORKStepViewController_Internal.h"
|
||||
|
||||
|
||||
@implementation ORKActiveStepTimerView
|
||||
{
|
||||
@implementation ORKActiveStepTimerView {
|
||||
BOOL _started;
|
||||
BOOL _registeredForNotifications;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self)
|
||||
{
|
||||
|
||||
if (self) {
|
||||
// Count Down
|
||||
{
|
||||
_countDownLabel = [ORKCountdownLabel new];
|
||||
@@ -61,10 +58,8 @@
|
||||
|
||||
[self addSubview:_countDownLabel];
|
||||
}
|
||||
|
||||
// Count down start button
|
||||
{
|
||||
|
||||
_startTimerButton = [ORKTextButton new];
|
||||
[_startTimerButton setTitle:ORKLocalizedString(@"BUTTON_START_TIMER", nil) forState:UIControlStateNormal];
|
||||
[_startTimerButton addTarget:self action:@selector(startTimerButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
|
||||
@@ -76,16 +71,14 @@
|
||||
_countDownLabel.accessibilityTraits |= UIAccessibilityTraitUpdatesFrequently;
|
||||
|
||||
[self setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
|
||||
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)applicationDidBecomeActive:(NSNotification *)notification {
|
||||
ORKActiveStepViewController *vc = self.activeStepViewController;
|
||||
if (vc) {
|
||||
[self updateDisplay:vc];
|
||||
ORKActiveStepViewController *viewController = self.activeStepViewController;
|
||||
if (viewController) {
|
||||
[self updateDisplay:viewController];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +86,7 @@
|
||||
if (registered == _registeredForNotifications) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
registered = _registeredForNotifications;
|
||||
NSNotificationCenter *nfc = [NSNotificationCenter defaultCenter];
|
||||
if (registered) {
|
||||
@@ -107,9 +100,7 @@
|
||||
[self setRegisteredForNotifications:(self.window != nil)];
|
||||
}
|
||||
|
||||
|
||||
- (void)setStep:(ORKActiveStep *)step
|
||||
{
|
||||
- (void)setStep:(ORKActiveStep *)step {
|
||||
_step = step;
|
||||
_countDownLabel.hidden = !(_step.hasCountDown);
|
||||
BOOL hasTimerButton = (_step.hasCountDown && _step.shouldStartTimerAutomatically == NO);
|
||||
@@ -121,19 +112,16 @@
|
||||
[self setNeedsUpdateConstraints];
|
||||
}
|
||||
|
||||
- (void)startTimerButtonTapped:(id)sender
|
||||
{
|
||||
- (void)startTimerButtonTapped:(id)sender {
|
||||
[self.activeStepViewController start];
|
||||
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, _countDownLabel);
|
||||
}
|
||||
|
||||
|
||||
- (void)updateDisplay:(ORKActiveStepViewController *)viewController {
|
||||
NSInteger countDownValue = (NSInteger)round(viewController.timeRemaining);
|
||||
[_countDownLabel setCountDownValue:countDownValue];
|
||||
}
|
||||
|
||||
|
||||
- (void)resetStep:(ORKActiveStepViewController *)viewController {
|
||||
self.step = (ORKActiveStep *)viewController.step;
|
||||
}
|
||||
@@ -147,8 +135,7 @@
|
||||
|
||||
- (void)resumeStep:(ORKActiveStepViewController *)viewController {
|
||||
self.step = (ORKActiveStep *)viewController.step;
|
||||
if ([viewController timerActive])
|
||||
{
|
||||
if ([viewController timerActive]) {
|
||||
_startTimerButton.alpha = 0;
|
||||
[self updateDisplay:viewController];
|
||||
}
|
||||
@@ -157,44 +144,57 @@
|
||||
- (void)finishStep:(ORKActiveStepViewController *)viewController {
|
||||
}
|
||||
|
||||
- (void)setNeedsUpdateConstraints
|
||||
{
|
||||
- (void)setNeedsUpdateConstraints {
|
||||
[NSLayoutConstraint deactivateConstraints:[self constraints]];
|
||||
[super setNeedsUpdateConstraints];
|
||||
|
||||
}
|
||||
|
||||
- (void)updateConstraints
|
||||
{
|
||||
- (void)updateConstraints {
|
||||
NSDictionary *dictionary = NSDictionaryOfVariableBindings(_countDownLabel, _startTimerButton);
|
||||
NSDictionary *metrics = @{@"CS" : @(2)};
|
||||
ORKEnableAutoLayoutForViews([dictionary allValues]);
|
||||
|
||||
for (UIView *v in [dictionary allValues])
|
||||
{
|
||||
[self addConstraint:[NSLayoutConstraint constraintWithItem:v attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationLessThanOrEqual toItem:self attribute:NSLayoutAttributeWidth multiplier:1 constant:0]];
|
||||
[self addConstraint:[NSLayoutConstraint constraintWithItem:v attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationLessThanOrEqual toItem:self attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]];
|
||||
for (UIView *view in [dictionary allValues]) {
|
||||
[self addConstraint:[NSLayoutConstraint
|
||||
constraintWithItem:view
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationLessThanOrEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeWidth
|
||||
multiplier:1
|
||||
constant:0]];
|
||||
[self addConstraint:[NSLayoutConstraint
|
||||
constraintWithItem:view
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
relatedBy:NSLayoutRelationLessThanOrEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
multiplier:1
|
||||
constant:0]];
|
||||
}
|
||||
|
||||
if (! _countDownLabel.hidden)
|
||||
{
|
||||
if (! _countDownLabel.hidden) {
|
||||
NSMutableString *verticalLayout = [NSMutableString new];
|
||||
[verticalLayout appendString:@"V:|[_countDownLabel]"];
|
||||
if (! _startTimerButton.hidden)
|
||||
{
|
||||
if (! _startTimerButton.hidden) {
|
||||
[verticalLayout appendString:@"-CS-[_startTimerButton]"];
|
||||
}
|
||||
[verticalLayout appendString:@"|"];
|
||||
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:verticalLayout options:NSLayoutFormatAlignAllCenterX metrics:metrics views:dictionary]];
|
||||
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:verticalLayout
|
||||
options:NSLayoutFormatAlignAllCenterX
|
||||
metrics:metrics
|
||||
views:dictionary]];
|
||||
} else {
|
||||
[self addConstraint:[NSLayoutConstraint constraintWithItem:self
|
||||
attribute:NSLayoutAttributeHeight
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1
|
||||
constant:0]];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self addConstraint:[NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:0]];
|
||||
}
|
||||
|
||||
[super updateConstraints];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
|
||||
/**
|
||||
The `ORKActiveStepViewController` class is the base class for displaying `ORKActiveStep`
|
||||
subclasses. The predefined active tasks defined in `ORKOrderedTask` all make use
|
||||
@@ -105,10 +104,8 @@ ORK_CLASS_AVAILABLE
|
||||
*/
|
||||
@property (nonatomic, strong, readonly, nullable) NSArray *recorders;
|
||||
|
||||
|
||||
/// @name Active step life cycle
|
||||
|
||||
|
||||
/**
|
||||
A Boolean value that indicates whether the step has finished. (read-only)
|
||||
|
||||
@@ -118,8 +115,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
In addition, when a step is finished, all recorders are stopped.
|
||||
*/
|
||||
@property (nonatomic, assign, getter=isFinished, readonly) BOOL finished;
|
||||
|
||||
@property (nonatomic, assign, readonly, getter=isFinished) BOOL finished;
|
||||
|
||||
/**
|
||||
A Boolean value that indicates whether the step has started. (read-only)
|
||||
@@ -140,7 +136,6 @@ ORK_CLASS_AVAILABLE
|
||||
*/
|
||||
- (void)stepDidFinish;
|
||||
|
||||
|
||||
/**
|
||||
A Boolean value that indicates whether to suspend the step if the app is not
|
||||
active or the screen is off.
|
||||
|
||||
@@ -48,7 +48,8 @@
|
||||
#import "ORKStepHeaderView_Internal.h"
|
||||
#import "ORKActiveStepView.h"
|
||||
|
||||
@interface ORKActiveStepViewController (){
|
||||
|
||||
@interface ORKActiveStepViewController () {
|
||||
ORKActiveStepView *_activeStepView;
|
||||
ORKActiveStepTimer *_activeStepTimer;
|
||||
|
||||
@@ -76,10 +77,8 @@
|
||||
_timerUpdateInterval = 1;
|
||||
}
|
||||
return self;
|
||||
|
||||
}
|
||||
|
||||
|
||||
- (void)applicationWillResignActive:(NSNotification *)notification {
|
||||
if (self.suspendIfInactive) {
|
||||
[self suspend];
|
||||
@@ -100,8 +99,7 @@
|
||||
return _activeStepView;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad
|
||||
{
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
_activeStepView = [[ORKActiveStepView alloc] initWithFrame:self.view.bounds];
|
||||
@@ -119,8 +117,8 @@
|
||||
|
||||
[self prepareStep];
|
||||
}
|
||||
- (void)stepDidChange
|
||||
{
|
||||
|
||||
- (void)stepDidChange {
|
||||
[super stepDidChange];
|
||||
_activeStepView.activeStep = [self activeStep];
|
||||
[self updateContinueButtonItem];
|
||||
@@ -129,20 +127,17 @@
|
||||
[self prepareStep];
|
||||
}
|
||||
|
||||
- (UIView *)customViewContainer
|
||||
{
|
||||
__unused UIView *v = [self view];
|
||||
- (UIView *)customViewContainer {
|
||||
__unused UIView *view = [self view];
|
||||
return _activeStepView.customViewContainer;
|
||||
}
|
||||
|
||||
- (UIImageView *)imageView
|
||||
{
|
||||
__unused UIView *v = [self view];
|
||||
- (ORKTintedImageView *)imageView {
|
||||
__unused UIView *view = [self view];
|
||||
return _activeStepView.imageView;
|
||||
}
|
||||
|
||||
- (void)setCustomView:(UIView *)customView
|
||||
{
|
||||
- (void)setCustomView:(UIView *)customView {
|
||||
_customView = customView;
|
||||
[_activeStepView setStepView:_customView];
|
||||
}
|
||||
@@ -154,21 +149,16 @@
|
||||
[self.taskViewController setRegisteredScrollView:_activeStepView];
|
||||
}
|
||||
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
[super viewDidAppear:animated];
|
||||
ORK_Log_Debug(@"%@",self);
|
||||
|
||||
// Wait for animation complete
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
|
||||
if ([[self activeStep] shouldStartTimerAutomatically])
|
||||
{
|
||||
if ([[self activeStep] shouldStartTimerAutomatically]) {
|
||||
[self start];
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
@@ -187,22 +177,18 @@
|
||||
[self updateContinueButtonItem];
|
||||
}
|
||||
|
||||
- (void)setLearnMoreButtonItem:(UIBarButtonItem *)learnMoreButtonItem
|
||||
{
|
||||
- (void)setLearnMoreButtonItem:(UIBarButtonItem *)learnMoreButtonItem {
|
||||
[super setLearnMoreButtonItem:learnMoreButtonItem];
|
||||
_activeStepView.headerView.learnMoreButtonItem = self.learnMoreButtonItem;
|
||||
}
|
||||
|
||||
- (void)setSkipButtonItem:(UIBarButtonItem *)skipButtonItem
|
||||
{
|
||||
- (void)setSkipButtonItem:(UIBarButtonItem *)skipButtonItem {
|
||||
[super setSkipButtonItem:skipButtonItem];
|
||||
_activeStepView.continueSkipContainer.skipButtonItem = skipButtonItem;
|
||||
}
|
||||
|
||||
- (void)setFinished:(BOOL)finished
|
||||
{
|
||||
- (void)setFinished:(BOOL)finished {
|
||||
_finished = finished;
|
||||
|
||||
_activeStepView.continueSkipContainer.continueEnabled = finished;
|
||||
}
|
||||
|
||||
@@ -215,28 +201,21 @@
|
||||
#pragma mark - transition
|
||||
|
||||
- (void)recordersDidChange {
|
||||
|
||||
}
|
||||
|
||||
- (void)recordersWillStart {
|
||||
|
||||
}
|
||||
|
||||
- (void)recordersWillStop {
|
||||
|
||||
}
|
||||
|
||||
|
||||
- (void)prepareRecorders
|
||||
{
|
||||
- (void)prepareRecorders {
|
||||
// Stop any existing recorders
|
||||
[self recordersWillStop];
|
||||
for (ORKRecorder *recorder in self.recorders)
|
||||
{
|
||||
for (ORKRecorder *recorder in self.recorders) {
|
||||
recorder.delegate = nil;
|
||||
[recorder stop];
|
||||
}
|
||||
|
||||
NSMutableArray *recorders = [NSMutableArray array];
|
||||
|
||||
for (ORKRecorderConfiguration * provider in self.activeStep.recorderConfigurations) {
|
||||
@@ -249,7 +228,6 @@
|
||||
|
||||
[recorders addObject:recorder];
|
||||
}
|
||||
|
||||
self.recorders = recorders;
|
||||
|
||||
[self recordersDidChange];
|
||||
@@ -261,8 +239,7 @@
|
||||
}
|
||||
|
||||
- (void)prepareStep {
|
||||
|
||||
if (self.activeStep==nil) {
|
||||
if (self.activeStep == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -271,13 +248,10 @@
|
||||
ORK_Log_Debug(@"%@", self);
|
||||
_activeStepView.activeStep = self.activeStep;
|
||||
|
||||
if ([self.activeStep hasCountDown])
|
||||
{
|
||||
if ([self.activeStep hasCountDown]) {
|
||||
ORKActiveStepTimerView *timerView = [ORKActiveStepTimerView new];
|
||||
_activeStepView.activeCustomView = timerView;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
_activeStepView.activeCustomView = nil;
|
||||
}
|
||||
_activeStepView.activeCustomView.activeStepViewController = self;
|
||||
@@ -291,7 +265,6 @@
|
||||
[self recordersWillStart];
|
||||
// Start recorders
|
||||
for (ORKRecorder *recorder in self.recorders) {
|
||||
|
||||
[recorder viewController:self willStartStepWithView:self.customViewContainer];
|
||||
[recorder start];
|
||||
}
|
||||
@@ -304,8 +277,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)playSound
|
||||
{
|
||||
- (void)playSound {
|
||||
if (_alertSoundURL == nil) {
|
||||
_alertSoundURL = [NSURL URLWithString:@"/System/Library/Audio/UISounds/short_low_high.caf"];
|
||||
AudioServicesCreateSystemSoundID((__bridge CFURLRef)(_alertSoundURL), &_alertSound);
|
||||
@@ -321,14 +293,11 @@
|
||||
|
||||
[self startRecorders];
|
||||
|
||||
|
||||
if (self.activeStep.shouldVibrateOnStart) {
|
||||
|
||||
AudioServicesPlayAlertSound(kSystemSoundID_Vibrate);
|
||||
}
|
||||
|
||||
if (self.activeStep.shouldPlaySoundOnStart) {
|
||||
|
||||
[self playSound];
|
||||
}
|
||||
|
||||
@@ -337,7 +306,7 @@
|
||||
// Let VO speak "Step x of y" before the instruction.
|
||||
// If VO is not running, the text is spoken immediately.
|
||||
ORKAccessibilityPerformBlockAfterDelay(1.5, ^{
|
||||
[[ORKVoiceEngine sharedVoiceEngine] speakText:(NSString *__nonnull)self.activeStep.spokenInstruction];
|
||||
[[ORKVoiceEngine sharedVoiceEngine] speakText:self.activeStep.spokenInstruction];
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -366,7 +335,6 @@
|
||||
[_activeStepView.activeCustomView resumeStep:self];
|
||||
}
|
||||
|
||||
|
||||
- (void)finish {
|
||||
ORK_Log_Debug(@"%@",self);
|
||||
if (self.finished) {
|
||||
@@ -405,14 +373,12 @@
|
||||
_activeStepTimer = nil;
|
||||
}
|
||||
|
||||
- (void)startTimer
|
||||
{
|
||||
- (void)startTimer {
|
||||
[self resetTimer];
|
||||
|
||||
NSTimeInterval stepDuration = self.activeStep.stepDuration;
|
||||
|
||||
if (stepDuration > 0) {
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
_activeStepTimer = [[ORKActiveStepTimer alloc] initWithDuration:stepDuration
|
||||
interval:_timerUpdateInterval
|
||||
@@ -426,7 +392,6 @@
|
||||
}
|
||||
|
||||
- (void)countDownTimerFired:(ORKActiveStepTimer *)timer finished:(BOOL)finished {
|
||||
|
||||
if (finished) {
|
||||
[self finish];
|
||||
}
|
||||
@@ -462,14 +427,12 @@
|
||||
|
||||
#pragma mark - action handlers
|
||||
|
||||
- (void)stepDidFinish
|
||||
{
|
||||
- (void)stepDidFinish {
|
||||
}
|
||||
|
||||
#pragma mark - ORKRecorderDelegate
|
||||
|
||||
- (void)recorder:(ORKRecorder *)recorder didCompleteWithResult:(ORKResult *)result {
|
||||
|
||||
_recorderResults = [_recorderResults arrayByAddingObject:result];
|
||||
[self notifyDelegateOnResultChange];
|
||||
}
|
||||
@@ -492,25 +455,21 @@
|
||||
}
|
||||
}
|
||||
|
||||
static NSString *const _ORKFinishedRestoreKey = @"finished";
|
||||
static NSString *const _ORKRecorderResultsRestoreKey = @"recorderResults";
|
||||
|
||||
static NSString * const _ORKFinishedRestoreKey = @"finished";
|
||||
static NSString * const _ORKRecorderResultsRestoreKey = @"recorderResults";
|
||||
|
||||
- (void)encodeRestorableStateWithCoder:(NSCoder *)coder
|
||||
{
|
||||
- (void)encodeRestorableStateWithCoder:(NSCoder *)coder {
|
||||
[super encodeRestorableStateWithCoder:coder];
|
||||
|
||||
[coder encodeBool:_finished forKey:_ORKFinishedRestoreKey];
|
||||
[coder encodeObject:_recorderResults forKey:_ORKRecorderResultsRestoreKey];
|
||||
}
|
||||
|
||||
- (void)decodeRestorableStateWithCoder:(NSCoder *)coder
|
||||
{
|
||||
- (void)decodeRestorableStateWithCoder:(NSCoder *)coder {
|
||||
[super decodeRestorableStateWithCoder:coder];
|
||||
|
||||
self.finished = [coder decodeBoolForKey:_ORKFinishedRestoreKey];
|
||||
_recorderResults = [coder decodeObjectOfClass:[NSArray class] forKey:_ORKRecorderResultsRestoreKey];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@@ -29,20 +29,20 @@
|
||||
*/
|
||||
|
||||
|
||||
|
||||
#import "ORKActiveStepViewController.h"
|
||||
#import "ORKActiveStepTimer.h"
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class ORKActiveStepView;
|
||||
|
||||
@interface ORKActiveStepViewController ()
|
||||
|
||||
|
||||
/**
|
||||
* The customViewContainer allows custom view to be its subview.
|
||||
* @note When ORKTouchRecorder is present, its gesture recognizer attaches to customViewContainer.
|
||||
The customViewContainer allows custom view to be its subview.
|
||||
|
||||
@note When ORKTouchRecorder is present, its gesture recognizer attaches to customViewContainer.
|
||||
*/
|
||||
@property (nonatomic, strong, readonly, nullable) UIView *customViewContainer;
|
||||
|
||||
@@ -50,10 +50,8 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@property (nonatomic, readonly) NSTimeInterval timeRemaining;
|
||||
@property (nonatomic, readonly) BOOL timerActive;
|
||||
|
||||
@property (nonatomic, assign) NSTimeInterval timerUpdateInterval;
|
||||
|
||||
|
||||
@property (nonatomic, assign, getter=isStarted) BOOL started;
|
||||
|
||||
- (void)countDownTimerFired:(ORKActiveStepTimer *)timer finished:(BOOL)finished; // Let subclass receive timer fires
|
||||
@@ -61,6 +59,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
- (void)applicationWillResignActive:(NSNotification *)notification;
|
||||
- (void)applicationDidBecomeActive:(NSNotification *)notification;
|
||||
|
||||
- (void)stopRecorders;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -28,12 +28,14 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import <ResearchKit/ORKActiveStep.h>
|
||||
|
||||
|
||||
@interface ORKActiveStep ()
|
||||
|
||||
/**
|
||||
* Convenience methods.
|
||||
Convenience methods.
|
||||
*/
|
||||
- (BOOL)startsFinished;
|
||||
- (BOOL)hasCountDown;
|
||||
@@ -41,5 +43,4 @@
|
||||
- (BOOL)hasText;
|
||||
- (BOOL)hasVoice;
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@@ -28,9 +28,11 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "ORKCustomStepView_Internal.h"
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ORKAudioContentView : ORKActiveStepCustomView
|
||||
@@ -38,6 +40,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@property (nonatomic, copy, nullable) UIColor *keyColor;
|
||||
@property (nonatomic, copy, nullable) UIColor *alertColor;
|
||||
|
||||
@property (nonatomic, assign) BOOL failed;
|
||||
@property (nonatomic, assign, getter=isFinished) BOOL finished;
|
||||
@property (nonatomic, assign) NSTimeInterval timeLeft;
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKAudioContentView.h"
|
||||
#import "ORKHelpers.h"
|
||||
#import "ORKSkin.h"
|
||||
@@ -35,6 +36,7 @@
|
||||
#import "ORKHeadlineLabel.h"
|
||||
#import "ORKAccessibility.h"
|
||||
|
||||
|
||||
// The central blue region.
|
||||
static const CGFloat GraphViewBlueZoneHeight = 170;
|
||||
|
||||
@@ -53,6 +55,7 @@ static const CGFloat GraphViewRedZoneHeight = 25;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
static const CGFloat kValueLineWidth = 4.5;
|
||||
static const CGFloat kValueLineMargin = 1.5;
|
||||
|
||||
@@ -61,17 +64,6 @@ static const CGFloat kValueLineMargin = 1.5;
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
NSLayoutConstraint *c1 = [NSLayoutConstraint constraintWithItem:self
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:CGFLOAT_MAX];
|
||||
c1.priority = UILayoutPriorityFittingSizeLevel;
|
||||
NSLayoutConstraint *c2 = [NSLayoutConstraint constraintWithItem:self
|
||||
attribute:NSLayoutAttributeHeight
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:CGFLOAT_MAX];
|
||||
c2.priority = UILayoutPriorityFittingSizeLevel;
|
||||
[NSLayoutConstraint activateConstraints:@[c1,c2]];
|
||||
|
||||
#if TARGET_IPHONE_SIMULATOR
|
||||
_values = @[@(0.2),@(0.6),@(0.55), @(0.1), @(0.75), @(0.7)];
|
||||
@@ -101,39 +93,39 @@ static const CGFloat kValueLineMargin = 1.5;
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)rect {
|
||||
CGRect r = self.bounds;
|
||||
CGRect bounds = self.bounds;
|
||||
|
||||
CGContextRef ctx = UIGraphicsGetCurrentContext();
|
||||
CGContextSetFillColorWithColor(ctx, [UIColor whiteColor].CGColor);
|
||||
CGContextFillRect(ctx, r);
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
|
||||
CGContextFillRect(context, bounds);
|
||||
|
||||
CGFloat scale = [self.window.screen scale];
|
||||
|
||||
CGFloat midY = CGRectGetMidY(r);
|
||||
CGFloat maxX = CGRectGetMaxX(r);
|
||||
CGFloat halfHeight = r.size.height/2;
|
||||
CGContextSaveGState(ctx);
|
||||
CGFloat midY = CGRectGetMidY(bounds);
|
||||
CGFloat maxX = CGRectGetMaxX(bounds);
|
||||
CGFloat halfHeight = bounds.size.height/2;
|
||||
CGContextSaveGState(context);
|
||||
{
|
||||
UIBezierPath *centerLine = [UIBezierPath new];
|
||||
[centerLine moveToPoint:(CGPoint){.x=0,.y=midY}];
|
||||
[centerLine addLineToPoint:(CGPoint){.x=maxX,.y=midY}];
|
||||
|
||||
CGContextSetLineWidth(ctx, 1/scale);
|
||||
CGContextSetLineWidth(context, 1/scale);
|
||||
[_keyColor setStroke];
|
||||
CGFloat lengths[2] = {3,3};
|
||||
CGContextSetLineDash(ctx, 0, lengths, 2);
|
||||
CGContextSetLineDash(context, 0, lengths, 2);
|
||||
|
||||
[centerLine stroke];
|
||||
}
|
||||
CGContextRestoreGState(ctx);
|
||||
CGContextRestoreGState(context);
|
||||
|
||||
CGFloat lineStep = kValueLineMargin + kValueLineWidth;
|
||||
|
||||
CGContextSaveGState(ctx);
|
||||
CGContextSaveGState(context);
|
||||
{
|
||||
CGFloat x = maxX - lineStep/2;
|
||||
CGContextSetLineWidth(ctx, kValueLineWidth);
|
||||
CGContextSetLineCap(ctx, kCGLineCapRound);
|
||||
CGContextSetLineWidth(context, kValueLineWidth);
|
||||
CGContextSetLineCap(context, kCGLineCapRound);
|
||||
|
||||
UIBezierPath *path1 = [UIBezierPath new];
|
||||
path1.lineCapStyle = kCGLineCapRound;
|
||||
@@ -143,16 +135,16 @@ static const CGFloat kValueLineMargin = 1.5;
|
||||
for (NSNumber *value in [_values reverseObjectEnumerator]) {
|
||||
CGFloat floatValue = [value doubleValue];
|
||||
|
||||
UIBezierPath *p = nil;
|
||||
UIBezierPath *path = nil;
|
||||
if (floatValue > _alertThreshold) {
|
||||
p = path1;
|
||||
path = path1;
|
||||
[_alertColor setStroke];
|
||||
} else {
|
||||
p = path2;
|
||||
path = path2;
|
||||
[_keyColor setStroke];
|
||||
}
|
||||
[p moveToPoint:(CGPoint){.x=x,.y=midY-floatValue*halfHeight}];
|
||||
[p addLineToPoint:(CGPoint){.x=x,.y=midY+floatValue*halfHeight}];
|
||||
[path moveToPoint:(CGPoint){.x=x,.y=midY-floatValue*halfHeight}];
|
||||
[path addLineToPoint:(CGPoint){.x=x,.y=midY+floatValue*halfHeight}];
|
||||
|
||||
x -= lineStep;
|
||||
|
||||
@@ -169,29 +161,29 @@ static const CGFloat kValueLineMargin = 1.5;
|
||||
[path2 stroke];
|
||||
|
||||
}
|
||||
CGContextRestoreGState(ctx);
|
||||
|
||||
CGContextRestoreGState(context);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface ORKAudioTimerLabel : ORKLabel
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKAudioTimerLabel
|
||||
|
||||
+ (UIFont *)defaultFont {
|
||||
|
||||
UIFontDescriptor *descriptor = [UIFontDescriptor preferredFontDescriptorWithTextStyle:UIFontTextStyleSubheadline];
|
||||
UIFontDescriptor *alternativeDescriptor = ORKFontDescriptorForLightStylisticAlternative(descriptor);
|
||||
return [UIFont fontWithDescriptor:alternativeDescriptor size:[alternativeDescriptor pointSize]+4];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@interface ORKAudioContentView()
|
||||
|
||||
@interface ORKAudioContentView ()
|
||||
|
||||
@property (nonatomic, strong) ORKHeadlineLabel *alertLabel;
|
||||
@property (nonatomic, strong) UILabel *timerLabel;
|
||||
@@ -199,8 +191,8 @@ static const CGFloat kValueLineMargin = 1.5;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ORKAudioContentView
|
||||
{
|
||||
|
||||
@implementation ORKAudioContentView {
|
||||
NSArray *_constraints;
|
||||
NSMutableArray *_samples;
|
||||
UIColor *_keyColor;
|
||||
@@ -209,8 +201,7 @@ static const CGFloat kValueLineMargin = 1.5;
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
CGFloat margin = ORKStandardMarginForView(self);
|
||||
self.layoutMargins = (UIEdgeInsets){.left=2*margin,.right=2*margin};
|
||||
self.layoutMargins = ORKStandardFullScreenLayoutMarginsForView(self);
|
||||
|
||||
self.alertLabel = [ORKHeadlineLabel new];
|
||||
_alertLabel.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
@@ -243,6 +234,12 @@ static const CGFloat kValueLineMargin = 1.5;
|
||||
[self applyKeyColor];
|
||||
}
|
||||
|
||||
- (void)setFailed:(BOOL)failed {
|
||||
_failed = failed;
|
||||
_alertLabel.text = failed ? ORKLocalizedString(@"AUDIO_GENERIC_ERROR_LABEL", nil) : ORKLocalizedString(@"AUDIO_TOO_LOUD_LABEL", nil);
|
||||
[self updateAlertLabelHidden];
|
||||
}
|
||||
|
||||
- (void)setFinished:(BOOL)finished {
|
||||
_finished = finished;
|
||||
[self updateAlertLabelHidden];
|
||||
@@ -265,7 +262,6 @@ static const CGFloat kValueLineMargin = 1.5;
|
||||
|
||||
- (void)setAlertColor:(UIColor *)alertColor {
|
||||
_alertColor = alertColor;
|
||||
|
||||
_alertLabel.textColor = alertColor;
|
||||
_graphView.alertColor = alertColor;
|
||||
}
|
||||
@@ -281,21 +277,34 @@ static const CGFloat kValueLineMargin = 1.5;
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(_timerLabel, _alertLabel, _graphView);
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_graphView]-[_alertLabel]|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil views:views]];
|
||||
options:0
|
||||
metrics:nil
|
||||
views:views]];
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_alertLabel
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
multiplier:1 constant:0]];
|
||||
multiplier:1
|
||||
constant:0]];
|
||||
|
||||
const CGFloat sideMargin = self.layoutMargins.left + (2 * ORKStandardLeftMarginForTableViewCell(self));
|
||||
const CGFloat innerMargin = 2;
|
||||
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_graphView]-2-[_timerLabel]-|"
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-sideMargin-[_graphView]-innerMargin-[_timerLabel]-sideMargin-|"
|
||||
options:NSLayoutFormatAlignAllCenterY
|
||||
metrics:nil views:views]];
|
||||
metrics:@{@"sideMargin": @(sideMargin), @"innerMargin": @(innerMargin)}
|
||||
views:views]];
|
||||
|
||||
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_graphView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:(GraphViewBlueZoneHeight+GraphViewRedZoneHeight*2)]];
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_graphView
|
||||
attribute:NSLayoutAttributeHeight
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1
|
||||
constant:(GraphViewBlueZoneHeight+GraphViewRedZoneHeight*2)]];
|
||||
|
||||
_constraints = constraints;
|
||||
[NSLayoutConstraint activateConstraints:constraints];
|
||||
@@ -317,17 +326,16 @@ static const CGFloat kValueLineMargin = 1.5;
|
||||
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;
|
||||
NSDateComponentsFormatter *formatter = [NSDateComponentsFormatter new];
|
||||
formatter.unitsStyle = NSDateComponentsFormatterUnitsStylePositional;
|
||||
formatter.zeroFormattingBehavior = NSDateComponentsFormatterZeroFormattingBehaviorPad;
|
||||
formatter.allowedUnits = NSCalendarUnitMinute | NSCalendarUnitSecond;
|
||||
_formatter = formatter;
|
||||
});
|
||||
|
||||
NSString *s = [_formatter stringFromTimeInterval:MAX(round(_timeLeft),0)];
|
||||
_timerLabel.text = s;
|
||||
_timerLabel.hidden = (s == nil);
|
||||
|
||||
NSString *string = [_formatter stringFromTimeInterval:MAX(round(_timeLeft),0)];
|
||||
_timerLabel.text = string;
|
||||
_timerLabel.hidden = (string == nil);
|
||||
}
|
||||
|
||||
- (void)updateGraphSamples {
|
||||
@@ -337,8 +345,8 @@ static const CGFloat kValueLineMargin = 1.5;
|
||||
|
||||
- (void)updateAlertLabelHidden {
|
||||
NSNumber *sample = [_samples lastObject];
|
||||
BOOL hide = _finished || !([sample doubleValue] > _alertThreshold);
|
||||
_alertLabel.hidden = hide;
|
||||
BOOL show = (! _finished && ([sample doubleValue] > _alertThreshold)) || _failed;
|
||||
_alertLabel.hidden = !show;
|
||||
}
|
||||
|
||||
- (void)setSamples:(NSArray *)samples {
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
Copyright (c) 2015, Shazino SAS. 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.
|
||||
|
||||
---
|
||||
|
||||
This software is based on the original source by Matt Gallagher.
|
||||
http://www.cocoawithlove.com/2010/10/ios-tone-generator-introduction-to.html
|
||||
|
||||
Copyright (c) 2009-2011 Matt Gallagher. All rights reserved.
|
||||
|
||||
This software is provided 'as-is', without any express or implied warranty. In
|
||||
no event will the authors be held liable for any damages arising from the use
|
||||
of this software. Permission is granted to anyone to use this software for any
|
||||
purpose, including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not claim
|
||||
that you wrote the original software. If you use this software in a product,
|
||||
an acknowledgment in the product documentation would be appreciated but is
|
||||
not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
#import <ResearchKit/ORKRecorder.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
The `ORKAudioGenerator` class represents an audio tone generator.
|
||||
*/
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKAudioGenerator : NSObject
|
||||
|
||||
/**
|
||||
Plays a tone at a specific frequency in stereo.
|
||||
|
||||
The sound is a "pure" sinusoid tone.
|
||||
|
||||
@param frequency The audio frequency in hertz.
|
||||
*/
|
||||
- (void)playSoundAtFrequency:(double)frequency;
|
||||
|
||||
/**
|
||||
Plays a tone at a specific frequency on a specific channel, with a fade-in effect.
|
||||
|
||||
The sound is a "pure" sinusoid tone.
|
||||
The fade-in effect is applied linearly for the peak amplitude, from a 0 to 1 factor.
|
||||
|
||||
@param frequency The audio frequency in hertz.
|
||||
@param channel The audio channel (left or right).
|
||||
@param duration The fade-in duration.
|
||||
*/
|
||||
- (void)playSoundAtFrequency:(double)frequency
|
||||
onChannel:(ORKAudioChannel)channel
|
||||
fadeInDuration:(NSTimeInterval)duration;
|
||||
|
||||
/**
|
||||
Stops the audio being played.
|
||||
*/
|
||||
- (void)stop;
|
||||
|
||||
/**
|
||||
Returns the peak audio volume being currently played, in decibels (dB).
|
||||
|
||||
@return The current audio volume in decibels.
|
||||
*/
|
||||
- (double)volumeInDecibels;
|
||||
|
||||
/**
|
||||
Returns the peak audio volume amplitude being currently played (from 0 to 1).
|
||||
|
||||
@return The current audio volume amplitude.
|
||||
*/
|
||||
- (double)volumeAmplitude;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,284 @@
|
||||
/*
|
||||
Copyright (c) 2015, Shazino SAS. 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.
|
||||
|
||||
---
|
||||
|
||||
This software is based on the original source by Matt Gallagher.
|
||||
http://www.cocoawithlove.com/2010/10/ios-tone-generator-introduction-to.html
|
||||
|
||||
Copyright (c) 2009-2011 Matt Gallagher. All rights reserved.
|
||||
|
||||
This software is provided 'as-is', without any express or implied warranty. In
|
||||
no event will the authors be held liable for any damages arising from the use
|
||||
of this software. Permission is granted to anyone to use this software for any
|
||||
purpose, including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not claim
|
||||
that you wrote the original software. If you use this software in a product,
|
||||
an acknowledgment in the product documentation would be appreciated but is
|
||||
not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
#import "ORKAudioGenerator.h"
|
||||
|
||||
@import AudioToolbox;
|
||||
|
||||
@interface ORKAudioGenerator () {
|
||||
@public
|
||||
AudioComponentInstance _toneUnit;
|
||||
|
||||
@public
|
||||
double _frequency;
|
||||
double _theta;
|
||||
ORKAudioChannel _activeChannel;
|
||||
BOOL _playsStereo;
|
||||
double _fadeInFactor;
|
||||
NSTimeInterval _fadeInDuration;
|
||||
}
|
||||
|
||||
- (void)setupAudioSession;
|
||||
- (void)createToneUnit;
|
||||
- (void)play;
|
||||
- (void)handleInterruption:(id)sender;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
const double ORKSineWaveToneGeneratorAmplitudeDefault = 0.03f;
|
||||
const double ORKSineWaveToneGeneratorSampleRateDefault = 44100.0f;
|
||||
|
||||
OSStatus ORKAudioGeneratorRenderTone(void *inRefCon,
|
||||
AudioUnitRenderActionFlags *ioActionFlags,
|
||||
const AudioTimeStamp *inTimeStamp,
|
||||
UInt32 inBusNumber,
|
||||
UInt32 inNumberFrames,
|
||||
AudioBufferList *ioData) {
|
||||
// Fixed amplitude is good enough for our purposes
|
||||
const double amplitude = ORKSineWaveToneGeneratorAmplitudeDefault;
|
||||
|
||||
// Get the tone parameters out of the view controller
|
||||
ORKAudioGenerator *audioGenerator = (__bridge ORKAudioGenerator *)inRefCon;
|
||||
double theta = audioGenerator->_theta;
|
||||
double theta_increment = 2.0 * M_PI * audioGenerator->_frequency / ORKSineWaveToneGeneratorSampleRateDefault;
|
||||
|
||||
double fadeInFactor = audioGenerator->_fadeInFactor;
|
||||
|
||||
// This is a mono tone generator so we only need the first buffer
|
||||
Float32 *bufferActive = (Float32 *)ioData->mBuffers[audioGenerator->_activeChannel].mData;
|
||||
Float32 *bufferNonActive = (Float32 *)ioData->mBuffers[1 - audioGenerator->_activeChannel].mData;
|
||||
|
||||
// Generate the samples
|
||||
for (UInt32 frame = 0; frame < inNumberFrames; frame++) {
|
||||
double bufferValue = sin(theta) * amplitude * pow(10, 2 * fadeInFactor - 2);
|
||||
bufferActive[frame] = bufferValue;
|
||||
if (audioGenerator->_playsStereo) {
|
||||
bufferNonActive[frame] = bufferValue;
|
||||
}
|
||||
else {
|
||||
bufferNonActive[frame] = 0;
|
||||
}
|
||||
|
||||
theta += theta_increment;
|
||||
if (theta > 2.0 * M_PI) {
|
||||
theta -= 2.0 * M_PI;
|
||||
}
|
||||
|
||||
fadeInFactor += 1/(ORKSineWaveToneGeneratorSampleRateDefault * audioGenerator->_fadeInDuration);
|
||||
if (fadeInFactor >= 1) {
|
||||
fadeInFactor = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Store the theta back in the view controller
|
||||
audioGenerator->_theta = theta;
|
||||
audioGenerator->_fadeInFactor = fadeInFactor;
|
||||
|
||||
return noErr;
|
||||
}
|
||||
|
||||
|
||||
@implementation ORKAudioGenerator
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
[self setupAudioSession];
|
||||
|
||||
// Automatically stop and then restart audio playback when the app resigns active.
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillResignActive:) name:UIApplicationWillResignActiveNotification object:nil];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[self stop];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
- (void)applicationDidBecomeActive:(NSNotification *)notification {
|
||||
if (_toneUnit) {
|
||||
__unused OSErr err = AudioOutputUnitStart(_toneUnit);
|
||||
NSAssert1(err == noErr, @"Error starting unit: %hd", err);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)applicationWillResignActive:(NSNotification *)notification {
|
||||
if (_toneUnit) {
|
||||
__unused OSErr err = AudioOutputUnitStop(_toneUnit);
|
||||
NSAssert1(err == noErr, @"Error stopping unit: %hd", err);
|
||||
}
|
||||
}
|
||||
|
||||
- (double)volumeInDecibels {
|
||||
return 20 * log(self.volumeAmplitude);
|
||||
}
|
||||
|
||||
- (double)volumeAmplitude {
|
||||
return ORKSineWaveToneGeneratorAmplitudeDefault * pow(10, 2 * _fadeInFactor - 2);
|
||||
}
|
||||
|
||||
- (void)playSoundAtFrequency:(double)playFrequency {
|
||||
_frequency = playFrequency;
|
||||
_fadeInFactor = 0;
|
||||
_fadeInDuration = 0.5;
|
||||
_playsStereo = YES;
|
||||
|
||||
[self play];
|
||||
}
|
||||
|
||||
- (void)playSoundAtFrequency:(double)playFrequency
|
||||
onChannel:(ORKAudioChannel)playChannel
|
||||
fadeInDuration:(NSTimeInterval)duration {
|
||||
_frequency = playFrequency;
|
||||
_activeChannel = playChannel;
|
||||
_fadeInFactor = 0;
|
||||
_fadeInDuration = duration;
|
||||
_playsStereo = NO;
|
||||
|
||||
[self play];
|
||||
}
|
||||
|
||||
- (void)play {
|
||||
if (!_toneUnit) {
|
||||
[self createToneUnit];
|
||||
|
||||
// Stop changing parameters on the unit
|
||||
OSErr err = AudioUnitInitialize(_toneUnit);
|
||||
NSAssert1(err == noErr, @"Error initializing unit: %hd", err);
|
||||
|
||||
// Start playback
|
||||
err = AudioOutputUnitStart(_toneUnit);
|
||||
NSAssert1(err == noErr, @"Error starting unit: %hd", err);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)stop {
|
||||
if (_toneUnit) {
|
||||
AudioOutputUnitStop(_toneUnit);
|
||||
AudioUnitUninitialize(_toneUnit);
|
||||
AudioComponentInstanceDispose(_toneUnit);
|
||||
_toneUnit = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setupAudioSession {
|
||||
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
|
||||
BOOL ok;
|
||||
NSError *setCategoryError = nil;
|
||||
ok = [audioSession setCategory:AVAudioSessionCategoryPlayback error:&setCategoryError];
|
||||
NSAssert1(ok, @"Audio error %@", setCategoryError);
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(handleInterruption:)
|
||||
name:AVAudioSessionInterruptionNotification
|
||||
object:audioSession];
|
||||
}
|
||||
|
||||
- (void)createToneUnit {
|
||||
// Configure the search parameters to find the default playback output unit
|
||||
// (called the kAudioUnitSubType_RemoteIO on iOS but
|
||||
// kAudioUnitSubType_DefaultOutput on Mac OS X)
|
||||
AudioComponentDescription defaultOutputDescription;
|
||||
defaultOutputDescription.componentType = kAudioUnitType_Output;
|
||||
defaultOutputDescription.componentSubType = kAudioUnitSubType_RemoteIO;
|
||||
defaultOutputDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
|
||||
defaultOutputDescription.componentFlags = 0;
|
||||
defaultOutputDescription.componentFlagsMask = 0;
|
||||
|
||||
// Get the default playback output unit
|
||||
AudioComponent defaultOutput = AudioComponentFindNext(NULL, &defaultOutputDescription);
|
||||
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);
|
||||
|
||||
// Set our tone rendering function on the unit
|
||||
AURenderCallbackStruct input;
|
||||
input.inputProc = ORKAudioGeneratorRenderTone;
|
||||
input.inputProcRefCon = (__bridge void *)(self);
|
||||
err = AudioUnitSetProperty(_toneUnit,
|
||||
kAudioUnitProperty_SetRenderCallback,
|
||||
kAudioUnitScope_Input,
|
||||
0,
|
||||
&input,
|
||||
sizeof(input));
|
||||
NSAssert1(err == noErr, @"Error setting callback: %hd", err);
|
||||
|
||||
// Set the format to 32 bit, single channel, floating point, linear PCM
|
||||
const int four_bytes_per_float = 4;
|
||||
const int eight_bits_per_byte = 8;
|
||||
AudioStreamBasicDescription streamFormat;
|
||||
streamFormat.mSampleRate = ORKSineWaveToneGeneratorSampleRateDefault;
|
||||
streamFormat.mFormatID = kAudioFormatLinearPCM;
|
||||
streamFormat.mFormatFlags = kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved;
|
||||
streamFormat.mBytesPerPacket = four_bytes_per_float;
|
||||
streamFormat.mFramesPerPacket = 1;
|
||||
streamFormat.mBytesPerFrame = four_bytes_per_float;
|
||||
streamFormat.mChannelsPerFrame = 2;
|
||||
streamFormat.mBitsPerChannel = four_bytes_per_float * eight_bits_per_byte;
|
||||
err = AudioUnitSetProperty (_toneUnit,
|
||||
kAudioUnitProperty_StreamFormat,
|
||||
kAudioUnitScope_Input,
|
||||
0,
|
||||
&streamFormat,
|
||||
sizeof(AudioStreamBasicDescription));
|
||||
NSAssert1(err == noErr, @"Error setting stream format: %hd", err);
|
||||
}
|
||||
|
||||
- (void)handleInterruption:(id)sender {
|
||||
[self stop];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -28,9 +28,11 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import <ResearchKit/ORKRecorder.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
@@ -58,20 +60,22 @@ ORK_CLASS_AVAILABLE
|
||||
Passed to AVAudioRecorder`'s `-initWithURL:settings:error:`
|
||||
For information on the settings available for an audio recorder, see "AV Foundation Audio Settings Constants".
|
||||
*/
|
||||
@property (nonatomic, copy, readonly, nullable) NSDictionary *recorderSettings;
|
||||
@property (nonatomic, copy, readonly) NSDictionary *recorderSettings;
|
||||
|
||||
/**
|
||||
Returns an initialized audio recorder using the specified settings, step, and output directory.
|
||||
|
||||
@param recorderSettings The settings for the recording session.
|
||||
@param step The step that requested this recording.
|
||||
@param outputDirectory The directory in which the audio output should be stored.
|
||||
@param identifier The unique identifier of the recorder (assigned by the recorder configuration).
|
||||
@param recorderSettings The settings for the recording session.
|
||||
@param step The step that requested this recorder.
|
||||
@param outputDirectory The directory in which the audio output should be stored.
|
||||
|
||||
@return An initialized audio recorder.
|
||||
*/
|
||||
- (instancetype)initWithRecorderSettings:(NSDictionary *)recorderSettings
|
||||
step:(ORKStep *)step
|
||||
outputDirectory:(NSURL *)outputDirectory NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier
|
||||
recorderSettings:(nullable NSDictionary *)recorderSettings
|
||||
step:(nullable ORKStep *)step
|
||||
outputDirectory:(nullable NSURL *)outputDirectory NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/**
|
||||
Reference to the audio recorder being used.
|
||||
|
||||
@@ -28,12 +28,14 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKAudioRecorder.h"
|
||||
#import "ORKHelpers.h"
|
||||
#import "ORKRecorder_Internal.h"
|
||||
#import "ORKRecorder_Private.h"
|
||||
#import "ORKDefines_Private.h"
|
||||
|
||||
|
||||
@interface ORKAudioRecorder ()
|
||||
|
||||
@property (nonatomic, strong) AVAudioRecorder *audioRecorder;
|
||||
@@ -42,41 +44,34 @@
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKAudioRecorder
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
- (void)dealloc {
|
||||
ORK_Log_Debug(@"Remove audiorecorder %p", self);
|
||||
[_audioRecorder stop];
|
||||
_audioRecorder = nil;
|
||||
}
|
||||
|
||||
|
||||
+ (NSDictionary *)defaultRecorderSettings
|
||||
{
|
||||
return [NSDictionary
|
||||
dictionaryWithObjectsAndKeys:
|
||||
@(kAudioFormatMPEG4AAC), AVFormatIDKey,
|
||||
@(AVAudioQualityMin), AVEncoderAudioQualityKey,
|
||||
@(2), AVNumberOfChannelsKey,
|
||||
@(44100.0), AVSampleRateKey,
|
||||
nil];
|
||||
+ (NSDictionary *)defaultRecorderSettings {
|
||||
return @{AVFormatIDKey : @(kAudioFormatMPEG4AAC),
|
||||
AVEncoderAudioQualityKey : @(AVAudioQualityMin),
|
||||
AVNumberOfChannelsKey : @(2),
|
||||
AVSampleRateKey : @(44100.0)};
|
||||
}
|
||||
|
||||
- (instancetype)initWithRecorderSettings:(NSDictionary *)recorderSettings
|
||||
step:(ORKStep *)step
|
||||
outputDirectory:(NSURL *)outputDirectory
|
||||
{
|
||||
self = [super initWithStep:step outputDirectory:outputDirectory];
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier
|
||||
recorderSettings:(NSDictionary *)recorderSettings
|
||||
step:(ORKStep *)step
|
||||
outputDirectory:(NSURL *)outputDirectory {
|
||||
self = [super initWithIdentifier:identifier step:step outputDirectory:outputDirectory];
|
||||
if (self) {
|
||||
|
||||
self.continuesInBackground = YES;
|
||||
if (! recorderSettings)
|
||||
{
|
||||
if (! recorderSettings) {
|
||||
recorderSettings = [[self class] defaultRecorderSettings];
|
||||
}
|
||||
if (! [recorderSettings isKindOfClass:[NSDictionary class]])
|
||||
{
|
||||
if (! [recorderSettings isKindOfClass:[NSDictionary class]]) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"recorderSettings should be a dictionary" userInfo:recorderSettings];
|
||||
}
|
||||
self.recorderSettings = recorderSettings;
|
||||
@@ -85,7 +80,9 @@
|
||||
}
|
||||
|
||||
- (void)start {
|
||||
|
||||
if (self.outputDirectory == nil) {
|
||||
@throw [NSException exceptionWithName:NSDestinationInvalidException reason:@"audioRecorder requires an output directory" userInfo:nil];
|
||||
}
|
||||
// Only create the file when we should actually start recording.
|
||||
if (! _audioRecorder) {
|
||||
|
||||
@@ -103,7 +100,6 @@
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
ORK_Log_Debug(@"Create audioRecorder %p", self);
|
||||
_audioRecorder = [[AVAudioRecorder alloc]
|
||||
initWithURL:soundFileURL
|
||||
@@ -115,16 +111,14 @@
|
||||
}
|
||||
|
||||
#if ! TARGET_IPHONE_SIMULATOR
|
||||
if (!_audioRecorder.recording)
|
||||
{
|
||||
if (!_audioRecorder.recording) {
|
||||
[_audioRecorder prepareToRecord];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if ! TARGET_IPHONE_SIMULATOR
|
||||
if (!_audioRecorder.recording)
|
||||
{
|
||||
if (!_audioRecorder.recording) {
|
||||
[_audioRecorder prepareToRecord];
|
||||
[_audioRecorder record];
|
||||
}
|
||||
@@ -136,7 +130,6 @@
|
||||
- (void)stop {
|
||||
if (! _audioRecorder) {
|
||||
// Error has already been returned.
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -161,43 +154,39 @@
|
||||
unsigned int recorderFormat = [recorderSettings[AVFormatIDKey] unsignedIntValue];
|
||||
|
||||
NSString *contentType = @"audio";
|
||||
switch (recorderFormat)
|
||||
{
|
||||
case kAudioFormatLinearPCM:
|
||||
{
|
||||
switch (recorderFormat) {
|
||||
case kAudioFormatLinearPCM: {
|
||||
int numBits = [recorderSettings[AVLinearPCMBitDepthKey] intValue] ? : 16;
|
||||
contentType = [NSString stringWithFormat:@"audio/L%d", numBits];
|
||||
break;
|
||||
}
|
||||
case kAudioFormatAC3:
|
||||
case kAudioFormatAC3: {
|
||||
contentType = @"audio/ac3";
|
||||
break;
|
||||
}
|
||||
case kAudioFormatMPEG4AAC:
|
||||
case kAudioFormatMPEG4CELP:
|
||||
case kAudioFormatMPEG4HVXC:
|
||||
case kAudioFormatMPEG4TwinVQ:
|
||||
case kAudioFormatAppleLossless:
|
||||
case kAudioFormatAppleLossless: {
|
||||
contentType = @"audio/m4a";
|
||||
break;
|
||||
case kAudioFormatULaw:
|
||||
}
|
||||
case kAudioFormatULaw: {
|
||||
contentType = @"audio/basic";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return contentType;
|
||||
|
||||
}
|
||||
|
||||
|
||||
- (NSString *)recorderType
|
||||
{
|
||||
- (NSString *)recorderType {
|
||||
return @"audio";
|
||||
}
|
||||
|
||||
- (void)doStopRecording
|
||||
{
|
||||
- (void)doStopRecording {
|
||||
if (self.isRecording) {
|
||||
#if ! TARGET_IPHONE_SIMULATOR
|
||||
#if !TARGET_IPHONE_SIMULATOR
|
||||
[_audioRecorder stop];
|
||||
|
||||
[self applyFileProtection:ORKFileProtectionComplete toFileAtURL:[self recordingFileURL]];
|
||||
@@ -205,48 +194,44 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)finishRecordingWithError:(NSError *)error
|
||||
{
|
||||
- (void)finishRecordingWithError:(NSError *)error {
|
||||
[self doStopRecording];
|
||||
|
||||
[super finishRecordingWithError:error];
|
||||
}
|
||||
|
||||
- (NSString *)extension
|
||||
{
|
||||
- (NSString *)extension {
|
||||
NSDictionary *recorderSettings = [self recorderSettings];
|
||||
unsigned int recorderFormat = [recorderSettings[AVFormatIDKey] unsignedIntValue];
|
||||
|
||||
NSString *extension = @"au";
|
||||
switch (recorderFormat)
|
||||
{
|
||||
switch (recorderFormat) {
|
||||
case kAudioFormatLinearPCM:
|
||||
{
|
||||
extension = @"pcm";
|
||||
break;
|
||||
}
|
||||
case kAudioFormatAC3:
|
||||
case kAudioFormatAC3: {
|
||||
extension = @"ac3";
|
||||
break;
|
||||
}
|
||||
case kAudioFormatMPEG4AAC:
|
||||
case kAudioFormatMPEG4CELP:
|
||||
case kAudioFormatMPEG4HVXC:
|
||||
case kAudioFormatMPEG4TwinVQ:
|
||||
case kAudioFormatAppleLossless:
|
||||
case kAudioFormatAppleLossless: {
|
||||
extension = @"m4a";
|
||||
break;
|
||||
}
|
||||
}
|
||||
return extension;
|
||||
}
|
||||
|
||||
- (NSURL *)recordingFileURL
|
||||
{
|
||||
- (NSURL *)recordingFileURL {
|
||||
return [[self recordingDirectoryURL] URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", [self logName], [self extension]]];
|
||||
}
|
||||
|
||||
|
||||
- (BOOL)recreateFileWithError:(NSError * __autoreleasing *)error
|
||||
{
|
||||
- (BOOL)recreateFileWithError:(NSError * __autoreleasing *)error {
|
||||
NSURL *url = [self recordingFileURL];
|
||||
if (! url) {
|
||||
if (error) {
|
||||
@@ -255,34 +240,34 @@
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSFileManager *fm = [NSFileManager defaultManager];
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
|
||||
if (! [fm createDirectoryAtURL:url withIntermediateDirectories:YES attributes:nil error:error]) {
|
||||
if (! [fileManager createDirectoryAtURL:url withIntermediateDirectories:YES attributes:nil error:error]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
if ([fm fileExistsAtPath:[url path]]) {
|
||||
if (! [fm removeItemAtPath:[url path] error:error]) {
|
||||
if ([fileManager fileExistsAtPath:[url path]]) {
|
||||
if (! [fileManager removeItemAtPath:[url path] error:error]) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
[fm createFileAtPath:[url path] contents:nil attributes:nil];
|
||||
[fm setAttributes:@{NSFileProtectionKey : ORKFileProtectionFromMode(ORKFileProtectionCompleteUnlessOpen)} ofItemAtPath:[url path] error:error];
|
||||
[fileManager createFileAtPath:[url path] contents:nil attributes:nil];
|
||||
[fileManager setAttributes:@{NSFileProtectionKey : ORKFileProtectionFromMode(ORKFileProtectionCompleteUnlessOpen)} ofItemAtPath:[url path] error:error];
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
- (void)reset {
|
||||
[_audioRecorder stop];
|
||||
_audioRecorder = nil;
|
||||
[super reset];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface ORKAudioRecorderConfiguration ()
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -290,13 +275,15 @@
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wobjc-designated-initializers"
|
||||
- (instancetype)initWithRecorderSettings:(NSDictionary *)recorderSettings
|
||||
{
|
||||
self = [self ork_init];
|
||||
if (self)
|
||||
{
|
||||
if (recorderSettings && ! [recorderSettings isKindOfClass:[NSDictionary class]])
|
||||
{
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
||||
@throw [NSException exceptionWithName:NSGenericException reason:@"Use subclass designated initializer" userInfo:nil];
|
||||
}
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier
|
||||
recorderSettings:(NSDictionary *)recorderSettings {
|
||||
self = [super initWithIdentifier:identifier];
|
||||
if (self) {
|
||||
if (recorderSettings && ! [recorderSettings isKindOfClass:[NSDictionary class]]) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"recorderSettings should be a dictionary" userInfo:recorderSettings];
|
||||
}
|
||||
_recorderSettings = recorderSettings;
|
||||
@@ -306,31 +293,27 @@
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
- (ORKRecorder *)recorderForStep:(ORKStep *)step
|
||||
outputDirectory:(NSURL *)outputDirectory
|
||||
{
|
||||
return [[ORKAudioRecorder alloc] initWithRecorderSettings:(NSDictionary *__nonnull)self.recorderSettings
|
||||
step:step
|
||||
outputDirectory:outputDirectory];
|
||||
outputDirectory:(NSURL *)outputDirectory {
|
||||
return [[ORKAudioRecorder alloc] initWithIdentifier:self.identifier
|
||||
recorderSettings:self.recorderSettings
|
||||
step:step
|
||||
outputDirectory:outputDirectory];
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder
|
||||
{
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [super initWithCoder:aDecoder];
|
||||
if (self)
|
||||
{
|
||||
if (self) {
|
||||
ORK_DECODE_OBJ_CLASS(aDecoder, recorderSettings, NSDictionary);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder
|
||||
{
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
[super encodeWithCoder:aCoder];
|
||||
ORK_ENCODE_OBJ(aCoder, recorderSettings);
|
||||
}
|
||||
|
||||
+ (BOOL)supportsSecureCoding
|
||||
{
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
@@ -339,12 +322,11 @@
|
||||
|
||||
__typeof(self) castObject = object;
|
||||
return (isParentSame &&
|
||||
ORKEqualObjects(self.recorderSettings, castObject.recorderSettings)) ;
|
||||
ORKEqualObjects(self.recorderSettings, castObject.recorderSettings));
|
||||
}
|
||||
|
||||
|
||||
- (ORKPermissionMask)requestedPermissionMask {
|
||||
return ORKPermissionAudioRecording;
|
||||
}
|
||||
|
||||
@end
|
||||
@end
|
||||
|
||||
@@ -28,15 +28,15 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import <ResearchKit/ORKActiveStep.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKAudioStep : ORKActiveStep
|
||||
|
||||
@property (nonatomic, assign) NSTimeInterval duration;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -28,69 +28,39 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKAudioStep.h"
|
||||
#import "ORKAudioStepViewController.h"
|
||||
#import "ORKHelpers.h"
|
||||
#import "ORKStep_Private.h"
|
||||
|
||||
|
||||
@implementation ORKAudioStep
|
||||
|
||||
+ (Class)stepViewControllerClass {
|
||||
return [ORKAudioStepViewController class];
|
||||
}
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
||||
self = [super initWithIdentifier:identifier];
|
||||
if (self) {
|
||||
self.shouldShowDefaultTimer = NO;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)validateParameters {
|
||||
|
||||
[super validateParameters];
|
||||
|
||||
NSTimeInterval const ORKAudioTaskMinimumDuration = 5.0;
|
||||
|
||||
if ( self.duration < ORKAudioTaskMinimumDuration)
|
||||
{
|
||||
if ( self.stepDuration < ORKAudioTaskMinimumDuration) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"duration can not be shorter than %@ seconds.", @(ORKAudioTaskMinimumDuration)] userInfo:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (instancetype)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
ORKAudioStep *step = [super copyWithZone:zone];
|
||||
step.duration = self.duration;
|
||||
return step;
|
||||
}
|
||||
|
||||
- (BOOL)startsFinished {
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder
|
||||
{
|
||||
self = [super initWithCoder:aDecoder];
|
||||
if (self)
|
||||
{
|
||||
ORK_DECODE_DOUBLE(aDecoder, duration);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder
|
||||
{
|
||||
[super encodeWithCoder:aCoder];
|
||||
ORK_ENCODE_DOUBLE(aCoder, duration);
|
||||
}
|
||||
|
||||
+ (BOOL)supportsSecureCoding
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
- (BOOL)isEqual:(id)object {
|
||||
BOOL isParentSame = [super isEqual:object];
|
||||
|
||||
__typeof(self) castObject = object;
|
||||
return (isParentSame &&
|
||||
(self.duration == castObject.duration)) ;
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@@ -28,8 +28,10 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import <ResearchKit/ResearchKit_Private.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
ORK_CLASS_AVAILABLE
|
||||
@@ -37,4 +39,4 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKAudioStepViewController.h"
|
||||
#import "ORKAudioContentView.h"
|
||||
#import "ORKActiveStepViewController_Internal.h"
|
||||
@@ -41,22 +42,22 @@
|
||||
#import "ORKActiveStepView.h"
|
||||
#import "ORKCustomStepView_Internal.h"
|
||||
|
||||
|
||||
@interface ORKAudioStepViewController ()
|
||||
|
||||
@property (nonatomic, strong) AVAudioRecorder *avAudioRecorder;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ORKAudioStepViewController
|
||||
{
|
||||
|
||||
@implementation ORKAudioStepViewController {
|
||||
ORKAudioContentView *_audioContentView;
|
||||
ORKAudioRecorder *_audioRecorder;
|
||||
|
||||
ORKActiveStepTimer *_timer;
|
||||
NSError *_audioRecorderError;
|
||||
}
|
||||
|
||||
- (instancetype)initWithStep:(ORKStep *)step {
|
||||
|
||||
self = [super initWithStep:step];
|
||||
if (self) {
|
||||
// Continue audio recording in the background
|
||||
@@ -68,15 +69,13 @@
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
// Do any additional setup after loading the view.
|
||||
|
||||
_audioContentView = [ORKAudioContentView new];
|
||||
_audioContentView.timeLeft = self.audioStep.duration;
|
||||
_audioContentView.timeLeft = self.audioStep.stepDuration;
|
||||
self.activeStepView.activeCustomView = _audioContentView;
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
[super viewDidAppear:animated];
|
||||
|
||||
[self start];
|
||||
}
|
||||
|
||||
@@ -93,7 +92,6 @@
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_audioRecorder = audioRecorder;
|
||||
[self audioRecorderDidChange];
|
||||
}
|
||||
@@ -103,26 +101,26 @@
|
||||
}
|
||||
|
||||
- (void)doSample {
|
||||
if (_audioRecorderError) {
|
||||
return;
|
||||
}
|
||||
[_avAudioRecorder updateMeters];
|
||||
float value = [_avAudioRecorder averagePowerForChannel:0];
|
||||
|
||||
// Assume value is in range roughly -60dB to 0dB
|
||||
float clampedValue = MAX(value/60.0, -1) + 1;
|
||||
|
||||
[_audioContentView addSample:@(clampedValue)];
|
||||
_audioContentView.timeLeft = [_timer duration] - [_timer runtime];
|
||||
}
|
||||
|
||||
- (void)startNewTimerIfNeeded {
|
||||
if (! _timer) {
|
||||
|
||||
NSTimeInterval duration = self.audioStep.duration;
|
||||
NSTimeInterval duration = self.audioStep.stepDuration;
|
||||
__weak typeof(self) weakSelf = self;
|
||||
_timer = [[ORKActiveStepTimer alloc] initWithDuration:duration interval:duration/100 runtime:0 handler:^(ORKActiveStepTimer *timer, BOOL finished) {
|
||||
typeof(self) strongSelf = weakSelf;
|
||||
[strongSelf doSample];
|
||||
if (finished) {
|
||||
[self finish];
|
||||
[strongSelf finish];
|
||||
}
|
||||
}];
|
||||
[_timer resume];
|
||||
@@ -130,16 +128,15 @@
|
||||
_audioContentView.finished = NO;
|
||||
}
|
||||
|
||||
|
||||
- (void)start {
|
||||
[super start];
|
||||
[self audioRecorderDidChange];
|
||||
|
||||
[_timer reset];
|
||||
_timer = nil;
|
||||
[self startNewTimerIfNeeded];
|
||||
|
||||
}
|
||||
|
||||
- (void)suspend {
|
||||
[super suspend];
|
||||
[_timer pause];
|
||||
@@ -147,13 +144,18 @@
|
||||
[_audioContentView addSample:@(0)];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)resume {
|
||||
[super resume];
|
||||
[self audioRecorderDidChange];
|
||||
[self startNewTimerIfNeeded];
|
||||
[_timer resume];
|
||||
}
|
||||
|
||||
- (void)finish {
|
||||
if (_audioRecorderError) {
|
||||
return;
|
||||
}
|
||||
[super finish];
|
||||
[_timer reset];
|
||||
_timer = nil;
|
||||
@@ -166,7 +168,12 @@
|
||||
- (void)setAvAudioRecorder:(AVAudioRecorder *)recorder {
|
||||
_avAudioRecorder = nil;
|
||||
_avAudioRecorder = recorder;
|
||||
|
||||
}
|
||||
|
||||
- (void) recorder:(ORKRecorder *)recorder didFailWithError:(NSError *)error {
|
||||
[super recorder:recorder didFailWithError:error];
|
||||
_audioRecorderError = error;
|
||||
_audioContentView.failed = YES;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -28,9 +28,11 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import <ResearchKit/ResearchKit.h>
|
||||
#import <ResearchKit/ORKActiveStep.h>
|
||||
|
||||
|
||||
/**
|
||||
The `ORKCountdownStep` class represents a step that displays a label and a
|
||||
countdown for a time equal to its duration.
|
||||
|
||||
@@ -28,18 +28,18 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKCountdownStep.h"
|
||||
#import "ORKCountdownStepViewController.h"
|
||||
|
||||
|
||||
@implementation ORKCountdownStep
|
||||
|
||||
+ (Class)stepViewControllerClass
|
||||
{
|
||||
+ (Class)stepViewControllerClass {
|
||||
return [ORKCountdownStepViewController class];
|
||||
}
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier
|
||||
{
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
||||
self = [super initWithIdentifier:identifier];
|
||||
if (self) {
|
||||
self.shouldStartTimerAutomatically = YES;
|
||||
@@ -56,8 +56,7 @@
|
||||
|
||||
NSTimeInterval const ORKCountdownStepMinimumDuration = 3.0;
|
||||
|
||||
if ( self.stepDuration < ORKCountdownStepMinimumDuration)
|
||||
{
|
||||
if ( self.stepDuration < ORKCountdownStepMinimumDuration) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"duration can not be shorter than %@ seconds.", @(ORKCountdownStepMinimumDuration)] userInfo:nil];
|
||||
}
|
||||
|
||||
|
||||
@@ -28,8 +28,10 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import <ResearchKit/ResearchKit_Private.h>
|
||||
|
||||
|
||||
/**
|
||||
The `ORKCountdownStepViewController` class represents the step view controller that corresponds to an `ORKCountdownStep`.
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKCountdownStepViewController.h"
|
||||
#import "ORKCustomStepView_Internal.h"
|
||||
#import "ORKActiveStepViewController_internal.h"
|
||||
@@ -41,6 +42,7 @@
|
||||
#import "ORKAccessibility.h"
|
||||
#import "ORKActiveStepView.h"
|
||||
|
||||
|
||||
@interface ORKCountDownViewLabel : ORKLabel
|
||||
|
||||
@end
|
||||
@@ -51,6 +53,7 @@
|
||||
}
|
||||
@end
|
||||
|
||||
|
||||
@interface ORKCountdownView : ORKActiveStepCustomView
|
||||
|
||||
@property (nonatomic, strong) ORKSubheadlineLabel *textLabel;
|
||||
@@ -61,15 +64,14 @@
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKCountdownView {
|
||||
CAShapeLayer *_circleLayer;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
|
||||
_textLabel = [ORKSubheadlineLabel new];
|
||||
_textLabel.textAlignment = NSTextAlignmentCenter;
|
||||
_textLabel.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
@@ -93,9 +95,18 @@
|
||||
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(_textLabel, _timeLabel, _progressView);
|
||||
|
||||
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_textLabel]-(>=0)-[_progressView(==d)]|" options:NSLayoutFormatDirectionLeadingToTrailing|NSLayoutFormatAlignAllCenterX metrics:metrics views:views]];
|
||||
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(>=0)-[_textLabel]-(>=0)-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:metrics views:views]];
|
||||
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(>=0)-[_progressView(==d)]-(>=0)-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:metrics views:views]];
|
||||
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_textLabel]-(>=0)-[_progressView(==d)]|"
|
||||
options:NSLayoutFormatDirectionLeadingToTrailing|NSLayoutFormatAlignAllCenterX
|
||||
metrics:metrics
|
||||
views:views]];
|
||||
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(>=0)-[_textLabel]-(>=0)-|"
|
||||
options:NSLayoutFormatDirectionLeadingToTrailing
|
||||
metrics:metrics
|
||||
views:views]];
|
||||
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(>=0)-[_progressView(==d)]-(>=0)-|"
|
||||
options:NSLayoutFormatDirectionLeadingToTrailing
|
||||
metrics:metrics
|
||||
views:views]];
|
||||
[self addConstraint:[NSLayoutConstraint constraintWithItem:_progressView
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
@@ -134,13 +145,11 @@
|
||||
_circleLayer.strokeColor = self.tintColor.CGColor;
|
||||
_circleLayer.lineWidth = 1;
|
||||
|
||||
|
||||
[_progressView.layer.sublayers makeObjectsPerformSelector:@selector(removeFromSuperlayer)];
|
||||
[_progressView.layer addSublayer:_circleLayer];
|
||||
|
||||
_textLabel.isAccessibilityElement = NO;
|
||||
_timeLabel.isAccessibilityElement = NO;
|
||||
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -150,7 +159,6 @@
|
||||
}
|
||||
|
||||
- (void)startAnimateWithDuration:(NSTimeInterval)duration {
|
||||
|
||||
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"strokeEnd"];
|
||||
animation.duration = duration*2;
|
||||
animation.removedOnCompletion = YES;
|
||||
@@ -158,7 +166,6 @@
|
||||
animation.keyTimes = @[@(0.0), @(0.5), @(1.0)];
|
||||
animation.timingFunction=[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
|
||||
[_circleLayer addAnimation:animation forKey:@"drawCircleAnimation"];
|
||||
|
||||
}
|
||||
|
||||
#pragma mark Accessibility
|
||||
@@ -184,12 +191,12 @@
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKCountdownStepViewController {
|
||||
NSInteger _countDown;
|
||||
}
|
||||
|
||||
- (instancetype)initWithStep:(ORKStep *)step {
|
||||
|
||||
self = [super initWithStep:step];
|
||||
if (self) {
|
||||
self.suspendIfInactive = NO;
|
||||
@@ -222,11 +229,10 @@
|
||||
}
|
||||
|
||||
- (void)updateCountdownLabel {
|
||||
_countdownView.timeLabel.text = [NSString stringWithFormat:@"%lu", (unsigned long)_countDown];
|
||||
_countdownView.timeLabel.text = ORKLocalizedStringFromNumber(@(_countDown));
|
||||
}
|
||||
|
||||
- (void)countDownTimerFired:(ORKActiveStepTimer *)timer finished:(BOOL)finished {
|
||||
|
||||
_countDown = MAX((_countDown-1), 0);
|
||||
[self updateCountdownLabel];
|
||||
|
||||
@@ -240,17 +246,13 @@
|
||||
[super countDownTimerFired:timer finished:finished];
|
||||
}];
|
||||
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, ORKLocalizedString(@"AX_ANNOUNCE_BEGIN_TASK", nil));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, [@(_countDown) stringValue]);
|
||||
[super countDownTimerFired:timer finished:finished];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
[super countDownTimerFired:timer finished:finished];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <ResearchKit/ORKDefines.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class ORKDataLogger;
|
||||
@@ -94,13 +95,13 @@ ORK_CLASS_AVAILABLE
|
||||
/**
|
||||
Returns a data logger with an `ORKJSONLogFormatter`.
|
||||
|
||||
@param url The URL of the directory in which to place log files.
|
||||
@param logName The prefix on the log file name in an ASCII string. Note that
|
||||
the string must not contain the hyphen character ("-"), because a hyphen is used as a separator in the log naming scheme.
|
||||
@param delegate The initial delegate. May be `nil`.
|
||||
@param url The URL of the directory in which to place log files.
|
||||
@param logName The prefix on the log file name in an ASCII string. Note that the string must not contain the hyphen character ("-"), because a hyphen is used as a separator in the log naming scheme.
|
||||
@param delegate The initial delegate. May be `nil`.
|
||||
*/
|
||||
+ (ORKDataLogger *)JSONDataLoggerWithDirectory:(NSURL *)url logName:(NSString *)logName delegate:(nullable id<ORKDataLoggerDelegate>)delegate;
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
/**
|
||||
Returns an initialized data logger using the specified URL, log name, formatter, and delegate.
|
||||
@@ -110,6 +111,7 @@ ORK_CLASS_AVAILABLE
|
||||
the string must not contain the hyphen character ("-"), because a hyphen is used as a separator in the log naming scheme.
|
||||
@param formatter The type of formatter to use for the log, such as `ORKJSONLogFormatter`.
|
||||
@param delegate The initial delegate. May be `nil`.
|
||||
|
||||
@return An initialized data logger.
|
||||
*/
|
||||
- (instancetype)initWithDirectory:(NSURL *)url logName:(NSString *)logName formatter:(ORKLogFormatter *)formatter delegate:(nullable id<ORKDataLoggerDelegate>)delegate NS_DESIGNATED_INITIALIZER;
|
||||
@@ -160,7 +162,8 @@ ORK_CLASS_AVAILABLE
|
||||
than through this object.
|
||||
|
||||
@param block The block to call during enumeration.
|
||||
@param error Any error detected during the enumeration
|
||||
@param error Any error detected during the enumeration.
|
||||
|
||||
@return `YES` if the enumeration was successful; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)enumerateLogs:(void (^)(NSURL *logFileUrl, BOOL *stop))block error:(NSError * __autoreleasing *)error;
|
||||
@@ -174,7 +177,8 @@ ORK_CLASS_AVAILABLE
|
||||
than through this object.
|
||||
|
||||
@param block The block to call during enumeration.
|
||||
@param error Any error detected during the enumeration
|
||||
@param error Any error detected during the enumeration.
|
||||
|
||||
@return `YES` if the enumeration was successful; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)enumerateLogsNeedingUpload:(void (^)(NSURL *logFileUrl, BOOL *stop))block error:(NSError * __autoreleasing *)error;
|
||||
@@ -188,7 +192,8 @@ ORK_CLASS_AVAILABLE
|
||||
than through this object.
|
||||
|
||||
@param block The block to call during enumeration.
|
||||
@param error Any error detected during the enumeration
|
||||
@param error Any error detected during the enumeration.
|
||||
|
||||
@return `YES` if the enumeration was successful; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)enumerateLogsAlreadyUploaded:(void (^)(NSURL *logFileUrl, BOOL *stop))block error:(NSError * __autoreleasing *)error;
|
||||
@@ -204,6 +209,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@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;
|
||||
@@ -216,6 +222,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@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;
|
||||
@@ -224,6 +231,7 @@ ORK_CLASS_AVAILABLE
|
||||
Checks whether a file has been marked as uploaded.
|
||||
|
||||
@param url The URL to check.
|
||||
|
||||
@return `YES` if the uploaded attribute has been set on the file and the file exists; otherwise,
|
||||
`NO`.
|
||||
*/
|
||||
@@ -240,6 +248,7 @@ ORK_CLASS_AVAILABLE
|
||||
@param uploaded A Boolean value that indicates whether to mark the file uploaded or not uploaded.
|
||||
@param url The URL to mark.
|
||||
@param error The error that occurred, if the operation fails.
|
||||
|
||||
@return `YES` if adding or removing the attribute succeeded; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)markFileUploaded:(BOOL)uploaded atURL:(NSURL *)url error:(NSError * __nullable __autoreleasing *)error;
|
||||
@@ -253,21 +262,23 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@param fileURLs The array of files that should be removed.
|
||||
@param error The error that occurred, if the operation fails.
|
||||
|
||||
@return `YES` if removing the files succeeded; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)removeUploadedFiles:(NSArray *)fileURLs withError:(NSError * __nullable __autoreleasing *)error;
|
||||
- (BOOL)removeUploadedFiles:(NSArray<NSURL *> *)fileURLs withError:(NSError * __nullable __autoreleasing *)error;
|
||||
|
||||
/**
|
||||
Removes all files managed by this logger (files that have the `logName` prefix).
|
||||
|
||||
@param error The error that occurred, if operation fails.
|
||||
|
||||
@return `YES` if removing the files succeeded.; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)removeAllFilesWithError:(NSError *__nullable __autoreleasing *)error;
|
||||
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
The `ORKLogFormatter` class represents the base (default) log formatter, which appends data
|
||||
blindly to a log file.
|
||||
@@ -285,6 +296,7 @@ ORK_CLASS_AVAILABLE
|
||||
Returns a Boolean value that indicates whether the log formatter can serialize the specified type of object.
|
||||
|
||||
@param c The class of object to serialize.
|
||||
|
||||
@return `YES` if the log formatter can serialize this object class; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)canAcceptLogObjectOfClass:(Class)c;
|
||||
@@ -293,6 +305,7 @@ ORK_CLASS_AVAILABLE
|
||||
Returns a Boolean value that indicates whether the log formatter can serialize the specified type of object.
|
||||
|
||||
@param object The object to serialize.
|
||||
|
||||
@return `YES` if the log formatter can serialize `object`; otherwise, `NO`
|
||||
*/
|
||||
- (BOOL)canAcceptLogObject:(id)object;
|
||||
@@ -304,6 +317,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@param fileHandle The file handle to which to write.
|
||||
@param error The error output, on failure.
|
||||
|
||||
@return `YES` if the write succeeds; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)beginLogWithFileHandle:(NSFileHandle *)fileHandle error:(NSError * __nullable __autoreleasing *)error;
|
||||
@@ -314,6 +328,7 @@ ORK_CLASS_AVAILABLE
|
||||
@param object The object to write.
|
||||
@param fileHandle The file handle to which to write.
|
||||
@param error The error output, on failure.
|
||||
|
||||
@return `YES` if the write succeeds; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)appendObject:(id)object fileHandle:(NSFileHandle *)fileHandle error:(NSError * __nullable __autoreleasing *)error;
|
||||
@@ -324,12 +339,14 @@ ORK_CLASS_AVAILABLE
|
||||
@param objects The objects to write.
|
||||
@param fileHandle The file handle to which to write.
|
||||
@param error The error output, on failure.
|
||||
|
||||
@return `YES` if the write succeeds; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)appendObjects:(NSArray *)objects fileHandle:(NSFileHandle *)fileHandle error:(NSError * __nullable __autoreleasing *)error;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
The `ORKJSONLogFormatter` class represents a log formatter for producing JSON output.
|
||||
|
||||
@@ -352,7 +369,6 @@ ORK_CLASS_AVAILABLE
|
||||
The `ORKDataLoggerManagerDelegate` protocol defines methods a delegate can implement to receive notifications
|
||||
when the data loggers managed by a `ORKDataLoggerManager` reach a certain file size threshold.
|
||||
*/
|
||||
|
||||
ORK_CLASS_AVAILABLE
|
||||
@protocol ORKDataLoggerManagerDelegate <NSObject>
|
||||
|
||||
@@ -377,6 +393,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
The `ORKDataLoggerManager` class represents a manager for multiple `ORKDataLogger` instances,
|
||||
which tracks the total size of log files produced and can notify its delegate
|
||||
@@ -408,6 +425,8 @@ ORK_CLASS_AVAILABLE
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKDataLoggerManager : NSObject <ORKDataLoggerDelegate>
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
/**
|
||||
Returns an initialized data logger manager using the specified directory and delegate.
|
||||
|
||||
@@ -415,6 +434,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@param directory The file URL of the directory where the data loggers should coexist.
|
||||
@param delegate The delegate to receive notifications.
|
||||
|
||||
@return An initialized data logger manager.
|
||||
*/
|
||||
- (instancetype)initWithDirectory:(NSURL *)directory delegate:(nullable id<ORKDataLoggerManagerDelegate>)delegate NS_DESIGNATED_INITIALIZER;
|
||||
@@ -440,6 +460,7 @@ ORK_CLASS_AVAILABLE
|
||||
This method throws an exception if a logger already exists with the specified log name.
|
||||
|
||||
@param logName The log name prefix for the data logger.
|
||||
|
||||
@return The `ORKDataLogger` object that was added.
|
||||
*/
|
||||
- (ORKDataLogger *)addJSONDataLoggerForLogName:(NSString *)logName;
|
||||
@@ -449,6 +470,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@param logName The log name prefix for the data logger.
|
||||
@param formatter The log formatter instance to use for this logger.
|
||||
|
||||
@return The `ORKDataLogger` object that was added, or the existing one if one already existed for
|
||||
that log name.
|
||||
*/
|
||||
@@ -458,6 +480,7 @@ ORK_CLASS_AVAILABLE
|
||||
Retrieves the already existing data logger for a log name.
|
||||
|
||||
@param logName The log name prefix for the data logger.
|
||||
|
||||
@return The `ORKDataLogger` object that was retrieved, or `nil` if one already existed for that log name.
|
||||
*/
|
||||
- (nullable ORKDataLogger *)dataLoggerForLogName:(NSString *)logName;
|
||||
@@ -470,7 +493,7 @@ ORK_CLASS_AVAILABLE
|
||||
- (void)removeDataLogger:(ORKDataLogger *)logger;
|
||||
|
||||
/// Returns the set of log names of the data loggers managed by this object.
|
||||
- (NSArray *)logNames;
|
||||
- (NSArray<NSString *> *)logNames;
|
||||
|
||||
/**
|
||||
Enumerates all the logs that need upload across all data loggers, sorted from oldest to first.
|
||||
@@ -479,6 +502,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@param block The block to call during enumeration.
|
||||
@param error The error, on failure.
|
||||
|
||||
@return `YES` if the enumeration succeeds; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)enumerateLogsNeedingUpload:(void (^)(ORKDataLogger *dataLogger, NSURL *logFileUrl, BOOL *stop))block error:(NSError * __autoreleasing *)error;
|
||||
@@ -489,11 +513,12 @@ ORK_CLASS_AVAILABLE
|
||||
Use this method to indicate that the specified files should no longer be marked uploaded (for example, because
|
||||
the upload did not succeed).
|
||||
|
||||
@param fileURLs The array of file URLs that should no longer be marked uploaded.
|
||||
@param fileURLs The array of file URLs that should no longer be marked uploaded.
|
||||
@param error The error, on failure.
|
||||
|
||||
@return `YES` if the operation succeeds; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)unmarkUploadedFiles:(NSArray *)fileURLs error:(NSError * __nullable __autoreleasing *)error;
|
||||
- (BOOL)unmarkUploadedFiles:(NSArray<NSURL *> *)fileURLs error:(NSError * __nullable __autoreleasing *)error;
|
||||
|
||||
/**
|
||||
Removes a set of uploaded files.
|
||||
@@ -502,11 +527,12 @@ ORK_CLASS_AVAILABLE
|
||||
that may relate to any of the data loggers. It is an error to pass a URL which would not
|
||||
belong to one of the loggers managed by this manager.
|
||||
|
||||
@param fileURLs The array of file URLs that should be removed.
|
||||
@param fileURLs The array of file URLs that should be removed.
|
||||
@param error The error, on failure.
|
||||
|
||||
@return `YES` if the operation succeeds; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)removeUploadedFiles:(NSArray *)fileURLs error:(NSError * __nullable __autoreleasing *)error;
|
||||
- (BOOL)removeUploadedFiles:(NSArray<NSURL *> *)fileURLs error:(NSError * __nullable __autoreleasing *)error;
|
||||
|
||||
/**
|
||||
Removes old and uploaded logs to bring total bytes down to a threshold.
|
||||
@@ -514,16 +540,13 @@ ORK_CLASS_AVAILABLE
|
||||
This method removes uploaded logs first, followed by the oldest log files across
|
||||
all of the data loggers, until the total usage falls below a threshold.
|
||||
|
||||
@param bytes The threshold down to which to remove old log files. File
|
||||
removal stops when the total bytes managed by all the data
|
||||
loggers reaches this threshold.
|
||||
@param bytes The threshold down to which to remove old log files. File removal stops when the total bytes managed by all the data loggers reaches this threshold.
|
||||
@param error The error, on failure.
|
||||
|
||||
@return `YES` if the operation succeeds; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)removeOldAndUploadedLogsToThreshold:(unsigned long long)bytes error:(NSError * __nullable __autoreleasing *)error;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -31,25 +31,32 @@
|
||||
|
||||
#import <ResearchKit/ResearchKit.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/*
|
||||
Exposing a minimal set of extra facilities to permit unit testing.
|
||||
*/
|
||||
@interface ORKDataLogger ()
|
||||
|
||||
|
||||
@interface ORKDataLogger()
|
||||
- (nullable NSFileHandle *)fileHandle;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@protocol ORKDataLoggerExtendedDelegate <ORKDataLoggerDelegate>
|
||||
|
||||
@optional
|
||||
- (void)dataLoggerThresholdsDidChange:(ORKDataLogger *)dataLogger;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface NSURL (ORKDataLogger)
|
||||
|
||||
- (BOOL)ork_isUploaded;
|
||||
- (BOOL)ork_setUploaded:(BOOL)uploaded error:(NSError * __autoreleasing *)error;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -30,6 +30,18 @@
|
||||
|
||||
#import <ResearchKit/ORKRecorder.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class CMDeviceMotion;
|
||||
|
||||
@protocol ORKDeviceMotionRecorderDelegate <ORKRecorderDelegate>
|
||||
|
||||
@optional
|
||||
|
||||
- (void)deviceMotionRecorderDidUpdateWithMotion:(CMDeviceMotion *)motion;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
The `ORKDeviceMotionRecorder` class represents a recorder that requests and collects device motion data from CoreMotion at a fixed frequency.
|
||||
|
||||
@@ -40,18 +52,25 @@ ORK_CLASS_AVAILABLE
|
||||
@interface ORKDeviceMotionRecorder : ORKRecorder
|
||||
|
||||
/**
|
||||
* The frequency of motion data collection from CoreMotion in hertz (Hz).
|
||||
The frequency of motion data collection from CoreMotion in hertz (Hz).
|
||||
*/
|
||||
@property (nonatomic, readonly) double frequency;
|
||||
|
||||
/**
|
||||
Returns an initialized device motion recorder using the specified frequency.
|
||||
|
||||
@param frequency The frequency of motion data collection from CoreMotion in hertz (Hz).
|
||||
@param identifier The unique identifier of the recorder (assigned by the recorder configuration).
|
||||
@param frequency The frequency of motion data collection from CoreMotion in hertz (Hz).
|
||||
@param step The step that requested this recorder.
|
||||
@param outputDirectory The directory in which the device motion data should be stored.
|
||||
|
||||
@return An initialized motion data recorder.
|
||||
*/
|
||||
- (instancetype)initWithFrequency:(double)frequency
|
||||
step:(ORKStep *)step
|
||||
outputDirectory:(NSURL *)outputDirectory NS_DESIGNATED_INITIALIZER;
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier
|
||||
frequency:(double)frequency
|
||||
step:(nullable ORKStep *)step
|
||||
outputDirectory:(nullable NSURL *)outputDirectory NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKDeviceMotionRecorder.h"
|
||||
#import "ORKHelpers.h"
|
||||
#import "ORKRecorder_Internal.h"
|
||||
@@ -36,12 +37,13 @@
|
||||
#import <CoreMotion/CoreMotion.h>
|
||||
#import "CMDeviceMotion+ORKJSONDictionary.h"
|
||||
|
||||
@interface ORKDeviceMotionRecorder()
|
||||
{
|
||||
|
||||
@interface ORKDeviceMotionRecorder () {
|
||||
ORKDataLogger *_logger;
|
||||
}
|
||||
|
||||
@property (nonatomic, strong) CMMotionManager *motionManager;
|
||||
|
||||
@property (nonatomic) NSTimeInterval uptime;
|
||||
|
||||
@end
|
||||
@@ -49,35 +51,28 @@
|
||||
|
||||
@implementation ORKDeviceMotionRecorder
|
||||
|
||||
|
||||
- (instancetype)initWithFrequency:(double)frequency
|
||||
step:(ORKStep *)step
|
||||
outputDirectory:(NSURL *)outputDirectory
|
||||
{
|
||||
self = [super initWithStep:step
|
||||
outputDirectory:(NSURL *)outputDirectory];
|
||||
if (self)
|
||||
{
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier
|
||||
frequency:(double)frequency
|
||||
step:(ORKStep *)step
|
||||
outputDirectory:(NSURL *)outputDirectory {
|
||||
self = [super initWithIdentifier:identifier
|
||||
step:step
|
||||
outputDirectory:outputDirectory];
|
||||
if (self) {
|
||||
self.frequency = frequency;
|
||||
self.continuesInBackground = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
- (void)dealloc {
|
||||
[_logger finishCurrentLog];
|
||||
}
|
||||
|
||||
- (void)setFrequency:(double)frequency
|
||||
{
|
||||
if (frequency <= 0)
|
||||
{
|
||||
- (void)setFrequency:(double)frequency {
|
||||
if (frequency <= 0) {
|
||||
_frequency = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
_frequency = frequency;
|
||||
}
|
||||
}
|
||||
@@ -87,7 +82,6 @@
|
||||
}
|
||||
|
||||
- (void)start {
|
||||
|
||||
[super start];
|
||||
|
||||
if (! _logger) {
|
||||
@@ -106,17 +100,16 @@
|
||||
|
||||
[self.motionManager stopDeviceMotionUpdates];
|
||||
|
||||
[self.motionManager
|
||||
startDeviceMotionUpdatesToQueue:[NSOperationQueue mainQueue]
|
||||
withHandler:^(CMDeviceMotion *data, NSError *error)
|
||||
{
|
||||
[self.motionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue mainQueue] withHandler:^(CMDeviceMotion *data, NSError *error) {
|
||||
BOOL success = NO;
|
||||
if (data)
|
||||
{
|
||||
if (data) {
|
||||
success = [_logger append:[data ork_JSONDictionary] error:&error];
|
||||
id delegate = self.delegate;
|
||||
if ([delegate respondsToSelector:@selector(deviceMotionRecorderDidUpdateWithMotion:)]) {
|
||||
[delegate deviceMotionRecorderDidUpdateWithMotion:data];
|
||||
}
|
||||
}
|
||||
if (!success)
|
||||
{
|
||||
if (!success) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self finishRecordingWithError:error];
|
||||
});
|
||||
@@ -124,13 +117,10 @@
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
- (NSString *)recorderType
|
||||
{
|
||||
- (NSString *)recorderType {
|
||||
return @"deviceMotion";
|
||||
}
|
||||
|
||||
|
||||
- (void)stop {
|
||||
[self doStopRecording];
|
||||
[_logger finishCurrentLog];
|
||||
@@ -146,16 +136,14 @@
|
||||
[super stop];
|
||||
}
|
||||
|
||||
- (void)doStopRecording
|
||||
{
|
||||
- (void)doStopRecording {
|
||||
if (self.isRecording) {
|
||||
[self.motionManager stopDeviceMotionUpdates];
|
||||
self.motionManager = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)finishRecordingWithError:(NSError *)error
|
||||
{
|
||||
- (void)finishRecordingWithError:(NSError *)error {
|
||||
[self doStopRecording];
|
||||
[super finishRecordingWithError:error];
|
||||
}
|
||||
@@ -168,8 +156,7 @@
|
||||
return @"application/json";
|
||||
}
|
||||
|
||||
- (void)reset
|
||||
{
|
||||
- (void)reset {
|
||||
[super reset];
|
||||
|
||||
_logger = nil;
|
||||
@@ -178,7 +165,8 @@
|
||||
@end
|
||||
|
||||
|
||||
@interface ORKDeviceMotionRecorderConfiguration()
|
||||
@interface ORKDeviceMotionRecorderConfiguration ()
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -186,8 +174,12 @@
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wobjc-designated-initializers"
|
||||
- (instancetype)initWithFrequency:(double)freq {
|
||||
self = [super ork_init];
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
||||
@throw [NSException exceptionWithName:NSGenericException reason:@"Use subclass designated initializer" userInfo:nil];
|
||||
}
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier frequency:(double)freq {
|
||||
self = [super initWithIdentifier:identifier];
|
||||
if (self) {
|
||||
_frequency = freq;
|
||||
}
|
||||
@@ -195,32 +187,27 @@
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
- (ORKRecorder *)recorderForStep:(ORKStep *)step outputDirectory:(NSURL *)outputDirectory
|
||||
{
|
||||
return [[ORKDeviceMotionRecorder alloc] initWithFrequency:self.frequency
|
||||
step:step
|
||||
outputDirectory:(NSURL *)outputDirectory];
|
||||
- (ORKRecorder *)recorderForStep:(ORKStep *)step outputDirectory:(NSURL *)outputDirectory {
|
||||
return [[ORKDeviceMotionRecorder alloc] initWithIdentifier:self.identifier
|
||||
frequency:self.frequency
|
||||
step:step
|
||||
outputDirectory:outputDirectory];
|
||||
}
|
||||
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder
|
||||
{
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [super initWithCoder:aDecoder];
|
||||
if (self)
|
||||
{
|
||||
if (self) {
|
||||
ORK_DECODE_DOUBLE(aDecoder, frequency);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder
|
||||
{
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
[super encodeWithCoder:aCoder];
|
||||
ORK_ENCODE_DOUBLE(aCoder, frequency);
|
||||
}
|
||||
|
||||
+ (BOOL)supportsSecureCoding
|
||||
{
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
@@ -229,7 +216,7 @@
|
||||
|
||||
__typeof(self) castObject = object;
|
||||
return (isParentSame &&
|
||||
(self.frequency == castObject.frequency)) ;
|
||||
(self.frequency == castObject.frequency));
|
||||
}
|
||||
|
||||
- (ORKPermissionMask)requestedPermissionMask {
|
||||
|
||||
@@ -28,8 +28,10 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKCustomStepView_Internal.h"
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ORKFitnessContentView : ORKActiveStepCustomView
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKFitnessContentView.h"
|
||||
#import "ORKHelpers.h"
|
||||
#import <CoreMotion/CoreMotion.h>
|
||||
@@ -35,11 +36,11 @@
|
||||
#import "ORKActiveStepQuantityView.h"
|
||||
#import "ORKTintedImageView.h"
|
||||
|
||||
|
||||
// #define LAYOUT_TEST 1
|
||||
// #define LAYOUT_DEBUG 1
|
||||
|
||||
@interface ORKFitnessContentView()
|
||||
{
|
||||
@interface ORKFitnessContentView () {
|
||||
ORKQuantityLabel *_timerLabel;
|
||||
ORKQuantityPairView *_quantityPairView;
|
||||
UIView *_imageSpacer1;
|
||||
@@ -54,6 +55,7 @@
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKFitnessContentView
|
||||
|
||||
- (ORKActiveStepQuantityView *)distanceView {
|
||||
@@ -104,11 +106,14 @@
|
||||
self.backgroundColor = [[UIColor redColor] colorWithAlphaComponent:0.2];
|
||||
_quantityPairView.backgroundColor = [[UIColor orangeColor] colorWithAlphaComponent:0.2];
|
||||
#endif
|
||||
|
||||
|
||||
[self setDistanceInMeters:0];
|
||||
[self heartRateView].title = ORKLocalizedString(@"FITNESS_HEARTRATE_TITLE", nil);
|
||||
|
||||
[self addSubview:_quantityPairView];
|
||||
[self addSubview:_imageView];
|
||||
[self addSubview:_timerLabel];
|
||||
[self setNeedsUpdateConstraints];
|
||||
[self setupConstraints];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(localeDidChange:) name:NSCurrentLocaleDidChangeNotification object:nil];
|
||||
|
||||
@@ -125,7 +130,6 @@
|
||||
_lengthFormatter = [NSLengthFormatter new];
|
||||
_lengthFormatter.numberFormatter.maximumFractionDigits = 1;
|
||||
_lengthFormatter.numberFormatter.maximumSignificantDigits = 3;
|
||||
|
||||
}
|
||||
|
||||
- (void)localeDidChange:(NSNotification *)notification {
|
||||
@@ -133,12 +137,9 @@
|
||||
[self setDistanceInMeters:_distanceInMeters];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
- (void)willMoveToWindow:(UIWindow *)newWindow {
|
||||
[super willMoveToWindow:newWindow];
|
||||
_screenType = ORKGetScreenTypeForWindow(newWindow);
|
||||
_screenType = ORKGetVerticalScreenTypeForWindow(newWindow);
|
||||
[self updateConstraintConstants];
|
||||
}
|
||||
|
||||
@@ -149,40 +150,94 @@
|
||||
_topConstraint.constant = (CaptionBaselineToTimerTop - CaptionBaselineToStepViewTop);
|
||||
}
|
||||
|
||||
- (void)updateConstraints {
|
||||
|
||||
|
||||
if (_constraints) {
|
||||
[self removeConstraints:_constraints];
|
||||
_constraints = nil;
|
||||
}
|
||||
- (void)setupConstraints {
|
||||
NSMutableArray *constraints = [NSMutableArray array];
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(_timerLabel, _imageView, _quantityPairView, _imageSpacer1, _imageSpacer2);
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_timerLabel][_imageSpacer1(>=0)][_imageView]" options:NSLayoutFormatAlignAllCenterX metrics:nil views:views]];
|
||||
_topConstraint = [NSLayoutConstraint constraintWithItem:_timerLabel attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTop multiplier:1 constant:0];
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_timerLabel][_imageSpacer1(>=0)][_imageView]"
|
||||
options:NSLayoutFormatAlignAllCenterX
|
||||
metrics:nil
|
||||
views:views]];
|
||||
_topConstraint = [NSLayoutConstraint constraintWithItem:_timerLabel
|
||||
attribute:NSLayoutAttributeTop
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeTop
|
||||
multiplier:1
|
||||
constant:0];
|
||||
[self updateConstraintConstants];
|
||||
[constraints addObject:_topConstraint];
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_timerLabel attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]];
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_timerLabel attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationLessThanOrEqual toItem:self attribute:NSLayoutAttributeWidth multiplier:1 constant:0]];
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_imageView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationLessThanOrEqual toItem:self attribute:NSLayoutAttributeWidth multiplier:1 constant:0]];
|
||||
[constraints addObject:[NSLayoutConstraint
|
||||
constraintWithItem:_timerLabel
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
multiplier:1
|
||||
constant:0]];
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_timerLabel
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationLessThanOrEqual
|
||||
toItem:self attribute:NSLayoutAttributeWidth
|
||||
multiplier:1
|
||||
constant:0]];
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_imageView
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationLessThanOrEqual
|
||||
toItem:self attribute:NSLayoutAttributeWidth
|
||||
multiplier:1
|
||||
constant:0]];
|
||||
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_imageView][_imageSpacer2(>=0)][_quantityPairView]|" options:0 metrics:nil views:views]];
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_imageSpacer1 attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:0]];
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_imageSpacer2 attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:0]];
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_imageSpacer1 attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:_imageSpacer2 attribute:NSLayoutAttributeHeight multiplier:1 constant:0]];
|
||||
NSLayoutConstraint *c1 = [NSLayoutConstraint constraintWithItem:_imageSpacer1 attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:1000];
|
||||
c1.priority = UILayoutPriorityDefaultLow-1;
|
||||
[constraints addObject:c1];
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_imageView][_imageSpacer2(>=0)][_quantityPairView]|"
|
||||
options:0
|
||||
metrics:nil
|
||||
views:views]];
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_imageSpacer1
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1
|
||||
constant:0]];
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_imageSpacer2
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1
|
||||
constant:0]];
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_imageSpacer1
|
||||
attribute:NSLayoutAttributeHeight
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_imageSpacer2
|
||||
attribute:NSLayoutAttributeHeight
|
||||
multiplier:1
|
||||
constant:0]];
|
||||
NSLayoutConstraint *constraint1 = [NSLayoutConstraint constraintWithItem:_imageSpacer1
|
||||
attribute:NSLayoutAttributeHeight
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1
|
||||
constant:1000];
|
||||
constraint1.priority = UILayoutPriorityDefaultLow-1;
|
||||
[constraints addObject:constraint1];
|
||||
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_quantityPairView]|" options:(NSLayoutFormatOptions)0 metrics:nil views:views]];
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_quantityPairView]|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil
|
||||
views:views]];
|
||||
|
||||
NSLayoutConstraint *maxWidthConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:10000];
|
||||
NSLayoutConstraint *maxWidthConstraint = [NSLayoutConstraint constraintWithItem:self
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1
|
||||
constant:ORKScreenMetricMaxDimension];
|
||||
maxWidthConstraint.priority = UILayoutPriorityRequired-1;
|
||||
[constraints addObject:maxWidthConstraint];
|
||||
|
||||
[self addConstraints:constraints];
|
||||
_constraints = constraints;
|
||||
[super updateConstraints];
|
||||
}
|
||||
|
||||
- (void)setImage:(UIImage *)image {
|
||||
@@ -191,9 +246,15 @@
|
||||
|
||||
_imageRatioConstraint.active = NO;
|
||||
|
||||
CGSize sz = image.size;
|
||||
if (sz.width > 0 && sz.height > 0) {
|
||||
_imageRatioConstraint = [NSLayoutConstraint constraintWithItem:_imageView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:_imageView attribute:NSLayoutAttributeWidth multiplier:sz.height/sz.width constant:0];
|
||||
CGSize size = image.size;
|
||||
if (size.width > 0 && size.height > 0) {
|
||||
_imageRatioConstraint = [NSLayoutConstraint constraintWithItem:_imageView
|
||||
attribute:NSLayoutAttributeHeight
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_imageView
|
||||
attribute:NSLayoutAttributeWidth
|
||||
multiplier:size
|
||||
.height/size.width constant:0];
|
||||
_imageRatioConstraint.active = YES;
|
||||
}
|
||||
}
|
||||
@@ -213,7 +274,6 @@
|
||||
- (void)setHeartRate:(NSString *)heartRate {
|
||||
_heartRate = heartRate;
|
||||
[self heartRateView].value = heartRate;
|
||||
[self heartRateView].title = ORKLocalizedString(@"FITNESS_HEARTRATE_TITLE", nil);
|
||||
}
|
||||
|
||||
- (void)updateKeylineVisible {
|
||||
|
||||
@@ -28,9 +28,11 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import <ResearchKit/ResearchKit.h>
|
||||
#import <ResearchKit/ORKActiveStep.h>
|
||||
|
||||
|
||||
/**
|
||||
Fitness step.
|
||||
|
||||
|
||||
@@ -28,30 +28,36 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKFitnessStep.h"
|
||||
#import "ORKFitnessStepViewController.h"
|
||||
|
||||
|
||||
@implementation ORKFitnessStep
|
||||
|
||||
+ (Class)stepViewControllerClass {
|
||||
return [ORKFitnessStepViewController class];
|
||||
}
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
||||
self = [super initWithIdentifier:identifier];
|
||||
if (self) {
|
||||
self.shouldShowDefaultTimer = NO;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)validateParameters {
|
||||
|
||||
[super validateParameters];
|
||||
|
||||
NSTimeInterval const ORKFitnessStepMinimumDuration = 5.0;
|
||||
|
||||
if ( self.stepDuration < ORKFitnessStepMinimumDuration)
|
||||
{
|
||||
if ( self.stepDuration < ORKFitnessStepMinimumDuration) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"rest duration can not be shorter than %@ seconds.", @(ORKFitnessStepMinimumDuration)] userInfo:nil];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
- (instancetype)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
- (instancetype)copyWithZone:(NSZone *)zone {
|
||||
ORKFitnessStep *step = [super copyWithZone:zone];
|
||||
return step;
|
||||
}
|
||||
@@ -60,6 +66,4 @@
|
||||
return NO;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@@ -28,8 +28,10 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import <ResearchKit/ResearchKit_Private.h>
|
||||
|
||||
|
||||
/**
|
||||
Step view controller corresponding to `ORKFitnessStep`.
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKFitnessStepViewController.h"
|
||||
#import "ORKHelpers.h"
|
||||
#import "ORKStep_Private.h"
|
||||
@@ -41,20 +42,18 @@
|
||||
#import "ORKActiveStepView.h"
|
||||
|
||||
|
||||
@interface ORKFitnessStepViewController () <ORKHealthQuantityTypeRecorderDelegate,ORKPedometerRecorderDelegate>
|
||||
{
|
||||
@interface ORKFitnessStepViewController () <ORKHealthQuantityTypeRecorderDelegate,ORKPedometerRecorderDelegate> {
|
||||
NSInteger _intendedSteps;
|
||||
ORKFitnessContentView *_contentView;
|
||||
NSNumberFormatter *_hrFormatter;
|
||||
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKFitnessStepViewController
|
||||
|
||||
|
||||
- (instancetype)initWithStep:(ORKStep *)step {
|
||||
|
||||
- (instancetype)initWithStep:(ORKStep *)step {
|
||||
self = [super initWithStep:step];
|
||||
if (self) {
|
||||
self.suspendIfInactive = NO;
|
||||
|
||||
@@ -28,8 +28,10 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import <ResearchKit/ORKRecorder.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class ORKHealthQuantityTypeRecorder;
|
||||
@@ -37,11 +39,11 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@protocol ORKHealthQuantityTypeRecorderDelegate <ORKRecorderDelegate>
|
||||
|
||||
@optional
|
||||
|
||||
- (void)healthQuantityTypeRecorderDidUpdate:(ORKHealthQuantityTypeRecorder *)healthQuantityTypeRecorder;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
The `ORKHealthQuantityTypeRecorder` class represents a recorder for collecting real time sample data from HealthKit, such as heart rate, during
|
||||
an active task.
|
||||
@@ -49,14 +51,28 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKHealthQuantityTypeRecorder : ORKRecorder
|
||||
|
||||
@property (nonatomic, readonly, copy) HKQuantityType *quantityType;
|
||||
@property (nonatomic, readonly, copy) HKUnit *unit;
|
||||
@property (nonatomic, readonly, copy, nullable) HKQuantitySample *lastSample;
|
||||
@property (nonatomic, copy, readonly) HKQuantityType *quantityType;
|
||||
|
||||
- (instancetype)initWithHealthQuantityType:(HKQuantityType *)quantityType
|
||||
unit:(HKUnit *)unit
|
||||
step:(ORKStep *)step
|
||||
outputDirectory:(NSURL *)outputDirectory NS_DESIGNATED_INITIALIZER;
|
||||
@property (nonatomic, copy, readonly) HKUnit *unit;
|
||||
|
||||
@property (nonatomic, copy, readonly, nullable) HKQuantitySample *lastSample;
|
||||
|
||||
/**
|
||||
Returns an initialized health quantity type recorder using the specified quantity type and unit.
|
||||
|
||||
@param identifier The unique identifier of the recorder (assigned by the recorder configuration).
|
||||
@param quantityType The quantity type that should be collected during the active task.
|
||||
@param unit The unit for the data that should be collected and serialized.
|
||||
@param step The step that requested this recorder.
|
||||
@param outputDirectory The directory in which the HealthKit data should be stored.
|
||||
|
||||
@return An initialized health quantity type recorder.
|
||||
*/
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier
|
||||
healthQuantityType:(HKQuantityType *)quantityType
|
||||
unit:(HKUnit *)unit
|
||||
step:(nullable ORKStep *)step
|
||||
outputDirectory:(nullable NSURL *)outputDirectory NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKHealthQuantityTypeRecorder.h"
|
||||
#import "ORKHelpers.h"
|
||||
#import "ORKDataLogger.h"
|
||||
@@ -35,8 +36,8 @@
|
||||
#import "ORKRecorder_Internal.h"
|
||||
#import "HKSample+ORKJSONDictionary.h"
|
||||
|
||||
@interface ORKHealthQuantityTypeRecorder()
|
||||
{
|
||||
|
||||
@interface ORKHealthQuantityTypeRecorder () {
|
||||
ORKDataLogger *_logger;
|
||||
BOOL _isRecording;
|
||||
HKHealthStore *_healthStore;
|
||||
@@ -46,23 +47,20 @@
|
||||
HKQuantitySample *_lastSample;
|
||||
}
|
||||
|
||||
@property (nonatomic, strong) NSError *recordingError;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKHealthQuantityTypeRecorder
|
||||
|
||||
|
||||
- (instancetype)initWithHealthQuantityType:(HKQuantityType *)quantityType
|
||||
unit:(HKUnit *)unit
|
||||
step:(ORKStep *)step
|
||||
outputDirectory:(NSURL *)outputDirectory
|
||||
{
|
||||
self = [super initWithStep:step
|
||||
outputDirectory:(NSURL *)outputDirectory];
|
||||
if (self)
|
||||
{
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier
|
||||
healthQuantityType:(HKQuantityType *)quantityType
|
||||
unit:(HKUnit *)unit
|
||||
step:(ORKStep *)step
|
||||
outputDirectory:(NSURL *)outputDirectory {
|
||||
self = [super initWithIdentifier:identifier
|
||||
step:step
|
||||
outputDirectory:outputDirectory];
|
||||
if (self) {
|
||||
NSParameterAssert(quantityType != nil);
|
||||
NSParameterAssert(unit != nil);
|
||||
// Quantity type and unit are immutable, so should be equivalent to -copy
|
||||
@@ -74,9 +72,7 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
- (void)dealloc {
|
||||
[_logger finishCurrentLog];
|
||||
}
|
||||
|
||||
@@ -89,7 +85,6 @@
|
||||
if (delegate && [delegate respondsToSelector:@selector(healthQuantityTypeRecorderDidUpdate:)]) {
|
||||
[delegate healthQuantityTypeRecorderDidUpdate:self];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static const NSInteger _HealthAnchoredQueryLimit = 100;
|
||||
@@ -123,19 +118,15 @@ static const NSInteger _HealthAnchoredQueryLimit = 100;
|
||||
// Do another fetch immediately rather than wait for an observation
|
||||
[self doFetchNewData];
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
- (void)doFetchNewData {
|
||||
if (! _healthStore || ! _isRecording) {
|
||||
return;
|
||||
}
|
||||
NSAssert(_samplePredicate != nil, @"Sample predicate should be non-nil if recording");
|
||||
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
HKAnchoredObjectQuery *anchoredQuery = [[HKAnchoredObjectQuery alloc]
|
||||
initWithType:_quantityType
|
||||
@@ -157,12 +148,9 @@ static const NSInteger _HealthAnchoredQueryLimit = 100;
|
||||
[_healthStore executeQuery:anchoredQuery];
|
||||
}
|
||||
|
||||
|
||||
- (void)start {
|
||||
|
||||
[super start];
|
||||
|
||||
|
||||
if (! _logger) {
|
||||
NSError *err = nil;
|
||||
_logger = [self makeJSONDataLoggerWithError:&err];
|
||||
@@ -172,8 +160,7 @@ static const NSInteger _HealthAnchoredQueryLimit = 100;
|
||||
}
|
||||
}
|
||||
|
||||
if (! [HKHealthStore isHealthDataAvailable])
|
||||
{
|
||||
if (! [HKHealthStore isHealthDataAvailable]) {
|
||||
[self finishRecordingWithError:[NSError errorWithDomain:NSCocoaErrorDomain
|
||||
code:NSFeatureUnsupportedError
|
||||
userInfo:@{@"recorder" : self}]];
|
||||
@@ -230,13 +217,10 @@ static const NSInteger _HealthAnchoredQueryLimit = 100;
|
||||
[_healthStore executeQuery:_observerQuery];
|
||||
}
|
||||
|
||||
|
||||
- (NSString *)recorderType
|
||||
{
|
||||
- (NSString *)recorderType {
|
||||
return _quantityType.identifier;
|
||||
}
|
||||
|
||||
|
||||
- (void)stop {
|
||||
if (! _isRecording) {
|
||||
return;
|
||||
@@ -251,14 +235,12 @@ static const NSInteger _HealthAnchoredQueryLimit = 100;
|
||||
fileUrl = logFileUrl;
|
||||
} error:&error];
|
||||
|
||||
|
||||
[self reportFileResultWithFile:fileUrl error:error];
|
||||
|
||||
[super stop];
|
||||
}
|
||||
|
||||
- (void)doStopRecording
|
||||
{
|
||||
- (void)doStopRecording {
|
||||
if (_isRecording) {
|
||||
NSAssert(_observerQuery != nil, @"Observer query should be non-nil when recording");
|
||||
[_healthStore stopQuery:_observerQuery];
|
||||
@@ -271,8 +253,7 @@ static const NSInteger _HealthAnchoredQueryLimit = 100;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)finishRecordingWithError:(NSError *)error
|
||||
{
|
||||
- (void)finishRecordingWithError:(NSError *)error {
|
||||
[self doStopRecording];
|
||||
[super finishRecordingWithError:error];
|
||||
}
|
||||
@@ -285,8 +266,7 @@ static const NSInteger _HealthAnchoredQueryLimit = 100;
|
||||
return @"application/json";
|
||||
}
|
||||
|
||||
- (void)reset
|
||||
{
|
||||
- (void)reset {
|
||||
[super reset];
|
||||
|
||||
_logger = nil;
|
||||
@@ -294,15 +274,22 @@ static const NSInteger _HealthAnchoredQueryLimit = 100;
|
||||
|
||||
@end
|
||||
|
||||
@interface ORKHealthQuantityTypeRecorderConfiguration()
|
||||
|
||||
@interface ORKHealthQuantityTypeRecorderConfiguration ()
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKHealthQuantityTypeRecorderConfiguration
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wobjc-designated-initializers"
|
||||
- (instancetype)initWithHealthQuantityType:(HKQuantityType *)quantityType unit:(HKUnit *)unit {
|
||||
self = [super ork_init];
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
||||
@throw [NSException exceptionWithName:NSGenericException reason:@"Use subclass designated initializer" userInfo:nil];
|
||||
}
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier healthQuantityType:(HKQuantityType *)quantityType unit:(HKUnit *)unit {
|
||||
self = [super initWithIdentifier:identifier];
|
||||
if (self) {
|
||||
NSParameterAssert(quantityType != nil);
|
||||
NSParameterAssert(unit != nil);
|
||||
@@ -314,34 +301,29 @@ static const NSInteger _HealthAnchoredQueryLimit = 100;
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
- (ORKRecorder *)recorderForStep:(ORKStep *)step outputDirectory:(NSURL *)outputDirectory
|
||||
{
|
||||
return [[ORKHealthQuantityTypeRecorder alloc] initWithHealthQuantityType:_quantityType
|
||||
unit:_unit
|
||||
step:step
|
||||
outputDirectory:outputDirectory];
|
||||
- (ORKRecorder *)recorderForStep:(ORKStep *)step outputDirectory:(NSURL *)outputDirectory {
|
||||
return [[ORKHealthQuantityTypeRecorder alloc] initWithIdentifier:self.identifier
|
||||
healthQuantityType:_quantityType
|
||||
unit:_unit
|
||||
step:step
|
||||
outputDirectory:outputDirectory];
|
||||
}
|
||||
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder
|
||||
{
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [super initWithCoder:aDecoder];
|
||||
if (self)
|
||||
{
|
||||
if (self) {
|
||||
ORK_DECODE_OBJ_CLASS(aDecoder, quantityType, HKQuantityType);
|
||||
ORK_DECODE_OBJ_CLASS(aDecoder, unit, HKUnit);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder
|
||||
{
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
ORK_ENCODE_OBJ(aCoder, quantityType);
|
||||
ORK_ENCODE_OBJ(aCoder, unit);
|
||||
}
|
||||
|
||||
+ (BOOL)supportsSecureCoding
|
||||
{
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
@@ -351,10 +333,9 @@ static const NSInteger _HealthAnchoredQueryLimit = 100;
|
||||
__typeof(self) castObject = object;
|
||||
return (isParentSame &&
|
||||
ORKEqualObjects(self.quantityType, castObject.quantityType)&&
|
||||
ORKEqualObjects(self.unit, castObject.unit)) ;
|
||||
ORKEqualObjects(self.unit, castObject.unit));
|
||||
}
|
||||
|
||||
|
||||
- (NSSet *)requestedHealthKitTypesForReading {
|
||||
return [NSSet setWithObject:_quantityType];
|
||||
}
|
||||
|
||||
@@ -28,7 +28,12 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import <ResearchKit/ORKRecorder.h>
|
||||
#import <CoreLocation/CoreLocation.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
The `ORKLocationRecorder` class represents a recorder for collecting location data from CoreLocation.
|
||||
@@ -41,4 +46,25 @@
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKLocationRecorder : ORKRecorder
|
||||
|
||||
/**
|
||||
Returns an initialized location recorder.
|
||||
|
||||
@param identifier The unique identifier of the recorder (assigned by the recorder configuration).
|
||||
@param step The step that requested this recorder.
|
||||
@param outputDirectory The directory in which the location data should be stored.
|
||||
|
||||
@return An initialized location recorder.
|
||||
*/
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier
|
||||
step:(nullable ORKStep *)step
|
||||
outputDirectory:(nullable NSURL *)outputDirectory NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
|
||||
/**
|
||||
The location manager, if any, being used by this recorder.
|
||||
*/
|
||||
@property (nonatomic, strong, nullable, readonly) CLLocationManager *locationManager;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKLocationRecorder.h"
|
||||
#import <CoreLocation/CoreLocation.h>
|
||||
#import "CLLocation+ORKJSONDictionary.h"
|
||||
@@ -35,13 +36,15 @@
|
||||
#import "ORKRecorder_Internal.h"
|
||||
#import "ORKRecorder_Private.h"
|
||||
|
||||
@interface ORKLocationRecorder() <CLLocationManagerDelegate>
|
||||
{
|
||||
|
||||
@interface ORKLocationRecorder () <CLLocationManagerDelegate> {
|
||||
ORKDataLogger *_logger;
|
||||
NSError *_recordingError;
|
||||
BOOL _started;
|
||||
}
|
||||
@property (nonatomic, strong) CLLocationManager *locationManager;
|
||||
|
||||
@property (nonatomic, strong, nullable) CLLocationManager *locationManager;
|
||||
|
||||
@property (nonatomic) NSTimeInterval uptime;
|
||||
|
||||
@end
|
||||
@@ -49,19 +52,15 @@
|
||||
|
||||
@implementation ORKLocationRecorder
|
||||
|
||||
|
||||
- (instancetype)initWithStep:(ORKStep *)step outputDirectory:(NSURL *)outputDirectory
|
||||
{
|
||||
self = [super initWithStep:step outputDirectory:outputDirectory];
|
||||
if (self)
|
||||
{
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier step:(ORKStep *)step outputDirectory:(NSURL *)outputDirectory {
|
||||
self = [super initWithIdentifier:identifier step:step outputDirectory:outputDirectory];
|
||||
if (self) {
|
||||
self.continuesInBackground = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
- (void)dealloc {
|
||||
[_logger finishCurrentLog];
|
||||
}
|
||||
|
||||
@@ -86,8 +85,7 @@
|
||||
}
|
||||
|
||||
self.locationManager = [self createLocationManager];
|
||||
if ([CLLocationManager authorizationStatus] <= kCLAuthorizationStatusDenied)
|
||||
{
|
||||
if ([CLLocationManager authorizationStatus] <= kCLAuthorizationStatusDenied) {
|
||||
[self.locationManager requestWhenInUseAuthorization];
|
||||
}
|
||||
self.locationManager.pausesLocationUpdatesAutomatically = NO;
|
||||
@@ -115,7 +113,6 @@
|
||||
[self doStopRecording];
|
||||
[_logger finishCurrentLog];
|
||||
|
||||
|
||||
NSError *error = _recordingError;
|
||||
_recordingError = nil;
|
||||
__block NSURL *fileUrl = nil;
|
||||
@@ -129,13 +126,11 @@
|
||||
}
|
||||
|
||||
- (void)locationManager:(CLLocationManager *)manager
|
||||
didUpdateLocations:(NSArray *)locations
|
||||
{
|
||||
didUpdateLocations:(NSArray *)locations {
|
||||
BOOL success = YES;
|
||||
NSParameterAssert([locations count] >= 0);
|
||||
NSError *error = nil;
|
||||
if (locations)
|
||||
{
|
||||
if (locations) {
|
||||
NSMutableArray *dictionaries = [NSMutableArray arrayWithCapacity:[locations count]];
|
||||
[locations enumerateObjectsUsingBlock:^(CLLocation *obj, NSUInteger idx, BOOL *stop) {
|
||||
NSDictionary *d = [obj ork_JSONDictionary];
|
||||
@@ -144,8 +139,7 @@
|
||||
|
||||
success = [_logger appendObjects:dictionaries error:&error];
|
||||
}
|
||||
if (!success)
|
||||
{
|
||||
if (!success) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
_recordingError = error;
|
||||
[self stop];
|
||||
@@ -153,8 +147,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)finishRecordingWithError:(NSError *)error
|
||||
{
|
||||
- (void)finishRecordingWithError:(NSError *)error {
|
||||
[self doStopRecording];
|
||||
[super finishRecordingWithError:nil];
|
||||
}
|
||||
@@ -163,14 +156,12 @@
|
||||
return [CLLocationManager locationServicesEnabled] && (self.locationManager != nil) && ([CLLocationManager authorizationStatus] > kCLAuthorizationStatusDenied);
|
||||
}
|
||||
|
||||
- (void)reset
|
||||
{
|
||||
- (void)reset {
|
||||
[super reset];
|
||||
|
||||
_logger = nil;
|
||||
}
|
||||
|
||||
|
||||
- (NSString *)mimeType {
|
||||
return @"application/json";
|
||||
}
|
||||
@@ -178,35 +169,36 @@
|
||||
@end
|
||||
|
||||
|
||||
@interface ORKLocationRecorderConfiguration()
|
||||
@interface ORKLocationRecorderConfiguration ()
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKLocationRecorderConfiguration
|
||||
|
||||
- (ORKRecorder *)recorderForStep:(ORKStep *)step outputDirectory:(NSURL *)outputDirectory {
|
||||
return [[ORKLocationRecorder alloc] initWithStep:step outputDirectory:outputDirectory];
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
||||
return [super initWithIdentifier:identifier];
|
||||
}
|
||||
|
||||
|
||||
- (ORKRecorder *)recorderForStep:(ORKStep *)step outputDirectory:(NSURL *)outputDirectory {
|
||||
return [[ORKLocationRecorder alloc] initWithIdentifier:self.identifier step:step outputDirectory:outputDirectory];
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [super initWithCoder:aDecoder];
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (BOOL)supportsSecureCoding
|
||||
{
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
- (BOOL)isEqual:(id)object {
|
||||
BOOL isParentSame = [super isEqual:object];
|
||||
|
||||
return isParentSame;
|
||||
}
|
||||
|
||||
|
||||
- (ORKPermissionMask)requestedPermissionMask {
|
||||
return ORKPermissionCoreLocation;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
Copyright (c) 2015, Shazino SAS. 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_Private.h>
|
||||
#import "ORKCustomStepView_Internal.h"
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class ORKPSATKeyboardView;
|
||||
|
||||
@interface ORKPSATContentView : ORKActiveStepCustomView
|
||||
|
||||
@property (nonatomic, strong) ORKPSATKeyboardView *keyboardView;
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
|
||||
- (instancetype)initWithPresentationMode:(ORKPSATPresentationMode)presentationMode NS_DESIGNATED_INITIALIZER;
|
||||
- (void)setEnabled:(BOOL)enabled;
|
||||
- (void)setAddition:(NSUInteger)additionIndex forTotal:(NSUInteger)totalAddition withDigit:(NSNumber *)digit;
|
||||
- (void)setProgress:(CGFloat)progress animated:(BOOL)animated;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
Copyright (c) 2015, Shazino SAS. 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 "ORKPSATContentView.h"
|
||||
#import "ORKSkin.h"
|
||||
#import "ORKPSATKeyboardView.h"
|
||||
#import "ORKTapCountLabel.h"
|
||||
#import "ORKBorderedButton.h"
|
||||
#import "ORKVoiceEngine.h"
|
||||
#import "ORKHelpers.h"
|
||||
|
||||
|
||||
@interface ORKPSATContentView ()
|
||||
|
||||
@property (nonatomic, assign, getter = isAuditory) BOOL auditory;
|
||||
@property (nonatomic, strong) UIProgressView *progressView;
|
||||
@property (nonatomic, strong) ORKTapCountLabel *digitLabel;
|
||||
@property (nonatomic, assign) ORKScreenType screenType;
|
||||
@property (nonatomic, strong) NSArray *constraints;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKPSATContentView
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
ORKThrowMethodUnavailableException();
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [self initWithPresentationMode:ORKPSATPresentationModeAuditory];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithPresentationMode:(ORKPSATPresentationMode)presentationMode {
|
||||
self = [super initWithFrame:CGRectZero];
|
||||
|
||||
if (self) {
|
||||
|
||||
_screenType = ORKGetVerticalScreenTypeForWindow(self.window);
|
||||
|
||||
_digitLabel = [ORKTapCountLabel new];
|
||||
_digitLabel.textAlignment = NSTextAlignmentCenter;
|
||||
_digitLabel.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[self addSubview:_digitLabel];
|
||||
_auditory = (presentationMode & ORKPSATPresentationModeAuditory) ? YES : NO;
|
||||
if (!(presentationMode & ORKPSATPresentationModeVisual)) {
|
||||
_digitLabel.hidden = YES;
|
||||
}
|
||||
|
||||
_progressView = [UIProgressView new];
|
||||
_progressView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
_progressView.progressTintColor = [self tintColor];
|
||||
[_progressView setAlpha:0];
|
||||
[self addSubview:_progressView];
|
||||
|
||||
_keyboardView = [ORKPSATKeyboardView new];
|
||||
_keyboardView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[self addSubview:_keyboardView];
|
||||
|
||||
self.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
|
||||
[self setNeedsUpdateConstraints];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setEnabled:(BOOL)enabled {
|
||||
self.keyboardView.enabled = enabled;
|
||||
}
|
||||
|
||||
- (void)setAddition:(NSUInteger)additionIndex forTotal:(NSUInteger)totalAddition withDigit:(NSNumber *)digit {
|
||||
if (digit.integerValue == -1) {
|
||||
self.digitLabel.textColor = [[UIColor blackColor] colorWithAlphaComponent:0.3f];
|
||||
self.digitLabel.text = ORKLocalizedString(@"PSAT_NO_DIGIT", nil);
|
||||
} else {
|
||||
[self.keyboardView.selectedAnswerButton setSelected:NO];
|
||||
self.digitLabel.textColor = nil;
|
||||
self.digitLabel.text = [NSNumberFormatter localizedStringFromNumber:digit
|
||||
numberStyle:NSNumberFormatterNoStyle];
|
||||
if (self.isAuditory) {
|
||||
[[ORKVoiceEngine sharedVoiceEngine] speakInt:digit.integerValue];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setProgress:(CGFloat)progress animated:(BOOL)animated {
|
||||
[self.progressView setProgress:progress animated:animated];
|
||||
[UIView animateWithDuration:animated ? 0.2 : 0 animations:^{
|
||||
[self.progressView setAlpha:(progress == 0) ? 0 : 1];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)updateConstraints {
|
||||
if ([self.constraints count]) {
|
||||
[NSLayoutConstraint deactivateConstraints:self.constraints];
|
||||
self.constraints = nil;
|
||||
}
|
||||
|
||||
const CGFloat ORKPSATKeyboardWidth = ORKGetMetricForScreenType(ORKScreenMetricPSATKeyboardViewWidth, self.screenType);
|
||||
const CGFloat ORKPSATKeyboardHeight = ORKGetMetricForScreenType(ORKScreenMetricPSATKeyboardViewHeight, self.screenType);
|
||||
|
||||
NSMutableArray *constraintsArray = [NSMutableArray array];
|
||||
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(_progressView, _digitLabel, _keyboardView);
|
||||
|
||||
[constraintsArray addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_progressView]-|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil views:views]];
|
||||
|
||||
[constraintsArray addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:@"H:|-[_keyboardView(==%f)]-|", ORKPSATKeyboardWidth]
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil views:views]];
|
||||
|
||||
[constraintsArray addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:@"V:[_keyboardView(==%f)]", ORKPSATKeyboardHeight]
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil views:views]];
|
||||
|
||||
[constraintsArray addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_progressView]-[_digitLabel]-(>=10)-[_keyboardView]-|"
|
||||
options:NSLayoutFormatAlignAllCenterX
|
||||
metrics:nil views:views]];
|
||||
|
||||
self.constraints = constraintsArray;
|
||||
[self addConstraints:self.constraints];
|
||||
|
||||
[NSLayoutConstraint activateConstraints:self.constraints];
|
||||
[super updateConstraints];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
Copyright (c) 2015, Shazino SAS. 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>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class ORKBorderedButton;
|
||||
@protocol ORKPSATKeyboardViewDelegate;
|
||||
|
||||
@interface ORKPSATKeyboardView : UIView
|
||||
|
||||
@property (nonatomic, weak) id<ORKPSATKeyboardViewDelegate> delegate;
|
||||
@property (nonatomic, weak) ORKBorderedButton *selectedAnswerButton;
|
||||
|
||||
- (void)setEnabled:(BOOL)enabled;
|
||||
|
||||
@end
|
||||
|
||||
@protocol ORKPSATKeyboardViewDelegate <NSObject>
|
||||
|
||||
- (void)keyboardView:(ORKPSATKeyboardView *)keyboardView didSelectAnswer:(NSInteger)answer;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
Copyright (c) 2015, Shazino SAS. 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 "ORKPSATKeyboardView.h"
|
||||
#import "ORKBorderedButton.h"
|
||||
|
||||
|
||||
NSUInteger const ORKPSATMinimumAnswer = 3;
|
||||
NSUInteger const ORKPSATMaximumAnswer = 17;
|
||||
|
||||
@interface ORKPSATKeyboardView ()
|
||||
|
||||
@property (nonatomic, strong, readonly) NSArray *answerButtons;
|
||||
@property (nonatomic, strong) NSArray *constraints;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKPSATKeyboardView
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
NSMutableArray *buttonsArray = [[NSMutableArray alloc] initWithCapacity:(ORKPSATMaximumAnswer - ORKPSATMinimumAnswer) + 1];
|
||||
ORKBorderedButton *answerButton = nil;
|
||||
for (int i = ORKPSATMinimumAnswer; i <= ORKPSATMaximumAnswer; i++) {
|
||||
answerButton = [self answerButtonWithTitle:[NSNumberFormatter localizedStringFromNumber:@(i)
|
||||
numberStyle:NSNumberFormatterNoStyle]];
|
||||
[buttonsArray addObject:answerButton];
|
||||
[self addSubview:answerButton];
|
||||
}
|
||||
_answerButtons = [NSArray arrayWithArray:buttonsArray];
|
||||
|
||||
self.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
|
||||
[self setNeedsUpdateConstraints];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (ORKBorderedButton *)answerButtonWithTitle:(NSString *)title {
|
||||
ORKBorderedButton *answerButton = [ORKBorderedButton new];
|
||||
answerButton.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[answerButton setTitle:title forState:UIControlStateNormal];
|
||||
[answerButton addTarget:self action:@selector(buttonPressed:forEvent:) forControlEvents:UIControlEventTouchUpInside];
|
||||
answerButton.accessibilityTraits |= UIAccessibilityTraitKeyboardKey;
|
||||
return answerButton;
|
||||
}
|
||||
|
||||
- (void)setEnabled:(BOOL)enabled {
|
||||
for (ORKBorderedButton *answerButton in self.answerButtons) {
|
||||
[answerButton setEnabled:enabled];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateConstraints {
|
||||
if ([self.constraints count]) {
|
||||
[NSLayoutConstraint deactivateConstraints:self.constraints];
|
||||
self.constraints = nil;
|
||||
}
|
||||
|
||||
NSMutableArray *constraintsArray = [NSMutableArray array];
|
||||
|
||||
ORKBorderedButton *answer3Button = self.answerButtons[0];
|
||||
ORKBorderedButton *answer4Button = self.answerButtons[1];
|
||||
ORKBorderedButton *answer5Button = self.answerButtons[2];
|
||||
ORKBorderedButton *answer6Button = self.answerButtons[3];
|
||||
ORKBorderedButton *answer7Button = self.answerButtons[4];
|
||||
ORKBorderedButton *answer8Button = self.answerButtons[5];
|
||||
ORKBorderedButton *answer9Button = self.answerButtons[6];
|
||||
ORKBorderedButton *answer10Button = self.answerButtons[7];
|
||||
ORKBorderedButton *answer11Button = self.answerButtons[8];
|
||||
ORKBorderedButton *answer12Button = self.answerButtons[9];
|
||||
ORKBorderedButton *answer13Button = self.answerButtons[10];
|
||||
ORKBorderedButton *answer14Button = self.answerButtons[11];
|
||||
ORKBorderedButton *answer15Button = self.answerButtons[12];
|
||||
ORKBorderedButton *answer16Button = self.answerButtons[13];
|
||||
ORKBorderedButton *answer17Button = self.answerButtons[14];
|
||||
|
||||
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(answer3Button, answer4Button, answer5Button, answer6Button, answer7Button, answer8Button, answer9Button, answer10Button, answer11Button, answer12Button, answer13Button, answer14Button, answer15Button, answer16Button, answer17Button);
|
||||
|
||||
// First line of answer buttons
|
||||
[constraintsArray addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[answer3Button]-[answer4Button(==answer3Button)]-[answer5Button(==answer3Button)]-[answer6Button(==answer3Button)]-[answer7Button(==answer3Button)]-|"
|
||||
options:NSLayoutFormatAlignAllCenterY|NSLayoutFormatAlignAllTop|NSLayoutFormatAlignAllBottom
|
||||
metrics:nil views:views]];
|
||||
|
||||
// Second line of answer buttons
|
||||
[constraintsArray addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[answer8Button]-[answer9Button(==answer8Button)]-[answer10Button(==answer8Button)]-[answer11Button(==answer8Button)]-[answer12Button(==answer8Button)]-|"
|
||||
options:NSLayoutFormatAlignAllCenterY|NSLayoutFormatAlignAllTop|NSLayoutFormatAlignAllBottom
|
||||
metrics:nil views:views]];
|
||||
|
||||
// Third line of answer buttons
|
||||
[constraintsArray addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[answer13Button]-[answer14Button(==answer13Button)]-[answer15Button(==answer13Button)]-[answer16Button(==answer13Button)]-[answer17Button(==answer13Button)]-|"
|
||||
options:NSLayoutFormatAlignAllCenterY|NSLayoutFormatAlignAllTop|NSLayoutFormatAlignAllBottom
|
||||
metrics:nil views:views]];
|
||||
|
||||
// Align vertically
|
||||
[constraintsArray addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[answer3Button]-[answer8Button(==answer3Button)]-[answer13Button(==answer3Button)]-|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil views:views]];
|
||||
|
||||
self.constraints = constraintsArray;
|
||||
[self addConstraints:self.constraints];
|
||||
|
||||
[NSLayoutConstraint activateConstraints:self.constraints];
|
||||
[super updateConstraints];
|
||||
}
|
||||
|
||||
#pragma mark buttonAction
|
||||
|
||||
- (IBAction)buttonPressed:(id)button forEvent:(UIEvent *)event {
|
||||
ORKBorderedButton *tappedAnswerButton = (ORKBorderedButton *)button;
|
||||
|
||||
[self.selectedAnswerButton setSelected:NO];
|
||||
self.selectedAnswerButton = tappedAnswerButton;
|
||||
[self.selectedAnswerButton setSelected:YES];
|
||||
|
||||
if ([self.delegate respondsToSelector:@selector(keyboardView:didSelectAnswer:)]) {
|
||||
NSInteger answerValue = [self.answerButtons indexOfObject:tappedAnswerButton] + ORKPSATMinimumAnswer;
|
||||
[self.delegate keyboardView:self didSelectAnswer:answerValue];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
Copyright (c) 2015, Shazino SAS. 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_Private.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKPSATStep : ORKActiveStep
|
||||
|
||||
@property (nonatomic, assign) ORKPSATPresentationMode presentationMode;
|
||||
@property (nonatomic, assign) NSTimeInterval interStimulusInterval;
|
||||
@property (nonatomic, assign) NSTimeInterval stimulusDuration;
|
||||
@property (nonatomic, assign) NSInteger seriesLength;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
Copyright (c) 2015, Shazino SAS. 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 "ORKPSATStep.h"
|
||||
#import "ORKPSATStepViewController.h"
|
||||
|
||||
|
||||
@implementation ORKPSATStep
|
||||
|
||||
+ (Class)stepViewControllerClass {
|
||||
return [ORKPSATStepViewController class];
|
||||
}
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
||||
self = [super initWithIdentifier:identifier];
|
||||
if (self) {
|
||||
self.shouldStartTimerAutomatically = YES;
|
||||
self.shouldShowDefaultTimer = NO;
|
||||
self.shouldContinueOnFinish = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)validateParameters {
|
||||
[super validateParameters];
|
||||
|
||||
NSTimeInterval const ORKPSATInterStimulusMinimumInterval = 1.0;
|
||||
NSTimeInterval const ORKPSATInterStimulusMaximumInterval = 5.0;
|
||||
|
||||
NSTimeInterval const ORKPSATStimulusMinimumDuration = 0.2;
|
||||
|
||||
NSInteger const ORKPSATSerieMinimumLength = 10;
|
||||
NSInteger const ORKPSATSerieMaximumLength = 120;
|
||||
|
||||
NSTimeInterval totalDuration = (self.seriesLength + 1) * self.interStimulusInterval;
|
||||
if (self.stepDuration != totalDuration) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"step duration must be equal to %@ seconds.", @(totalDuration)] userInfo:nil];
|
||||
}
|
||||
|
||||
if (!(self.presentationMode & ORKPSATPresentationModeAuditory) &&
|
||||
!(self.presentationMode & ORKPSATPresentationModeVisual)) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"step presentation mode must be auditory and/or visual." userInfo:nil];
|
||||
}
|
||||
|
||||
if (self.interStimulusInterval < ORKPSATInterStimulusMinimumInterval ||
|
||||
self.interStimulusInterval > ORKPSATInterStimulusMaximumInterval) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"inter stimulus interval must be greater than or equal to %@ seconds and less than or equal to %@ seconds.", @(ORKPSATInterStimulusMinimumInterval), @(ORKPSATInterStimulusMaximumInterval)] userInfo:nil];
|
||||
}
|
||||
|
||||
if ((self.presentationMode & ORKPSATPresentationModeVisual) &&
|
||||
(self.stimulusDuration < ORKPSATStimulusMinimumDuration || self.stimulusDuration > self.interStimulusInterval)) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"stimulus duration must be greater than or equal to %@ seconds and less than or equal to %@ seconds.", @(ORKPSATStimulusMinimumDuration), @(self.interStimulusInterval)] userInfo:nil];
|
||||
}
|
||||
|
||||
if (self.seriesLength < ORKPSATSerieMinimumLength ||
|
||||
self.seriesLength > ORKPSATSerieMaximumLength) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"serie length must be greater than or equal to %@ additions and less than or equal to %@ additions.", @(ORKPSATSerieMinimumLength), @(ORKPSATSerieMaximumLength)] userInfo:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)allowsBackNavigation {
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
+6
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
Copyright (c) 2015, Shazino SAS. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
@@ -28,13 +28,15 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import <ResearchKit/ResearchKit_Private.h>
|
||||
#import "ORKConsentSection.h"
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
NSURL *__nullable ORKMovieURLForConsentSectionType(ORKConsentSectionType type);
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKPSATStepViewController : ORKActiveStepViewController
|
||||
|
||||
UIImage *__nullable ORKImageForConsentSectionType(ORKConsentSectionType type);
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,235 @@
|
||||
/*
|
||||
Copyright (c) 2015, Shazino SAS. 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 "ORKPSATStepViewController.h"
|
||||
#import "ORKActiveStepViewController_Internal.h"
|
||||
#import "ORKStepViewController_Internal.h"
|
||||
#import "ORKPSATContentView.h"
|
||||
#import "ORKPSATStep.h"
|
||||
#import "ORKVerticalContainerView.h"
|
||||
#import "ORKActiveStepView.h"
|
||||
#import "ORKPSATKeyboardView.h"
|
||||
|
||||
|
||||
@interface ORKPSATStepViewController () <ORKPSATKeyboardViewDelegate>
|
||||
|
||||
@property (nonatomic, strong) NSMutableArray *samples;
|
||||
@property (nonatomic, strong) ORKPSATContentView *psatContentView;
|
||||
@property (nonatomic, strong) NSArray *digits;
|
||||
@property (nonatomic, assign) NSUInteger currentDigitIndex;
|
||||
@property (nonatomic, assign) NSInteger currentAnswer;
|
||||
@property (nonatomic, strong) ORKActiveStepTimer *clearDigitsTimer;
|
||||
@property (nonatomic, assign) NSTimeInterval answerStart;
|
||||
@property (nonatomic, assign) NSTimeInterval answerEnd;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ORKPSATStepViewController
|
||||
|
||||
- (instancetype)initWithStep:(ORKStep *)step {
|
||||
self = [super initWithStep:step];
|
||||
|
||||
if (self) {
|
||||
self.suspendIfInactive = YES;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (ORKPSATStep *)psatStep {
|
||||
return (ORKPSATStep *)self.step;
|
||||
}
|
||||
|
||||
- (NSArray *)arrayWithPSATDigits {
|
||||
NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:[self psatStep].seriesLength + 1];
|
||||
NSUInteger digit = 0;
|
||||
for (NSUInteger i = 0; i < [self psatStep].seriesLength + 1; i++) {
|
||||
do
|
||||
{
|
||||
digit = (arc4random() % (9)) + 1;
|
||||
} while (digit == ((NSNumber *)[array lastObject]).integerValue);
|
||||
[array addObject:@(digit)];
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
- (void)initializeInternalButtonItems {
|
||||
[super initializeInternalButtonItems];
|
||||
|
||||
// Don't show buttons
|
||||
self.internalContinueButtonItem = nil;
|
||||
self.internalDoneButtonItem = nil;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
self.activeStepView.stepViewFillsAvailableSpace = YES;
|
||||
self.psatContentView = [[ORKPSATContentView alloc] initWithPresentationMode:[self psatStep].presentationMode];
|
||||
self.psatContentView.keyboardView.delegate = self;
|
||||
[self.psatContentView setEnabled:NO];
|
||||
self.activeStepView.activeCustomView = self.psatContentView;
|
||||
|
||||
self.timerUpdateInterval = [self psatStep].interStimulusInterval;
|
||||
}
|
||||
|
||||
- (ORKStepResult *)result {
|
||||
|
||||
ORKStepResult *sResult = [super result];
|
||||
|
||||
NSMutableArray *results = [NSMutableArray arrayWithArray:sResult.results];
|
||||
|
||||
ORKPSATResult *PSATResult = [[ORKPSATResult alloc] initWithIdentifier:(NSString *__nonnull)self.step.identifier];
|
||||
PSATResult.presentationMode = [self psatStep].presentationMode;
|
||||
PSATResult.interStimulusInterval = [self psatStep].interStimulusInterval;
|
||||
if ([self psatStep].presentationMode & ORKPSATPresentationModeVisual) {
|
||||
PSATResult.stimulusDuration = [self psatStep].stimulusDuration;
|
||||
} else {
|
||||
PSATResult.stimulusDuration = 0.0;
|
||||
}
|
||||
PSATResult.length = [self psatStep].seriesLength;
|
||||
PSATResult.initialDigit = [(NSNumber *)[self.digits objectAtIndex:0] integerValue];
|
||||
NSInteger totalCorrect = 0;
|
||||
BOOL previousAnswerCorrect = NO;
|
||||
NSInteger totalDyad = 0;
|
||||
CGFloat totalTime = 0.0;
|
||||
for (ORKPSATSample *sample in self.samples) {
|
||||
totalTime += sample.time;
|
||||
if (sample.isCorrect) {
|
||||
totalCorrect++;
|
||||
if (previousAnswerCorrect) {
|
||||
totalDyad++;
|
||||
}
|
||||
}
|
||||
previousAnswerCorrect = sample.isCorrect;
|
||||
}
|
||||
PSATResult.totalCorrect = totalCorrect;
|
||||
PSATResult.totalTime = totalTime;
|
||||
PSATResult.totalDyad = totalDyad;
|
||||
PSATResult.samples = self.samples;
|
||||
|
||||
[results addObject:PSATResult];
|
||||
|
||||
sResult.results = [results copy];
|
||||
|
||||
return sResult;
|
||||
}
|
||||
|
||||
- (void)start {
|
||||
self.digits = [self arrayWithPSATDigits];
|
||||
self.currentDigitIndex = 0;
|
||||
[self.psatContentView setAddition:self.currentDigitIndex forTotal:[self psatStep].seriesLength withDigit:[self.digits objectAtIndex:self.currentDigitIndex]];
|
||||
[self.psatContentView setProgress:0.001 animated:NO];
|
||||
self.currentAnswer = -1;
|
||||
self.samples = [NSMutableArray array];
|
||||
|
||||
if ([self psatStep].presentationMode & ORKPSATPresentationModeVisual &&
|
||||
([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;
|
||||
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;
|
||||
[strongSelf clearDigitsTimerFired];
|
||||
}];
|
||||
[self.clearDigitsTimer resume];
|
||||
}
|
||||
|
||||
[super start];
|
||||
}
|
||||
|
||||
- (void)suspend {
|
||||
[self.clearDigitsTimer pause];
|
||||
[super suspend];
|
||||
}
|
||||
|
||||
- (void)resume {
|
||||
[self.clearDigitsTimer resume];
|
||||
[super resume];
|
||||
}
|
||||
|
||||
- (void)finish {
|
||||
[self.clearDigitsTimer reset];
|
||||
self.clearDigitsTimer = nil;
|
||||
[super finish];
|
||||
}
|
||||
|
||||
- (void)countDownTimerFired:(ORKActiveStepTimer *)timer finished:(BOOL)finished {
|
||||
if (self.currentDigitIndex == 0) {
|
||||
[self.psatContentView setEnabled:YES];
|
||||
[self.activeStepView updateTitle:ORKLocalizedString(@"PSAT_INSTRUCTION", nil) text:nil];
|
||||
} else {
|
||||
[self saveSample];
|
||||
}
|
||||
|
||||
self.currentDigitIndex++;
|
||||
self.answerStart = CACurrentMediaTime();
|
||||
self.answerEnd = 0;
|
||||
|
||||
if (self.currentDigitIndex <= [self psatStep].seriesLength) {
|
||||
[self.psatContentView setAddition:self.currentDigitIndex forTotal:[self psatStep].seriesLength withDigit:[self.digits objectAtIndex:self.currentDigitIndex]];
|
||||
}
|
||||
|
||||
self.currentAnswer = -1;
|
||||
|
||||
CGFloat progress = finished ? 1 : (timer.runtime / timer.duration);
|
||||
[self.psatContentView setProgress:progress animated:YES];
|
||||
|
||||
[super countDownTimerFired:timer finished:finished];
|
||||
}
|
||||
|
||||
- (void)clearDigitsTimerFired {
|
||||
[self.psatContentView setAddition:self.currentDigitIndex forTotal:[self psatStep].seriesLength withDigit:@(-1)];
|
||||
}
|
||||
|
||||
- (void)saveSample {
|
||||
ORKPSATSample *sample = [[ORKPSATSample alloc] init];
|
||||
NSInteger previousDigit = [(NSNumber *)[self.digits objectAtIndex:self.currentDigitIndex - 1] integerValue];
|
||||
NSInteger currentDigit = [(NSNumber *)[self.digits objectAtIndex:self.currentDigitIndex] integerValue];
|
||||
sample.correct = previousDigit + currentDigit == self.currentAnswer ? YES : NO;
|
||||
sample.digit = currentDigit;
|
||||
sample.answer = self.currentAnswer;
|
||||
sample.time = self.answerEnd == 0 ? [self psatStep].interStimulusInterval : self.answerEnd - self.answerStart;
|
||||
|
||||
[self.samples addObject:sample];
|
||||
}
|
||||
|
||||
#pragma mark - keyboard view delegate
|
||||
|
||||
- (void)keyboardView:(ORKPSATKeyboardView *)keyboardView didSelectAnswer:(NSInteger)answer {
|
||||
self.currentAnswer = answer;
|
||||
self.answerEnd = CACurrentMediaTime();
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -28,8 +28,10 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import <ResearchKit/ORKRecorder.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class ORKPedometerRecorder;
|
||||
@@ -37,11 +39,11 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@protocol ORKPedometerRecorderDelegate <ORKRecorderDelegate>
|
||||
|
||||
@optional
|
||||
|
||||
- (void)pedometerRecorderDidUpdate:(ORKPedometerRecorder *)pedometerRecorder;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
A recorder that requests and collects device motion data from CoreMotion at a fixed frequency.
|
||||
|
||||
@@ -52,13 +54,24 @@ ORK_CLASS_AVAILABLE
|
||||
@interface ORKPedometerRecorder : ORKRecorder
|
||||
|
||||
@property (nonatomic, readonly, nullable) NSDate *lastUpdateDate;
|
||||
|
||||
@property (nonatomic, readonly) NSInteger totalNumberOfSteps;
|
||||
|
||||
// Negative if an invalid value.
|
||||
@property (nonatomic, readonly) NSInteger totalDistance;
|
||||
|
||||
- (instancetype)initWithStep:(ORKStep *)step
|
||||
outputDirectory:(NSURL *)outputDirectory NS_DESIGNATED_INITIALIZER;
|
||||
/**
|
||||
Returns an initialized pedometer recorder.
|
||||
|
||||
@param identifier The unique identifier of the recorder (assigned by the recorder configuration).
|
||||
@param step The step that requested this recorder.
|
||||
@param outputDirectory The directory in which the pedometer data should be stored.
|
||||
|
||||
@return An initialized pedometer recorder.
|
||||
*/
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier
|
||||
step:(nullable ORKStep *)step
|
||||
outputDirectory:(nullable NSURL *)outputDirectory NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -28,49 +28,43 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKPedometerRecorder.h"
|
||||
#import "ORKDataLogger.h"
|
||||
#import "CMPedometerData+ORKJSONDictionary.h"
|
||||
#import "ORKRecorder_Internal.h"
|
||||
#import "ORKRecorder_Private.h"
|
||||
|
||||
@interface ORKPedometerRecorder()
|
||||
{
|
||||
|
||||
@interface ORKPedometerRecorder () {
|
||||
ORKDataLogger *_logger;
|
||||
BOOL _isRecording;
|
||||
}
|
||||
|
||||
@property (nonatomic, strong) CMPedometer *pedometer;
|
||||
@property (nonatomic, strong) NSError *recordingError;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKPedometerRecorder
|
||||
|
||||
|
||||
- (instancetype)initWithStep:(ORKStep *)step
|
||||
outputDirectory:(NSURL *)outputDirectory
|
||||
{
|
||||
self = [super initWithStep:step
|
||||
outputDirectory:(NSURL *)outputDirectory];
|
||||
if (self)
|
||||
{
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier
|
||||
step:(ORKStep *)step
|
||||
outputDirectory:(NSURL *)outputDirectory {
|
||||
self = [super initWithIdentifier:identifier
|
||||
step:step
|
||||
outputDirectory:outputDirectory];
|
||||
if (self) {
|
||||
self.continuesInBackground = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
- (void)dealloc {
|
||||
[_logger finishCurrentLog];
|
||||
}
|
||||
|
||||
|
||||
|
||||
- (void)updateStatisticsWithData:(CMPedometerData *)pedometerData {
|
||||
|
||||
_lastUpdateDate = pedometerData.endDate;
|
||||
_totalNumberOfSteps = [pedometerData.numberOfSteps integerValue];
|
||||
if (pedometerData.distance) {
|
||||
@@ -90,7 +84,6 @@
|
||||
}
|
||||
|
||||
- (void)start {
|
||||
|
||||
[super start];
|
||||
|
||||
_lastUpdateDate = nil;
|
||||
@@ -108,8 +101,7 @@
|
||||
|
||||
self.pedometer = [self createPedometer];
|
||||
|
||||
if (! [[self.pedometer class] isStepCountingAvailable])
|
||||
{
|
||||
if (! [[self.pedometer class] isStepCountingAvailable]) {
|
||||
[self finishRecordingWithError:[NSError errorWithDomain:NSCocoaErrorDomain
|
||||
code:NSFeatureUnsupportedError
|
||||
userInfo:@{@"recorder" : self}]];
|
||||
@@ -121,16 +113,14 @@
|
||||
[self.pedometer startPedometerUpdatesFromDate:[NSDate date] withHandler:^(CMPedometerData *pedometerData, NSError *error) {
|
||||
|
||||
BOOL success = NO;
|
||||
if (pedometerData)
|
||||
{
|
||||
if (pedometerData) {
|
||||
success = [_logger append:[pedometerData ork_JSONDictionary] error:&error];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
__typeof(self) strongSelf = weakSelf;
|
||||
[strongSelf updateStatisticsWithData:pedometerData];
|
||||
});
|
||||
}
|
||||
if (!success || error)
|
||||
{
|
||||
if (!success || error) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
__typeof(self) strongSelf = weakSelf;
|
||||
[strongSelf finishRecordingWithError:error];
|
||||
@@ -139,13 +129,10 @@
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
- (NSString *)recorderType
|
||||
{
|
||||
- (NSString *)recorderType {
|
||||
return @"pedometer";
|
||||
}
|
||||
|
||||
|
||||
- (void)stop {
|
||||
[self doStopRecording];
|
||||
[_logger finishCurrentLog];
|
||||
@@ -154,17 +141,15 @@
|
||||
__block NSURL *fileUrl = nil;
|
||||
[_logger enumerateLogs:^(NSURL *logFileUrl, BOOL *stop) {
|
||||
fileUrl = logFileUrl;
|
||||
} error:&error];
|
||||
|
||||
|
||||
}
|
||||
error:&error];
|
||||
|
||||
[self reportFileResultWithFile:fileUrl error:error];
|
||||
|
||||
[super stop];
|
||||
}
|
||||
|
||||
- (void)doStopRecording
|
||||
{
|
||||
- (void)doStopRecording {
|
||||
if (_isRecording) {
|
||||
[self.pedometer stopPedometerUpdates];
|
||||
_isRecording = NO;
|
||||
@@ -172,8 +157,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)finishRecordingWithError:(NSError *)error
|
||||
{
|
||||
- (void)finishRecordingWithError:(NSError *)error {
|
||||
[self doStopRecording];
|
||||
[super finishRecordingWithError:error];
|
||||
}
|
||||
@@ -186,8 +170,7 @@
|
||||
return @"application/json";
|
||||
}
|
||||
|
||||
- (void)reset
|
||||
{
|
||||
- (void)reset {
|
||||
[super reset];
|
||||
|
||||
_logger = nil;
|
||||
@@ -196,47 +179,40 @@
|
||||
@end
|
||||
|
||||
|
||||
@interface ORKPedometerRecorderConfiguration()
|
||||
@interface ORKPedometerRecorderConfiguration ()
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKPedometerRecorderConfiguration
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super ork_init];
|
||||
return self;
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
||||
return [super initWithIdentifier:identifier];
|
||||
}
|
||||
|
||||
- (ORKRecorder *)recorderForStep:(ORKStep *)step outputDirectory:(NSURL *)outputDirectory
|
||||
{
|
||||
return [[ORKPedometerRecorder alloc] initWithStep:step
|
||||
outputDirectory:outputDirectory];
|
||||
- (ORKRecorder *)recorderForStep:(ORKStep *)step outputDirectory:(NSURL *)outputDirectory {
|
||||
return [[ORKPedometerRecorder alloc] initWithIdentifier:self.identifier
|
||||
step:step
|
||||
outputDirectory:outputDirectory];
|
||||
}
|
||||
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder
|
||||
{
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [super initWithCoder:aDecoder];
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (BOOL)supportsSecureCoding
|
||||
{
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
- (BOOL)isEqual:(id)object {
|
||||
BOOL isParentSame = [super isEqual:object];
|
||||
|
||||
return (isParentSame) ;
|
||||
return (isParentSame);
|
||||
}
|
||||
|
||||
|
||||
|
||||
- (ORKPermissionMask)requestedPermissionMask {
|
||||
return ORKPermissionCoreMotionActivity;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
Copyright (c) 2015, James Cox. 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 "ORKCustomStepView_Internal.h"
|
||||
|
||||
|
||||
@interface ORKReactionTimeContentView : ORKActiveStepCustomView
|
||||
|
||||
- (void)setStimulusHidden:(BOOL)hidden;
|
||||
|
||||
- (void)startSuccessAnimationWithDuration:(NSTimeInterval)duration completion:(nullable void (^)(void))completion;
|
||||
|
||||
- (void)startFailureAnimationWithDuration:(NSTimeInterval)duration completion:(nullable void (^)(void))completion;
|
||||
|
||||
- (void)resetAfterDelay:(NSTimeInterval)delay completion:(nullable void (^)(void))completion;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
Copyright (c) 2015, James Cox. 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 "ORKReactionTimeContentView.h"
|
||||
#import "ORKReactionTimeStimulusView.h"
|
||||
#import "ORKNavigationContainerView.h"
|
||||
|
||||
|
||||
@interface ORKReactionTimeContentView ()
|
||||
|
||||
@property (nonatomic, strong) ORKReactionTimeStimulusView *stimulusView;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKReactionTimeContentView
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
self.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[self addStimulusView];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)startSuccessAnimationWithDuration:(NSTimeInterval)duration completion:(void (^)(void))completion {
|
||||
[_stimulusView startSuccessAnimationWithDuration:duration completion:completion];
|
||||
}
|
||||
|
||||
- (void)startFailureAnimationWithDuration:(NSTimeInterval)duration completion:(void (^)(void))completion {
|
||||
[_stimulusView startFailureAnimationWithDuration:duration completion:completion];
|
||||
}
|
||||
|
||||
- (void)resetAfterDelay:(NSTimeInterval)delay completion:(nullable void (^)(void))completion {
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
_stimulusView.hidden = YES;
|
||||
if (completion) {
|
||||
completion();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)addStimulusView {
|
||||
if (!_stimulusView) {
|
||||
_stimulusView = [ORKReactionTimeStimulusView new];
|
||||
_stimulusView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
_stimulusView.backgroundColor = self.tintColor;
|
||||
[self addSubview:_stimulusView];
|
||||
[self setUpStimulusViewConstraints];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setStimulusHidden:(BOOL)hidden {
|
||||
_stimulusView.hidden = hidden;
|
||||
}
|
||||
|
||||
- (void)setUpStimulusViewConstraints {
|
||||
NSMutableArray *constraints = [NSMutableArray array];
|
||||
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_stimulusView
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_stimulusView
|
||||
attribute:NSLayoutAttributeTop
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeTop
|
||||
multiplier:1.0
|
||||
constant:8.0]];
|
||||
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_stimulusView]-(>=0)-|"
|
||||
options:NSLayoutFormatAlignAllCenterX
|
||||
metrics:nil
|
||||
views:NSDictionaryOfVariableBindings(_stimulusView)]];
|
||||
|
||||
[NSLayoutConstraint activateConstraints:constraints];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
Copyright (c) 2015, James Cox. 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 <AudioToolbox/AudioServices.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKReactionTimeStep : ORKActiveStep
|
||||
|
||||
@property (nonatomic, assign) NSTimeInterval maximumStimulusInterval;
|
||||
|
||||
@property (nonatomic, assign) NSTimeInterval minimumStimulusInterval;
|
||||
|
||||
@property (nonatomic, assign) NSTimeInterval timeout;
|
||||
|
||||
@property (nonatomic, assign) NSInteger numberOfAttempts;
|
||||
|
||||
@property (nonatomic, assign) double thresholdAcceleration;
|
||||
|
||||
@property (nonatomic, assign) SystemSoundID successSound;
|
||||
|
||||
@property (nonatomic, assign) SystemSoundID timeoutSound;
|
||||
|
||||
@property (nonatomic, assign) SystemSoundID failureSound;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
Copyright (c) 2015, James Cox. 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 "ORKReactionTimeStep.h"
|
||||
#import "ORKReactionTimeViewController.h"
|
||||
#import "ORKHelpers.h"
|
||||
|
||||
|
||||
@implementation ORKReactionTimeStep
|
||||
|
||||
+ (Class)stepViewControllerClass {
|
||||
return [ORKReactionTimeViewController class];
|
||||
}
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
||||
self = [super initWithIdentifier:identifier];
|
||||
self.shouldContinueOnFinish = YES;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)copyWithZone:(NSZone *)zone {
|
||||
ORKReactionTimeStep *step = [super copyWithZone:zone];
|
||||
step.maximumStimulusInterval = self.maximumStimulusInterval;
|
||||
step.minimumStimulusInterval = self.minimumStimulusInterval;
|
||||
step.thresholdAcceleration = self.thresholdAcceleration;
|
||||
step.timeout = self.timeout;
|
||||
step.numberOfAttempts = self.numberOfAttempts;
|
||||
step.successSound = self.successSound;
|
||||
step.timeoutSound = self.timeoutSound;
|
||||
step.failureSound = self.failureSound;
|
||||
return step;
|
||||
}
|
||||
|
||||
- (void)validateParameters {
|
||||
[super validateParameters];
|
||||
|
||||
if (self.minimumStimulusInterval <= 0) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException
|
||||
reason:@"minimumStimulusInterval must be greater than zero"
|
||||
userInfo:nil];
|
||||
}
|
||||
if (self.maximumStimulusInterval < self.minimumStimulusInterval) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException
|
||||
reason:@"maximumStimulusInterval can not be less than minimumStimulusInterval"
|
||||
userInfo:nil];
|
||||
}
|
||||
if (self.thresholdAcceleration <= 0) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException
|
||||
reason:@"thresholdAcceleration must be greater than zero"
|
||||
userInfo:nil];
|
||||
}
|
||||
if (self.timeout <= 0) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException
|
||||
reason:@"timeout must be greater than zero"
|
||||
userInfo:nil];
|
||||
}
|
||||
if (self.numberOfAttempts <= 0) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException
|
||||
reason:@"numberOfAttempts must be greater than zero"
|
||||
userInfo:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [super initWithCoder:aDecoder];
|
||||
if (self) {
|
||||
ORK_DECODE_DOUBLE(aDecoder, maximumStimulusInterval);
|
||||
ORK_DECODE_DOUBLE(aDecoder, minimumStimulusInterval);
|
||||
ORK_DECODE_DOUBLE(aDecoder, thresholdAcceleration);
|
||||
ORK_DECODE_DOUBLE(aDecoder, timeout);
|
||||
ORK_DECODE_UINT32(aDecoder, successSound);
|
||||
ORK_DECODE_UINT32(aDecoder, timeoutSound);
|
||||
ORK_DECODE_UINT32(aDecoder, failureSound);
|
||||
ORK_DECODE_INTEGER(aDecoder, numberOfAttempts);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
[super encodeWithCoder:aCoder];
|
||||
ORK_ENCODE_DOUBLE(aCoder, maximumStimulusInterval);
|
||||
ORK_ENCODE_DOUBLE(aCoder, minimumStimulusInterval);
|
||||
ORK_ENCODE_DOUBLE(aCoder, thresholdAcceleration);
|
||||
ORK_ENCODE_DOUBLE(aCoder, timeout);
|
||||
ORK_ENCODE_UINT32(aCoder, successSound);
|
||||
ORK_ENCODE_UINT32(aCoder, timeoutSound);
|
||||
ORK_ENCODE_UINT32(aCoder, failureSound);
|
||||
ORK_ENCODE_INTEGER(aCoder, numberOfAttempts);
|
||||
}
|
||||
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object {
|
||||
BOOL isParentSame = [super isEqual:object];
|
||||
|
||||
__typeof(self) castObject = object;
|
||||
return (isParentSame &&
|
||||
(self.maximumStimulusInterval == castObject.maximumStimulusInterval) &&
|
||||
(self.minimumStimulusInterval == castObject.minimumStimulusInterval) &&
|
||||
(self.thresholdAcceleration == castObject.thresholdAcceleration) &&
|
||||
(self.timeout == castObject.timeout) &&
|
||||
(self.successSound == castObject.successSound) &&
|
||||
(self.timeoutSound == castObject.timeoutSound) &&
|
||||
(self.failureSound == castObject.failureSound) &&
|
||||
(self.numberOfAttempts == castObject.numberOfAttempts));
|
||||
}
|
||||
|
||||
- (BOOL)allowsBackNavigation {
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)startsFinished {
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
Copyright (c) 2015, James Cox. 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 "ORKCustomStepView_Internal.h"
|
||||
|
||||
|
||||
@interface ORKReactionTimeStimulusView : UIView
|
||||
|
||||
- (void)reset;
|
||||
|
||||
- (void)startSuccessAnimationWithDuration:(NSTimeInterval)duration completion:(nullable void(^)(void))completion;
|
||||
|
||||
- (void)startFailureAnimationWithDuration:(NSTimeInterval)duration completion:(nullable void(^)(void))completion;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
Copyright (c) 2015, James Cox. 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 "ORKReactionTimeStimulusView.h"
|
||||
|
||||
|
||||
@implementation ORKReactionTimeStimulusView {
|
||||
CAShapeLayer *_tickLayer;
|
||||
CAShapeLayer *_crossLayer;
|
||||
}
|
||||
|
||||
static const CGFloat RoundReactionTimeViewDiameter = 122;
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.layer.cornerRadius = RoundReactionTimeViewDiameter * 0.5;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CGSize)intrinsicContentSize {
|
||||
return CGSizeMake(RoundReactionTimeViewDiameter, RoundReactionTimeViewDiameter);
|
||||
}
|
||||
|
||||
- (void)reset {
|
||||
[_tickLayer removeFromSuperlayer];
|
||||
[_crossLayer removeFromSuperlayer];
|
||||
_tickLayer = nil;
|
||||
_crossLayer = nil;
|
||||
self.layer.backgroundColor = self.tintColor.CGColor;
|
||||
}
|
||||
|
||||
- (void)startSuccessAnimationWithDuration:(NSTimeInterval)duration completion:(void(^)(void))completion {
|
||||
if (self.hidden) {
|
||||
if (completion) {
|
||||
completion();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
[self addTickLayer];
|
||||
[CATransaction begin];
|
||||
[CATransaction setCompletionBlock:completion];
|
||||
CAMediaTimingFunction *timing = [[CAMediaTimingFunction alloc] initWithControlPoints:0.180739998817444 :0 :0.577960014343262 :0.918200016021729];
|
||||
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
|
||||
[animation setTimingFunction:timing];
|
||||
animation.removedOnCompletion = NO;
|
||||
[animation setFillMode:kCAFillModeForwards];
|
||||
animation.fromValue = @(0);
|
||||
animation.toValue = @(1);
|
||||
animation.duration = duration;
|
||||
[_tickLayer addAnimation:animation forKey:@"strokeEnd"];
|
||||
[CATransaction commit];
|
||||
}
|
||||
|
||||
- (void)startFailureAnimationWithDuration:(NSTimeInterval)duration completion:(void(^)(void))completion {
|
||||
self.hidden = NO;
|
||||
|
||||
self.layer.backgroundColor = [UIColor clearColor].CGColor;
|
||||
[self addCrossLayer];
|
||||
[CATransaction begin];
|
||||
[CATransaction setCompletionBlock:completion];
|
||||
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
|
||||
[animation setFillMode:kCAFillModeForwards];
|
||||
animation.fromValue = @([(CAShapeLayer *)[_crossLayer presentationLayer] strokeEnd]);
|
||||
animation.toValue = @(1);
|
||||
animation.duration = duration;
|
||||
_crossLayer.strokeEnd = 1;
|
||||
[_crossLayer addAnimation:animation forKey:@"strokeEnd"];
|
||||
[CATransaction commit];
|
||||
}
|
||||
|
||||
- (void)setHidden:(BOOL)hidden {
|
||||
[self reset];
|
||||
[super setHidden:hidden];
|
||||
}
|
||||
|
||||
- (void)addCrossLayer {
|
||||
_crossLayer = [self lineDrawingLayer];
|
||||
_crossLayer.strokeColor = [UIColor redColor].CGColor;
|
||||
_crossLayer.path = [self crossPath];
|
||||
[self.layer addSublayer:_crossLayer];
|
||||
}
|
||||
|
||||
- (void)addTickLayer {
|
||||
_tickLayer = [self lineDrawingLayer];
|
||||
_tickLayer.strokeColor = [UIColor whiteColor].CGColor;
|
||||
_tickLayer.path = [self tickPath];
|
||||
[self.layer addSublayer:_tickLayer];
|
||||
}
|
||||
|
||||
- (CGPathRef)concealPath:(CGFloat)radius {
|
||||
return [[UIBezierPath bezierPathWithArcCenter:CGPointMake(radius, radius)
|
||||
radius:radius / 2
|
||||
startAngle:M_PI + M_PI_2
|
||||
endAngle:-M_PI_2
|
||||
clockwise:NO] CGPath];
|
||||
}
|
||||
|
||||
- (CGPathRef)tickPath {
|
||||
UIBezierPath *path = [self linePath];
|
||||
[path moveToPoint:(CGPoint){37,65}];
|
||||
[path addLineToPoint:(CGPoint){50,78}];
|
||||
[path addLineToPoint:(CGPoint){87,42}];
|
||||
return path.CGPath;
|
||||
}
|
||||
|
||||
- (CGPathRef)crossPath {
|
||||
UIBezierPath *path = [self linePath];
|
||||
[path moveToPoint:(CGPoint){45,78}];
|
||||
[path addLineToPoint:(CGPoint){82,42}];
|
||||
[path moveToPoint:(CGPoint){45,42}];
|
||||
[path addLineToPoint:(CGPoint){82,78}];
|
||||
return path.CGPath;
|
||||
}
|
||||
|
||||
- (UIBezierPath *)linePath {
|
||||
UIBezierPath *path = [UIBezierPath new];
|
||||
path.lineCapStyle = kCGLineCapRound;
|
||||
path.lineWidth = 5;
|
||||
return path;
|
||||
}
|
||||
|
||||
- (CAShapeLayer *)lineDrawingLayer {
|
||||
CAShapeLayer *shapeLayer = [CAShapeLayer new];
|
||||
shapeLayer.strokeEnd = 0;
|
||||
shapeLayer.lineWidth = 5;
|
||||
shapeLayer.lineCap = kCALineCapRound;
|
||||
shapeLayer.lineJoin = kCALineJoinRound;
|
||||
shapeLayer.frame = self.layer.bounds;
|
||||
shapeLayer.backgroundColor = [UIColor clearColor].CGColor;
|
||||
shapeLayer.fillColor = nil;
|
||||
return shapeLayer;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
Copyright (c) 2015, James Cox. 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>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKReactionTimeViewController : ORKActiveStepViewController
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,229 @@
|
||||
/*
|
||||
Copyright (c) 2015, James Cox. 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 "ORKReactionTimeViewController.h"
|
||||
#import "ORKActiveStepViewController_Internal.h"
|
||||
#import "ORKActiveStepView.h"
|
||||
#import "ORKReactionTimeContentView.h"
|
||||
#import "ORKReactionTimeStep.h"
|
||||
#import <CoreMotion/CMDeviceMotion.h>
|
||||
#import <AudioToolbox/AudioServices.h>
|
||||
|
||||
|
||||
@implementation ORKReactionTimeViewController {
|
||||
ORKReactionTimeContentView *_reactionTimeContentView;
|
||||
NSMutableArray *_results;
|
||||
NSTimer *_stimulusTimer;
|
||||
NSTimer *_timeoutTimer;
|
||||
NSTimeInterval _stimulusTimestamp;
|
||||
BOOL _validResult;
|
||||
BOOL _timedOut;
|
||||
BOOL _shouldIndicateFailure;
|
||||
}
|
||||
|
||||
static const NSTimeInterval OutcomeAnimationDuration = 0.3;
|
||||
|
||||
#pragma mark - UIViewController
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
// Do any additional setup after loading the view.
|
||||
[self configureTitle];
|
||||
_results = [NSMutableArray new];
|
||||
_reactionTimeContentView = [ORKReactionTimeContentView new];
|
||||
self.activeStepView.activeCustomView = _reactionTimeContentView;
|
||||
self.activeStepView.stepViewFillsAvailableSpace = YES;
|
||||
[_reactionTimeContentView setStimulusHidden:YES];
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
[super viewDidAppear:animated];
|
||||
[self start];
|
||||
_shouldIndicateFailure = YES;
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
[super viewWillDisappear:animated];
|
||||
_shouldIndicateFailure = NO;
|
||||
}
|
||||
|
||||
#pragma mark - ORKActiveStepViewController
|
||||
|
||||
- (void)start {
|
||||
[super start];
|
||||
[self startStimulusTimer];
|
||||
|
||||
}
|
||||
|
||||
#if TARGET_IPHONE_SIMULATOR
|
||||
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event {
|
||||
if (event.type == UIEventSubtypeMotionShake) {
|
||||
if (_validResult) {
|
||||
ORKReactionTimeResult *reactionTimeResult = [[ORKReactionTimeResult alloc] initWithIdentifier:self.step.identifier];
|
||||
reactionTimeResult.timestamp = _stimulusTimestamp;
|
||||
[_results addObject:reactionTimeResult];
|
||||
}
|
||||
[self attemptDidFinish];
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
- (ORKStepResult *)result {
|
||||
ORKStepResult *stepResult = [super result];
|
||||
stepResult.results = _results;
|
||||
return stepResult;
|
||||
}
|
||||
|
||||
- (void)applicationWillResignActive:(NSNotification *)notification {
|
||||
[super applicationWillResignActive:notification];
|
||||
_validResult = NO;
|
||||
[_stimulusTimer invalidate];
|
||||
[_timeoutTimer invalidate];
|
||||
}
|
||||
|
||||
- (void)applicationDidBecomeActive:(NSNotification *)notification {
|
||||
[super applicationDidBecomeActive:notification];
|
||||
[self resetAfterDelay:0];
|
||||
}
|
||||
|
||||
#pragma mark - ORKRecorderDelegate
|
||||
|
||||
- (void)recorder:(ORKRecorder *)recorder didCompleteWithResult:(ORKResult *)result {
|
||||
if (_validResult) {
|
||||
ORKReactionTimeResult *reactionTimeResult = [[ORKReactionTimeResult alloc] initWithIdentifier:self.step.identifier];
|
||||
reactionTimeResult.timestamp = _stimulusTimestamp;
|
||||
reactionTimeResult.fileResult = (ORKFileResult *)result;
|
||||
[_results addObject:reactionTimeResult];
|
||||
}
|
||||
[self attemptDidFinish];
|
||||
}
|
||||
|
||||
#pragma mark - ORKDeviceMotionRecorderDelegate
|
||||
|
||||
- (void)deviceMotionRecorderDidUpdateWithMotion:(CMDeviceMotion *)motion {
|
||||
CMAcceleration v = motion.userAcceleration;
|
||||
double vectorMagnitude = sqrt(((v.x * v.x) + (v.y * v.y) + (v.z * v.z)));
|
||||
if (vectorMagnitude > [self reactionTimeStep].thresholdAcceleration) {
|
||||
[self stopRecorders];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - ORKReactionTimeStepViewController
|
||||
|
||||
- (ORKReactionTimeStep *)reactionTimeStep {
|
||||
return (ORKReactionTimeStep *)self.step;
|
||||
}
|
||||
|
||||
- (void)configureTitle {
|
||||
NSString *format = ORKLocalizedString(@"REACTION_TIME_TASK_ATTEMPTS_FORMAT", nil);
|
||||
NSString *text = [NSString stringWithFormat:format, ORKLocalizedStringFromNumber(@(_results.count + 1)), ORKLocalizedStringFromNumber(@([self reactionTimeStep].numberOfAttempts))];
|
||||
[self.activeStepView updateTitle:ORKLocalizedString(@"REACTION_TIME_TASK_ACTIVE_STEP_TITLE", nil) text:text];
|
||||
}
|
||||
|
||||
- (void)attemptDidFinish {
|
||||
void (^completion)(void) = ^{
|
||||
if ([_results count] == [self reactionTimeStep].numberOfAttempts) {
|
||||
[self finish];
|
||||
} else {
|
||||
[self resetAfterDelay:2];
|
||||
}
|
||||
};
|
||||
if (_validResult) {
|
||||
[self indicateSuccess:completion];
|
||||
} else {
|
||||
[self indicateFailure:completion];
|
||||
}
|
||||
_validResult = NO;
|
||||
_timedOut = NO;
|
||||
[_stimulusTimer invalidate];
|
||||
[_timeoutTimer invalidate];
|
||||
}
|
||||
|
||||
- (void)indicateSuccess:(void(^)(void))completion {
|
||||
[_reactionTimeContentView startSuccessAnimationWithDuration:OutcomeAnimationDuration completion:completion];
|
||||
AudioServicesPlaySystemSound([self reactionTimeStep].successSound);
|
||||
}
|
||||
|
||||
- (void)indicateFailure:(void(^)(void))completion {
|
||||
if (!_shouldIndicateFailure) {
|
||||
return;
|
||||
}
|
||||
[_reactionTimeContentView startFailureAnimationWithDuration:OutcomeAnimationDuration completion:completion];
|
||||
SystemSoundID sound = _timedOut ? [self reactionTimeStep].timeoutSound : [self reactionTimeStep].failureSound;
|
||||
AudioServicesPlayAlertSound(sound);
|
||||
}
|
||||
|
||||
- (void)resetAfterDelay:(NSTimeInterval)delay {
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
[_reactionTimeContentView resetAfterDelay:delay completion:^{
|
||||
[weakSelf configureTitle];
|
||||
[weakSelf start];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)startStimulusTimer {
|
||||
_stimulusTimer = [NSTimer scheduledTimerWithTimeInterval:[self stimulusInterval] target:self selector:@selector(stimulusTimerDidFire) userInfo:nil repeats:NO];
|
||||
}
|
||||
|
||||
- (void)stimulusTimerDidFire {
|
||||
_stimulusTimestamp = [NSProcessInfo processInfo].systemUptime;
|
||||
[_reactionTimeContentView setStimulusHidden:NO];
|
||||
_validResult = YES;
|
||||
[self startTimeoutTimer];
|
||||
}
|
||||
|
||||
- (void)startTimeoutTimer {
|
||||
NSTimeInterval timeout = [self reactionTimeStep].timeout;
|
||||
if (timeout > 0) {
|
||||
_timeoutTimer = [NSTimer scheduledTimerWithTimeInterval:timeout target:self selector:@selector(timeoutTimerDidFire) userInfo:nil repeats:NO];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)timeoutTimerDidFire {
|
||||
_validResult = NO;
|
||||
_timedOut = YES;
|
||||
[self stopRecorders];
|
||||
|
||||
#if TARGET_IPHONE_SIMULATOR
|
||||
// Device motion recorder won't work, so manually trigger didfinish
|
||||
[self attemptDidFinish];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (NSTimeInterval)stimulusInterval {
|
||||
ORKReactionTimeStep *step = [self reactionTimeStep];
|
||||
NSTimeInterval range = step.maximumStimulusInterval - step.minimumStimulusInterval;
|
||||
NSTimeInterval randomFactor = ((NSTimeInterval)rand() / RAND_MAX) * range;
|
||||
return randomFactor + step.minimumStimulusInterval;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
Copyright (c) 2015, Ricardo Sánchez-Sáez.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
@@ -34,6 +35,7 @@
|
||||
#import <HealthKit/HealthKit.h>
|
||||
#import <ResearchKit/ORKResult.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class ORKRecorder;
|
||||
@@ -57,21 +59,35 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKRecorderConfiguration : NSObject <NSSecureCoding>
|
||||
|
||||
/**
|
||||
The `init` method is unavailable outside the framework on `ORKRecorderConfiguration`,
|
||||
/*
|
||||
The `init` and `new` methods are unavailable outside the framework on `ORKRecorderConfiguration`,
|
||||
because it is an abstract class.
|
||||
|
||||
`ORKRecorderConfiguration` classes should be initialized with custom designated
|
||||
initializers on each subclass.
|
||||
*/
|
||||
+ (instancetype)new NS_UNAVAILABLE;
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
/**
|
||||
A short string that uniquely identifies the recorder configuration within the step.
|
||||
|
||||
The identifier is reproduced in the results of a recorder created from this configuration. In fact, the only way to link a result
|
||||
(an `ORKFileResult` object) to the recorder that generated it is to look at the value of
|
||||
`identifier`. To accurately identify recorder results, you need to ensure that recorder identifiers
|
||||
are unique within each step.
|
||||
|
||||
In some cases, it can be useful to link the recorder identifier to a unique identifier in a
|
||||
database; in other cases, it can make sense to make the identifier human
|
||||
readable.
|
||||
*/
|
||||
@property (nonatomic, copy, readonly) NSString *identifier;
|
||||
|
||||
/**
|
||||
Returns a recorder instance using this configuration.
|
||||
|
||||
@param step The step for which this recorder is being created.
|
||||
@param outputDirectory The directory in which all output file data should be written
|
||||
(if producing `ORKFileResult` instances).
|
||||
@param step The step for which this recorder is being created.
|
||||
@param outputDirectory The directory in which all output file data should be written (if producing `ORKFileResult` instances).
|
||||
|
||||
@return A configured recorder instance.
|
||||
*/
|
||||
@@ -88,7 +104,7 @@ ORK_CLASS_AVAILABLE
|
||||
If your recorder requires or would benefit from read access to HealthKit at
|
||||
runtime during the task, return the appropriate set of `HKSampleType` objects.
|
||||
*/
|
||||
- (nullable NSSet *)requestedHealthKitTypesForReading;
|
||||
- (nullable NSSet<HKObjectType *> *)requestedHealthKitTypesForReading;
|
||||
|
||||
@end
|
||||
|
||||
@@ -117,17 +133,25 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
This method is the designated initializer.
|
||||
|
||||
@param frequency The frequency of accelerometer data collection in samples per second (Hz).
|
||||
@param identifier The unique identifier of the recorder configuration.
|
||||
@param frequency The frequency of accelerometer data collection in samples per second (Hz).
|
||||
|
||||
@return An initialized accelerometer recorder configuration.
|
||||
*/
|
||||
- (instancetype)initWithFrequency:(double)frequency NS_DESIGNATED_INITIALIZER;
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier frequency:(double)frequency NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/**
|
||||
Returns a new accelerometer recorder configuration initialized from data in the given unarchiver.
|
||||
|
||||
@param aDecoder Coder from which to initialize the accelerometer recorder configuration.
|
||||
|
||||
@return A new accelerometer recorder configuration.
|
||||
*/
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
/**
|
||||
The `ORKAudioRecorderConfiguration` class represents a configuration that records
|
||||
audio data during an active step.
|
||||
@@ -159,10 +183,20 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
For information on the settings available for an audio recorder, see "AV Foundation Audio Settings Constants".
|
||||
|
||||
@param recorderSettings The settings for the recording session.
|
||||
@param identifier The unique identifier of the recorder configuration.
|
||||
@param recorderSettings The settings for the recording session.
|
||||
|
||||
@return An initialized audio recorder configuration.
|
||||
*/
|
||||
- (instancetype)initWithRecorderSettings:(NSDictionary *)recorderSettings NS_DESIGNATED_INITIALIZER;
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier recorderSettings:(NSDictionary *)recorderSettings NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/**
|
||||
Returns a new audio recorder configuration initialized from data in the given unarchiver.
|
||||
|
||||
@param aDecoder Coder from which to initialize the audio recorder configuration.
|
||||
|
||||
@return A new audio recorder configuration.
|
||||
*/
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@end
|
||||
@@ -196,17 +230,25 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
This method is the designated initializer.
|
||||
|
||||
@param frequency Motion data collection frequency in samples per second (Hz).
|
||||
@param identifier The unique identifier of the recorder configuration.
|
||||
@param frequency Motion data collection frequency in samples per second (Hz).
|
||||
|
||||
@return An initialized device motion recorder configuration.
|
||||
*/
|
||||
- (instancetype)initWithFrequency:(double)frequency NS_DESIGNATED_INITIALIZER;
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier frequency:(double)frequency NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/**
|
||||
Returns a new device motion recorder configuration initialized from data in the given unarchiver.
|
||||
|
||||
@param aDecoder Coder from which to initialize the device motion recorder configuration.
|
||||
|
||||
@return A new device motion recorder configuration.
|
||||
*/
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
/**
|
||||
The `ORKPedometerRecorderConfiguration` class represents a configuration
|
||||
that records pedometer data during an active step.
|
||||
@@ -228,17 +270,30 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
/**
|
||||
Returns an initialized pedometer recorder configuration.
|
||||
|
||||
This method is the designated initializer.
|
||||
|
||||
Note that the recorder instantiates a `CMPedometer` object, so no parameters are required.
|
||||
*/
|
||||
- (instancetype)init NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
The recorder instantiates a `CMPedometer` object, so no additional parameters besides
|
||||
the identifier are required.
|
||||
|
||||
This method is the designated initializer.
|
||||
|
||||
@param identifier The unique identifier of the recorder configuration.
|
||||
|
||||
@return An initialized pedometer recorder configuration.
|
||||
*/
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/**
|
||||
Returns a new pedometer recorder configuration initialized from data in the given unarchiver.
|
||||
|
||||
@param aDecoder Coder from which to initialize the pedometer recorder configuration.
|
||||
|
||||
@return A new pedometer recorder configuration.
|
||||
*/
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
The `ORKLocationRecorderConfiguration` class represents a configuration
|
||||
that records location data during an active step.
|
||||
@@ -255,6 +310,8 @@ ORK_CLASS_AVAILABLE
|
||||
To use a recorder, include its configuration in the `recorderConfigurations` property
|
||||
of an `ORKActiveStep` object, include that step in a task, and present it with
|
||||
a task view controller.
|
||||
|
||||
No additional parameters besides the identifier are required.
|
||||
*/
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKLocationRecorderConfiguration : ORKRecorderConfiguration
|
||||
@@ -263,10 +320,20 @@ ORK_CLASS_AVAILABLE
|
||||
Returns an initialized location recorder configuration.
|
||||
|
||||
This method is the designated initializer.
|
||||
|
||||
@param identifier The unique identifier of the recorder configuration.
|
||||
|
||||
No parameters are required.
|
||||
@return An initialized location recorder configuration.
|
||||
*/
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/**
|
||||
Returns a new location recorder configuration initialized from data in the given unarchiver.
|
||||
|
||||
@param aDecoder Coder from which to initialize the location recorder configuration.
|
||||
|
||||
@return A new location recorder configuration.
|
||||
*/
|
||||
- (instancetype)init NS_DESIGNATED_INITIALIZER;
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@end
|
||||
@@ -294,12 +361,21 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
This method is the designated initializer.
|
||||
|
||||
@param identifier The unique identifier of the recorder configuration.
|
||||
@param quantityType The quantity type that should be collected during the active task.
|
||||
@param unit The unit for the data that should be collected and serialized.
|
||||
|
||||
@return An initialized health quantity type recorder configuration.
|
||||
*/
|
||||
- (instancetype)initWithHealthQuantityType:(HKQuantityType *)quantityType unit:(HKUnit *)unit NS_DESIGNATED_INITIALIZER;
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier healthQuantityType:(HKQuantityType *)quantityType unit:(HKUnit *)unit NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/**
|
||||
Returns a new health quantity type recorder configuration initialized from data in the given unarchiver.
|
||||
|
||||
@param aDecoder Coder from which to initialize the health quantity type recorder configuration.
|
||||
|
||||
@return A new health quantity type recorder configuration.
|
||||
*/
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/**
|
||||
@@ -307,7 +383,6 @@ ORK_CLASS_AVAILABLE
|
||||
*/
|
||||
@property (nonatomic, readonly, copy) HKQuantityType *quantityType;
|
||||
|
||||
|
||||
/**
|
||||
The unit in which to serialize the data from HealthKit. (read-only)
|
||||
*/
|
||||
@@ -315,6 +390,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
The `ORKRecorderDelegate` protocol defines methods that the delegate of an `ORKRecorder` object should use to handle errors and log the
|
||||
completed results.
|
||||
@@ -346,6 +422,7 @@ need to implement it.
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
A recorder is the runtime companion to an `ORKRecorderConfiguration` object, and is
|
||||
usually generated by one.
|
||||
@@ -369,12 +446,27 @@ need to implement it.
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKRecorder : NSObject
|
||||
|
||||
+ (instancetype)new NS_UNAVAILABLE;
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
/// @name Configuration
|
||||
|
||||
@property (nonatomic, weak, nullable) id<ORKRecorderDelegate> delegate;
|
||||
|
||||
/**
|
||||
A short string that uniquely identifies the recorder (usually assigned by the recorder configuration).
|
||||
|
||||
The identifier is reproduced in the results of a recorder created from this configuration. In fact, the only way to link a result
|
||||
(an `ORKFileResult` object) to the recorder that generated it is to look at the value of
|
||||
`identifier`. To accurately identify recorder results, you need to ensure that recorder identifiers
|
||||
are unique within each step.
|
||||
|
||||
In some cases, it can be useful to link the recorder identifier to a unique identifier in a
|
||||
database; in other cases, it can make sense to make the identifier human
|
||||
readable.
|
||||
*/
|
||||
@property (nonatomic, copy, readonly) NSString *identifier;
|
||||
|
||||
/**
|
||||
The step that produced this recorder, configured during initialization.
|
||||
*/
|
||||
@@ -425,4 +517,3 @@ ORK_CLASS_AVAILABLE
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
Copyright (c) 2015, Apple Inc. All rights reserved.
|
||||
Copyright (c) 2015, Ricardo Sánchez-Sáez.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
@@ -36,27 +37,38 @@
|
||||
#import "ORKDataLogger.h"
|
||||
#import "ORKDefines_Private.h"
|
||||
|
||||
|
||||
@implementation ORKRecorderConfiguration
|
||||
|
||||
- (instancetype)ork_init
|
||||
{
|
||||
return [super init];
|
||||
- (instancetype)init {
|
||||
ORKThrowMethodUnavailableException();
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder
|
||||
{
|
||||
return [super init];
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
ORKThrowInvalidArgumentExceptionIfNil(identifier);
|
||||
_identifier = [identifier copy];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder
|
||||
{
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
ORK_DECODE_OBJ_CLASS(aDecoder, identifier, NSString);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
ORK_ENCODE_OBJ(aCoder, identifier);
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object {
|
||||
if ([self class] != [object class]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
@@ -64,48 +76,41 @@
|
||||
return 0;
|
||||
}
|
||||
|
||||
+ (BOOL)supportsSecureCoding
|
||||
{
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (ORKRecorder *)recorderForStep:(ORKStep *)step outputDirectory:(NSURL *)outputDirectory
|
||||
{
|
||||
- (ORKRecorder *)recorderForStep:(ORKStep *)step outputDirectory:(NSURL *)outputDirectory {
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
- (NSSet *)requestedHealthKitTypesForReading {
|
||||
- (NSSet<HKObjectType *> *)requestedHealthKitTypesForReading {
|
||||
return nil;
|
||||
}
|
||||
- (ORKPermissionMask)requestedPermissionMask {
|
||||
return ORKPermissionNone;
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@implementation ORKRecorder
|
||||
{
|
||||
|
||||
@implementation ORKRecorder {
|
||||
UIBackgroundTaskIdentifier _backgroundTask;
|
||||
NSUUID *_recorderUUID;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
- (instancetype)init {
|
||||
@throw [NSException exceptionWithName:NSGenericException reason:@"Use designated initializer" userInfo:nil];
|
||||
}
|
||||
|
||||
- (instancetype)ork_init
|
||||
{
|
||||
return [super init];
|
||||
}
|
||||
|
||||
- (instancetype)initWithStep:(ORKStep *)step outputDirectory:(NSURL *)outputDirectory;
|
||||
{
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier step:(ORKStep *)step outputDirectory:(NSURL *)outputDirectory {
|
||||
self = [super init];
|
||||
if (self)
|
||||
{
|
||||
if (self) {
|
||||
if (nil == identifier) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"identifier cannot be nil." userInfo:nil];
|
||||
}
|
||||
|
||||
_identifier = [identifier copy];
|
||||
_outputDirectory = outputDirectory;
|
||||
self.step = step;
|
||||
_backgroundTask = NSNotFound;
|
||||
@@ -115,91 +120,70 @@
|
||||
}
|
||||
|
||||
- (void)viewController:(UIViewController *)viewController willStartStepWithView:(UIView *)view {
|
||||
|
||||
}
|
||||
|
||||
- (void)start
|
||||
{
|
||||
if (self.continuesInBackground)
|
||||
{
|
||||
- (void)start {
|
||||
if (self.continuesInBackground) {
|
||||
UIApplication *app = [UIApplication sharedApplication];
|
||||
UIBackgroundTaskIdentifier oldTask = _backgroundTask;
|
||||
_backgroundTask = [app beginBackgroundTaskWithName:[NSString stringWithFormat:@"%@.%p",NSStringFromClass([self class]),self] expirationHandler:^{
|
||||
_backgroundTask = [app beginBackgroundTaskWithName:[NSString stringWithFormat:@"%@.%p",NSStringFromClass([self class]),self]
|
||||
expirationHandler:^{
|
||||
[self stop];
|
||||
}];
|
||||
if (oldTask != NSNotFound)
|
||||
{
|
||||
if (oldTask != NSNotFound) {
|
||||
[app endBackgroundTask:oldTask];
|
||||
}
|
||||
}
|
||||
|
||||
self.startDate = [NSDate date];
|
||||
}
|
||||
|
||||
|
||||
- (void)stop
|
||||
{
|
||||
- (void)stop {
|
||||
[self finishRecordingWithError:nil];
|
||||
[self reset];
|
||||
}
|
||||
|
||||
|
||||
- (void)finishRecordingWithError:(NSError *)error
|
||||
{
|
||||
- (void)finishRecordingWithError:(NSError *)error {
|
||||
// NOTE. This method may be called multiple times (once when someone tries
|
||||
// to finish, and another time with -stop is actually called.
|
||||
|
||||
|
||||
if (error)
|
||||
{
|
||||
if (error) {
|
||||
// ALWAYS report errors to the delegate, even if we think we're finished already
|
||||
|
||||
id<ORKRecorderDelegate> localDelegate = self.delegate;
|
||||
if (localDelegate && [localDelegate respondsToSelector:@selector(recorder:didFailWithError:)])
|
||||
{
|
||||
if (localDelegate && [localDelegate respondsToSelector:@selector(recorder:didFailWithError:)]) {
|
||||
[localDelegate recorder:self didFailWithError:error];
|
||||
}
|
||||
|
||||
[self reset];
|
||||
}
|
||||
|
||||
|
||||
if (_backgroundTask != NSNotFound)
|
||||
{
|
||||
if (_backgroundTask != NSNotFound) {
|
||||
// End the background task asynchronously, so whatever we're doing cleaning up the recorder has a chance to complete.
|
||||
UIBackgroundTaskIdentifier ident = _backgroundTask;
|
||||
UIBackgroundTaskIdentifier identifier = _backgroundTask;
|
||||
_backgroundTask = NSNotFound;
|
||||
|
||||
// Hold the background task for a little extra to give time for the next step to kick in,
|
||||
// if it is an automatic transition.
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[[UIApplication sharedApplication] endBackgroundTask:ident];
|
||||
[[UIApplication sharedApplication] endBackgroundTask:identifier];
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
- (NSURL *)recordingDirectoryURL
|
||||
{
|
||||
- (NSURL *)recordingDirectoryURL {
|
||||
if (! _outputDirectory) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
return [NSURL fileURLWithPath:[_outputDirectory.path stringByAppendingPathComponent:[NSString stringWithFormat:@"recorder-%@",[_recorderUUID UUIDString]]]];
|
||||
return [NSURL fileURLWithPath:[_outputDirectory.path stringByAppendingPathComponent:[NSString stringWithFormat:@"recorder-%@", _recorderUUID.UUIDString]]];
|
||||
}
|
||||
|
||||
- (NSString *)recorderType
|
||||
{
|
||||
- (NSString *)recorderType {
|
||||
return @"recorder";
|
||||
}
|
||||
|
||||
- (NSString *)logName
|
||||
{
|
||||
return [NSString stringWithFormat:@"%@_%@", [self recorderType],self.step.identifier];
|
||||
- (NSString *)logName {
|
||||
return [NSString stringWithFormat:@"%@_%@", [self recorderType], _recorderUUID.UUIDString];
|
||||
}
|
||||
|
||||
- (ORKDataLogger *)makeJSONDataLoggerWithError:(NSError * __autoreleasing *)error
|
||||
{
|
||||
- (ORKDataLogger *)makeJSONDataLoggerWithError:(NSError * __autoreleasing *)error {
|
||||
NSURL *workingDir = [self recordingDirectoryURL];
|
||||
if (! workingDir) {
|
||||
if (error) {
|
||||
@@ -211,8 +195,8 @@
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSString *ident = [self logName];
|
||||
NSString *logName = [ident stringByReplacingOccurrencesOfString:@"-" withString:@"_"];
|
||||
NSString *identifier = [self logName];
|
||||
NSString *logName = [identifier stringByReplacingOccurrencesOfString:@"-" withString:@"_"];
|
||||
|
||||
// Class B data protection for temporary file during active task logging.
|
||||
ORKDataLogger *logger = [[ORKDataLogger alloc] initWithDirectory:workingDir logName:logName formatter:[ORKJSONLogFormatter new] delegate:nil];
|
||||
@@ -221,8 +205,7 @@
|
||||
return logger;
|
||||
}
|
||||
|
||||
- (void)reset
|
||||
{
|
||||
- (void)reset {
|
||||
_recorderUUID = [NSUUID UUID];
|
||||
}
|
||||
|
||||
@@ -235,9 +218,9 @@
|
||||
}
|
||||
|
||||
- (void)applyFileProtection:(ORKFileProtectionMode)fileProtection toFileAtURL:(NSURL *)url {
|
||||
NSFileManager *fm = [NSFileManager defaultManager];
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
NSError *error = nil;
|
||||
if (! [fm setAttributes:@{NSFileProtectionKey : ORKFileProtectionFromMode(fileProtection)} ofItemAtPath:[url path] error:&error]) {
|
||||
if (! [fileManager setAttributes:@{NSFileProtectionKey : ORKFileProtectionFromMode(fileProtection)} ofItemAtPath:[url path] error:&error]) {
|
||||
ORK_Log_Debug(@"Error setting %@ on %@: %@", ORKFileProtectionFromMode(fileProtection), url, error);
|
||||
}
|
||||
}
|
||||
@@ -247,7 +230,7 @@
|
||||
id<ORKRecorderDelegate> localDelegate = self.delegate;
|
||||
if (fileUrl && !error) {
|
||||
if (localDelegate && [localDelegate respondsToSelector:@selector(recorder:didCompleteWithResult:)]) {
|
||||
ORKFileResult *result = [[ORKFileResult alloc] initWithIdentifier:(NSString *__nonnull)self.step.identifier];
|
||||
ORKFileResult *result = [[ORKFileResult alloc] initWithIdentifier:self.identifier];
|
||||
result.contentType = [self mimeType];
|
||||
result.fileURL = fileUrl;
|
||||
result.userInfo = [self userInfo];
|
||||
@@ -259,8 +242,7 @@
|
||||
[self reset];
|
||||
}
|
||||
} else {
|
||||
if (! error)
|
||||
{
|
||||
if (! error) {
|
||||
error = [NSError errorWithDomain:NSCocoaErrorDomain
|
||||
code:NSFileReadNoSuchFileError
|
||||
userInfo:@{NSLocalizedDescriptionKey:ORKLocalizedString(@"ERROR_RECORDER_NO_DATA", nil)}];
|
||||
@@ -270,4 +252,3 @@
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -31,22 +31,18 @@
|
||||
|
||||
#import <ResearchKit/ORKRecorder.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class ORKDataLogger;
|
||||
|
||||
@interface ORKRecorderConfiguration()
|
||||
|
||||
- (instancetype)ork_init;
|
||||
@interface ORKRecorderConfiguration ()
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@interface ORKRecorder ()
|
||||
|
||||
- (instancetype)ork_init;
|
||||
|
||||
@property (nonatomic, strong, nullable) ORKStep *step;
|
||||
|
||||
@property (nonatomic, strong, nullable) ORKRecorderConfiguration *configuration;
|
||||
|
||||
@@ -28,8 +28,10 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import <ResearchKit/ORKRecorder.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class ORKStep;
|
||||
@@ -41,18 +43,45 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
It is currently considered private, and is not used in any of the active tasks.
|
||||
*/
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKTouchRecorderConfiguration: ORKRecorderConfiguration
|
||||
@interface ORKTouchRecorderConfiguration : ORKRecorderConfiguration
|
||||
|
||||
- (instancetype)init;
|
||||
/**
|
||||
Returns an initialized touch recorder configuration.
|
||||
|
||||
This method is the designated initializer.
|
||||
|
||||
@param identifier The unique identifier of the recorder configuration.
|
||||
|
||||
@return An initialized touch recorder configuration.
|
||||
*/
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/**
|
||||
Returns a new touch recorder configuration initialized from data in the given unarchiver.
|
||||
|
||||
@param aDecoder Coder from which to initialize the touch recorder configuration.
|
||||
|
||||
@return A new touch recorder configuration.
|
||||
*/
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface ORKRecorder()
|
||||
|
||||
- (instancetype)initWithStep:(nullable ORKStep *)step
|
||||
outputDirectory:(nullable NSURL *)outputDirectory;
|
||||
@interface ORKRecorder ()
|
||||
|
||||
/**
|
||||
Returns an initialized recorder.
|
||||
|
||||
This method is the designated initializer.
|
||||
|
||||
@param identifier The unique identifier of the recorder.
|
||||
@param step The step for which this recorder is being created.
|
||||
@param outputDirectory The directory in which all output file data should be written (if producing `ORKFileResult` instances).
|
||||
|
||||
@return An initialized recorder.
|
||||
*/
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier step:(nullable ORKStep *)step outputDirectory:(nullable NSURL *)outputDirectory;
|
||||
|
||||
/**
|
||||
A preparation step to provide viewController and view before record starting.
|
||||
@@ -87,7 +116,27 @@ ORK_CLASS_AVAILABLE
|
||||
@end
|
||||
|
||||
|
||||
@interface ORKRecorderConfiguration()
|
||||
@interface ORKRecorderConfiguration ()
|
||||
|
||||
/**
|
||||
Returns an initialized recorder configuration.
|
||||
|
||||
This method is the designated initializer.
|
||||
|
||||
@param identifier The unique identifier of the recorder configuration.
|
||||
|
||||
@return An initialized recorder configuration.
|
||||
*/
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/**
|
||||
Returns a new recorder configuration initialized from data in the given unarchiver.
|
||||
|
||||
@param aDecoder Coder from which to initialize the recorder configuration.
|
||||
|
||||
@return A new recorder configuration.
|
||||
*/
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/**
|
||||
Returns the permission mask indicating the permissions required for this configuration.
|
||||
@@ -96,7 +145,6 @@ ORK_CLASS_AVAILABLE
|
||||
*/
|
||||
- (ORKPermissionMask)requestedPermissionMask;
|
||||
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
|
||||
@@ -36,10 +37,11 @@
|
||||
|
||||
A game consists of a subset of a permutation of the integers [0 .. gameSize-1],
|
||||
which represent the sequence of targets that should be tapped.
|
||||
|
||||
*/
|
||||
@interface ORKSpatialSpanGame : NSObject
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
/**
|
||||
Returns an initialized spatial span game using the specified game size, sequence length, and seed.
|
||||
|
||||
@@ -64,12 +66,12 @@
|
||||
/**
|
||||
Enumerates the sequence, calling the block once for each element.
|
||||
|
||||
@param handler The block to be called for each element in the sequence. The `handler` block takes the following parameters:
|
||||
@param handler The block to be called for each element in the sequence. The `handler` block takes the following parameters:
|
||||
|
||||
`step` The step in the sequence. The step starts at 0 and increments by one on each call.
|
||||
`tileIndex` The index in [ 0 .. gameSize ] that corresponds to the step's element of the sequence.
|
||||
`isLastStep` A Boolean value that indicates if this is the last step in the sequence.
|
||||
`stop` A Boolean value that indicates if the enumeration should be terminated (pass `NO` to terminate the enumeration).
|
||||
`step` The step in the sequence. The step starts at 0 and increments by one on each call.
|
||||
`tileIndex` The index in [ 0 .. gameSize ] that corresponds to the step's element of the sequence.
|
||||
`isLastStep` A Boolean value that indicates if this is the last step in the sequence.
|
||||
`stop` A Boolean value that indicates if the enumeration should be terminated (pass `NO` to terminate the enumeration).
|
||||
*/
|
||||
|
||||
- (void)enumerateSequenceWithHandler:(void(^)(NSInteger step, NSInteger tileIndex, BOOL isLastStep, BOOL *stop))handler;
|
||||
@@ -77,5 +79,4 @@
|
||||
/// Returns the value of the specified step in the sequence.
|
||||
- (NSInteger)tileIndexForStep:(NSInteger)step;
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@@ -28,13 +28,18 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#import "ORKSpatialSpanGame.h"
|
||||
|
||||
@implementation ORKSpatialSpanGame
|
||||
{
|
||||
#import "ORKSpatialSpanGame.h"
|
||||
#import "ORKHelpers.h"
|
||||
|
||||
@implementation ORKSpatialSpanGame {
|
||||
NSInteger *_sequence;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
ORKThrowMethodUnavailableException();
|
||||
}
|
||||
|
||||
- (void)generateSequence {
|
||||
_sequence = calloc(_gameSize, sizeof(NSInteger));
|
||||
if (_sequence == NULL) {
|
||||
@@ -81,13 +86,10 @@
|
||||
if (_sequence == NULL) {
|
||||
self = nil;
|
||||
}
|
||||
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Step parameter is the step in the sequence; tileIndex is the value of that step of the sequence.
|
||||
- (void)enumerateSequenceWithHandler:(void(^)(NSInteger step, NSInteger tileIndex, BOOL isLastStep, BOOL *stop))handler {
|
||||
BOOL stop = NO;
|
||||
|
||||
@@ -28,9 +28,11 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "ORKSpatialSpanTargetView.h"
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class ORKSpatialSpanGame;
|
||||
@@ -43,6 +45,8 @@ typedef NS_ENUM(NSInteger, ORKSpatialSpanResult) {
|
||||
|
||||
@interface ORKSpatialSpanGameState : NSObject
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
- (instancetype)initWithGame:(ORKSpatialSpanGame *)game NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@property (nonatomic, strong, readonly, nullable) ORKSpatialSpanGame *game;
|
||||
|
||||
@@ -28,8 +28,10 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKSpatialSpanGameState.h"
|
||||
#import "ORKSpatialSpanGame.h"
|
||||
#import "ORKHelpers.h"
|
||||
|
||||
|
||||
@implementation ORKSpatialSpanGameState {
|
||||
@@ -37,6 +39,9 @@
|
||||
ORKSpatialSpanTargetState *_states;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
ORKThrowMethodUnavailableException();
|
||||
}
|
||||
|
||||
- (instancetype)initWithGame:(ORKSpatialSpanGame *)game {
|
||||
self = [super init];
|
||||
@@ -59,7 +64,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (void)reset {
|
||||
const NSInteger gameSize = [_game gameSize];
|
||||
for (NSInteger tileIndex = 0; tileIndex < gameSize; tileIndex++) {
|
||||
@@ -98,7 +102,6 @@
|
||||
return correct ? ORKSpatialSpanResultCorrect : ORKSpatialSpanResultIncorrect;
|
||||
}
|
||||
|
||||
|
||||
- (NSInteger)currentStepIndex {
|
||||
return [_plays count];
|
||||
}
|
||||
|
||||
@@ -28,9 +28,11 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKCustomStepView_Internal.h"
|
||||
#import "ORKSpatialSpanTargetView.h"
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef struct {
|
||||
@@ -46,6 +48,7 @@ typedef struct {
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface ORKSpatialSpanMemoryGameView : UIView <ORKSpatialSpanTargetViewDelegate>
|
||||
|
||||
@property (nonatomic, weak, nullable) id<ORKSpatialSpanMemoryGameViewDelegate> delegate;
|
||||
@@ -68,7 +71,7 @@ typedef struct {
|
||||
|
||||
@interface ORKSpatialSpanMemoryContentView : ORKActiveStepCustomView
|
||||
|
||||
@property (nonatomic, readonly, strong) ORKSpatialSpanMemoryGameView *gameView;
|
||||
@property (nonatomic, strong, readonly) ORKSpatialSpanMemoryGameView *gameView;
|
||||
|
||||
@property (nonatomic, assign, getter=isFooterHidden) BOOL footerHidden;
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKSpatialSpanMemoryContentView.h"
|
||||
#import "ORKSpatialSpanTargetView.h"
|
||||
#import "ORKActiveStepQuantityView.h"
|
||||
@@ -122,20 +123,20 @@
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
|
||||
CGRect bds = [self bounds];
|
||||
CGFloat gridItemEdgeLength = ORKFloorToViewScale(MIN(bds.size.width / _gridSize.width, bds.size.height / _gridSize.height), self);
|
||||
CGRect bounds = [self bounds];
|
||||
CGFloat gridItemEdgeLength = ORKFloorToViewScale(MIN(bounds.size.width / _gridSize.width, bounds.size.height / _gridSize.height), self);
|
||||
|
||||
gridItemEdgeLength = MIN(gridItemEdgeLength, 114);
|
||||
CGSize gridItemSize = (CGSize){gridItemEdgeLength, gridItemEdgeLength};
|
||||
|
||||
CGPoint centeringOffset = CGPointZero;
|
||||
centeringOffset.x = 0.5*(bds.size.width - (gridItemSize.width * _gridSize.width));
|
||||
centeringOffset.y = 0.5*(bds.size.height - (gridItemSize.height * _gridSize.height));
|
||||
centeringOffset.x = 0.5*(bounds.size.width - (gridItemSize.width * _gridSize.width));
|
||||
centeringOffset.y = 0.5*(bounds.size.height - (gridItemSize.height * _gridSize.height));
|
||||
|
||||
NSInteger tileIndex = 0;
|
||||
for (NSInteger x = 0; x < _gridSize.width; x++) {
|
||||
for (NSInteger y = 0; y < _gridSize.height; y++) {
|
||||
ORKSpatialSpanTargetView *targetView = [_tileViews objectAtIndex:tileIndex];
|
||||
ORKSpatialSpanTargetView *targetView = _tileViews[tileIndex];
|
||||
|
||||
CGPoint origin = (CGPoint){.x = ORKFloorToViewScale(centeringOffset.x + x * gridItemSize.width, self),
|
||||
.y = ORKFloorToViewScale(centeringOffset.y + y * gridItemSize.height, self)};
|
||||
@@ -147,12 +148,12 @@
|
||||
}
|
||||
|
||||
- (void)setState:(ORKSpatialSpanTargetState)state forTileIndex:(NSInteger)tileIndex animated:(BOOL)animated {
|
||||
ORKSpatialSpanTargetView *view = [_tileViews objectAtIndex:tileIndex];
|
||||
ORKSpatialSpanTargetView *view = _tileViews[tileIndex];
|
||||
[view setState:state animated:animated];
|
||||
}
|
||||
|
||||
- (ORKSpatialSpanTargetState)stateForTileIndex:(NSInteger)tileIndex {
|
||||
return [(ORKSpatialSpanTargetView *)[_tileViews objectAtIndex:tileIndex] state];
|
||||
return [(ORKSpatialSpanTargetView *)_tileViews[tileIndex] state];
|
||||
}
|
||||
|
||||
#pragma mark Accessibility
|
||||
@@ -163,6 +164,7 @@
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKSpatialSpanMemoryContentView {
|
||||
ORKQuantityPairView *_quantityPairView;
|
||||
ORKNavigationContainerView *_continueView;
|
||||
@@ -219,7 +221,6 @@
|
||||
[self countView].backgroundColor = [[UIColor purpleColor] colorWithAlphaComponent:0.2];
|
||||
#endif
|
||||
|
||||
|
||||
[self setNeedsUpdateConstraints];
|
||||
}
|
||||
return self;
|
||||
@@ -257,13 +258,19 @@
|
||||
[self updateFooterHidden];
|
||||
}
|
||||
|
||||
|
||||
- (void)updateMargins {
|
||||
self.layoutMargins = (UIEdgeInsets){.left=ORKStandardMarginForView(self),.right=ORKStandardMarginForView(self)};
|
||||
CGFloat margin = ORKStandardHorizontalMarginForView(self);
|
||||
self.layoutMargins = (UIEdgeInsets){.left = margin, .right = margin};
|
||||
_quantityPairView.layoutMargins = self.layoutMargins;
|
||||
}
|
||||
|
||||
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
|
||||
- (void)setFrame:(CGRect)frame {
|
||||
[super setFrame:frame];
|
||||
[self updateMargins];
|
||||
}
|
||||
|
||||
- (void)setBounds:(CGRect)bounds {
|
||||
[super setBounds:bounds];
|
||||
[self updateMargins];
|
||||
}
|
||||
|
||||
@@ -277,22 +284,57 @@
|
||||
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(_gameView, _quantityPairView, _continueView);
|
||||
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(>=0)-[_gameView][_quantityPairView]|" options:NSLayoutFormatAlignAllCenterX metrics:nil views:views]];
|
||||
NSLayoutConstraint *c1 = [NSLayoutConstraint constraintWithItem:_gameView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:1000];
|
||||
c1.priority = UILayoutPriorityDefaultLow-1;
|
||||
[constraints addObject:c1];
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(>=0)-[_gameView][_quantityPairView]|"
|
||||
options:NSLayoutFormatAlignAllCenterX
|
||||
metrics:nil
|
||||
views:views]];
|
||||
NSLayoutConstraint *constraint1 = [NSLayoutConstraint constraintWithItem:_gameView
|
||||
attribute:NSLayoutAttributeHeight
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1.0
|
||||
constant:1000.0];
|
||||
constraint1.priority = UILayoutPriorityDefaultLow-1;
|
||||
[constraints addObject:constraint1];
|
||||
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_gameView]-|" options:(NSLayoutFormatOptions)0 metrics:nil views:views]];
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_gameView]-|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil
|
||||
views:views]];
|
||||
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_quantityPairView]|" options:(NSLayoutFormatOptions)0 metrics:nil views:views]];
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_continueView]|" options:(NSLayoutFormatOptions)0 metrics:nil views:views]];
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_continueView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:_quantityPairView attribute:NSLayoutAttributeBottom multiplier:1 constant:0]];
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_continueView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:_quantityPairView attribute:NSLayoutAttributeTop multiplier:1 constant:0]];
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_quantityPairView]|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil
|
||||
views:views]];
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_continueView]|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil
|
||||
views:views]];
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_continueView
|
||||
attribute:NSLayoutAttributeBottom
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_quantityPairView
|
||||
attribute:NSLayoutAttributeBottom
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_continueView
|
||||
attribute:NSLayoutAttributeTop
|
||||
relatedBy:NSLayoutRelationGreaterThanOrEqual
|
||||
toItem:_quantityPairView
|
||||
attribute:NSLayoutAttributeTop
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
|
||||
NSLayoutConstraint *maxWidthConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:10000];
|
||||
NSLayoutConstraint *maxWidthConstraint = [NSLayoutConstraint constraintWithItem:self
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1.0
|
||||
constant:ORKScreenMetricMaxDimension];
|
||||
maxWidthConstraint.priority = UILayoutPriorityRequired-1;
|
||||
[constraints addObject:maxWidthConstraint];
|
||||
|
||||
|
||||
[NSLayoutConstraint activateConstraints:constraints];
|
||||
_constraints = constraints;
|
||||
@@ -300,6 +342,4 @@
|
||||
[super updateConstraints];
|
||||
}
|
||||
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@@ -28,8 +28,10 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import <ResearchKit/ResearchKit_Private.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
ORK_CLASS_AVAILABLE
|
||||
|
||||
@@ -28,11 +28,13 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import "ORKSpatialSpanMemoryStep.h"
|
||||
#import "ORKSpatialSpanMemoryStepViewController.h"
|
||||
#import "ORKHelpers.h"
|
||||
#import "ORKStep_Private.h"
|
||||
|
||||
|
||||
@implementation ORKSpatialSpanMemoryStep
|
||||
|
||||
+ (Class)stepViewControllerClass {
|
||||
@@ -41,13 +43,14 @@
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
||||
self = [super initWithIdentifier:identifier];
|
||||
self.shouldStartTimerAutomatically = YES;
|
||||
self.shouldContinueOnFinish = YES;
|
||||
if (self) {
|
||||
self.shouldStartTimerAutomatically = YES;
|
||||
self.shouldContinueOnFinish = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
- (instancetype)copyWithZone:(NSZone *)zone {
|
||||
ORKSpatialSpanMemoryStep *step = [super copyWithZone:zone];
|
||||
step.initialSpan = self.initialSpan;
|
||||
step.minimumSpan = self.minimumSpan;
|
||||
@@ -62,7 +65,6 @@
|
||||
}
|
||||
|
||||
- (void)validateParameters {
|
||||
|
||||
[super validateParameters];
|
||||
|
||||
NSInteger const ORKSpatialSpanMemoryTaskMinimumInitialSpan = 2;
|
||||
@@ -72,53 +74,45 @@
|
||||
NSInteger const ORKSpatialSpanMemoryTaskMinimumMaxTests = 1;
|
||||
NSInteger const ORKSpatialSpanMemoryTaskMinimumMaxConsecutiveFailures = 1;
|
||||
|
||||
if ( self.initialSpan < ORKSpatialSpanMemoryTaskMinimumInitialSpan)
|
||||
{
|
||||
if ( self.initialSpan < ORKSpatialSpanMemoryTaskMinimumInitialSpan) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException
|
||||
reason:[NSString stringWithFormat:@"initialSpan can not be less than %@.", @(ORKSpatialSpanMemoryTaskMinimumInitialSpan)]
|
||||
userInfo:nil];
|
||||
}
|
||||
|
||||
if ( self.minimumSpan > self.initialSpan)
|
||||
{
|
||||
if ( self.minimumSpan > self.initialSpan) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"initialSpan can not be less than minimumSpan." userInfo:nil];
|
||||
}
|
||||
|
||||
if ( self.initialSpan > self.maximumSpan)
|
||||
{
|
||||
if ( self.initialSpan > self.maximumSpan) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"maximumSpan can not be less than initialSpan." userInfo:nil];
|
||||
}
|
||||
|
||||
if ( self.maximumSpan > ORKSpatialSpanMemoryTaskMaximumSpan)
|
||||
{
|
||||
if ( self.maximumSpan > ORKSpatialSpanMemoryTaskMaximumSpan) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException
|
||||
reason:[NSString stringWithFormat:@"maximumSpan can not be more than %@.", @(ORKSpatialSpanMemoryTaskMaximumSpan)]
|
||||
userInfo:nil];
|
||||
}
|
||||
|
||||
if (self.playSpeed < ORKSpatialSpanMemoryTaskMinimumPlaySpeed)
|
||||
{
|
||||
if (self.playSpeed < ORKSpatialSpanMemoryTaskMinimumPlaySpeed) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException
|
||||
reason:[NSString stringWithFormat:@"playSpeed can not be shorter than %@ seconds.", @(ORKSpatialSpanMemoryTaskMinimumPlaySpeed)]
|
||||
userInfo:nil];
|
||||
}
|
||||
|
||||
if (self.playSpeed > ORKSpatialSpanMemoryTaskMaximumPlaySpeed)
|
||||
{
|
||||
if (self.playSpeed > ORKSpatialSpanMemoryTaskMaximumPlaySpeed) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException
|
||||
reason:[NSString stringWithFormat:@"playSpeed can not be longer than %@ seconds.", @(ORKSpatialSpanMemoryTaskMaximumPlaySpeed)]
|
||||
userInfo:nil];
|
||||
}
|
||||
|
||||
if (self.maxTests < ORKSpatialSpanMemoryTaskMinimumMaxTests)
|
||||
{
|
||||
if (self.maxTests < ORKSpatialSpanMemoryTaskMinimumMaxTests) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException
|
||||
reason:[NSString stringWithFormat:@"maxTests can not be less than %@.", @(ORKSpatialSpanMemoryTaskMinimumMaxTests)]
|
||||
userInfo:nil];
|
||||
}
|
||||
|
||||
if (self.maxConsecutiveFailures < ORKSpatialSpanMemoryTaskMinimumMaxConsecutiveFailures)
|
||||
{
|
||||
if (self.maxConsecutiveFailures < ORKSpatialSpanMemoryTaskMinimumMaxConsecutiveFailures) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException
|
||||
reason:[NSString stringWithFormat:@"maxConsecutiveFailures can not be less than %@.", @(ORKSpatialSpanMemoryTaskMinimumMaxConsecutiveFailures)]
|
||||
userInfo:nil];
|
||||
@@ -129,11 +123,9 @@
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder
|
||||
{
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [super initWithCoder:aDecoder];
|
||||
if (self)
|
||||
{
|
||||
if (self) {
|
||||
ORK_DECODE_INTEGER(aDecoder, initialSpan);
|
||||
ORK_DECODE_INTEGER(aDecoder, minimumSpan);
|
||||
ORK_DECODE_INTEGER(aDecoder, maximumSpan);
|
||||
@@ -147,8 +139,7 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder
|
||||
{
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
[super encodeWithCoder:aCoder];
|
||||
ORK_ENCODE_INTEGER(aCoder, initialSpan);
|
||||
ORK_ENCODE_INTEGER(aCoder, minimumSpan);
|
||||
@@ -161,12 +152,10 @@
|
||||
ORK_ENCODE_OBJ(aCoder, customTargetPluralName);
|
||||
}
|
||||
|
||||
+ (BOOL)supportsSecureCoding
|
||||
{
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
- (BOOL)isEqual:(id)object {
|
||||
BOOL isParentSame = [super isEqual:object];
|
||||
|
||||
@@ -179,14 +168,11 @@
|
||||
(self.maxTests == castObject.maxTests) &&
|
||||
(self.maxConsecutiveFailures == castObject.maxConsecutiveFailures) &&
|
||||
(ORKEqualObjects(self.customTargetPluralName, castObject.customTargetPluralName)) &&
|
||||
(self.requireReversal == castObject.requireReversal)) ;
|
||||
(self.requireReversal == castObject.requireReversal));
|
||||
}
|
||||
|
||||
|
||||
|
||||
- (BOOL)allowsBackNavigation {
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -28,8 +28,10 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import <ResearchKit/ResearchKit_Private.h>
|
||||
|
||||
|
||||
/**
|
||||
View controller corresponding to `ORKSpatialSpanMemoryStep`.
|
||||
*/
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user