Compare commits
1312 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4259a1cc92 | |||
| 70d4532d0e | |||
| 23900bed0e | |||
| 8c8a1adc47 | |||
| 429c3c9886 | |||
| 4228cf46c6 | |||
| ce31ebd366 | |||
| 135ec4a3f2 | |||
| a30119aa0d | |||
| 31526c6664 | |||
| e5ac62f298 | |||
| bb1be965a2 | |||
| 3fb266139e | |||
| 0b2216c6cf | |||
| c90d8c7fbd | |||
| 3cb705c123 | |||
| 1817398f61 | |||
| b542c46189 | |||
| b6cc636975 | |||
| 1844a0774c | |||
| 6a1343622d | |||
| a3b45b6268 | |||
| f076b69a98 | |||
| 39e17e7867 | |||
| 663426250f | |||
| 197eb8422a | |||
| fe10cd8a71 | |||
| 63dfad2b0d | |||
| 886605e013 | |||
| 4c0a4a1e51 | |||
| aa2b1f9e61 | |||
| fed9fb9ce6 | |||
| 6a786cfb37 | |||
| 39d76c8ed6 | |||
| 7059bd39a6 | |||
| dddc633e3a | |||
| 9e358e7fc5 | |||
| 0aacde2057 | |||
| 5bdd973315 | |||
| cb4514328e | |||
| 84710f773f | |||
| 42005af39c | |||
| 9c847f8fe6 | |||
| aac584b2f1 | |||
| 4be283e888 | |||
| 4d37142873 | |||
| 08b48589da | |||
| 6ba85a1504 | |||
| 5fc4316e22 | |||
| 287dcd979c | |||
| 29f2d16676 | |||
| b022ae3589 | |||
| 76bbbbe604 | |||
| a322f0b17f | |||
| f11d17752f | |||
| 14a815d63e | |||
| 1b4b0166b8 | |||
| 669269fd45 | |||
| 3f7c1d15d5 | |||
| ca136a9ce1 | |||
| fe2ea1e3d8 | |||
| eedcff0ab0 | |||
| 00bd267c1b | |||
| d9c005aa18 | |||
| be02e1a22a | |||
| b474f6f12d | |||
| bc263d92af | |||
| 0575f21c3e | |||
| 88543a14d6 | |||
| cd8a801738 | |||
| 30af7f46f7 | |||
| 1e889ad7dc | |||
| 839c8eca53 | |||
| 48ed4b3771 | |||
| e6f4c1deb8 | |||
| ae2bff43fa | |||
| 14260e0df1 | |||
| 4153702c2d | |||
| a9494ce5d8 | |||
| 0b06d4c18a | |||
| 2b6d1fcab9 | |||
| eb52acefa9 | |||
| 82de8783bd | |||
| 0dea3dd673 | |||
| 6f4c072f93 | |||
| fee0d2d7b6 | |||
| 610a1fd93f | |||
| 36bda05556 | |||
| 069ea16731 | |||
| a1ed3ea711 | |||
| d910062bb9 | |||
| ba5d80c6c1 | |||
| 21ada70ae4 | |||
| 9be3f057be | |||
| 51c23ccf17 | |||
| 113b7422a0 | |||
| c852c62262 | |||
| c27b784870 | |||
| 2510bc7f97 | |||
| de7860b60a | |||
| 37c65bdc55 | |||
| 4fe13d0b27 | |||
| 853d4384fd | |||
| f9669508dc | |||
| e1ea72b861 | |||
| 1cf445af09 | |||
| fc1c328b68 | |||
| 7b7a851cca | |||
| 37ac134b5e | |||
| 067f8f8e3d | |||
| 7d30dd09f3 | |||
| 19921baf55 | |||
| ffd8552aa8 | |||
| 140e1ebae0 | |||
| 9338ea8b59 | |||
| 63d56e44f8 | |||
| 3daf9484c4 | |||
| d87372923b | |||
| 3dccc6d438 | |||
| 50cf8171f5 | |||
| d2c51201ef | |||
| 8a320f0b72 | |||
| 2988cb0fa6 | |||
| 59788e53ec | |||
| af823d1f63 | |||
| 75ac5d3366 | |||
| 932024c4af | |||
| 26cf7e4d64 | |||
| fa2b18da42 | |||
| 304d93a79a | |||
| a57a8d8370 | |||
| d44676d816 | |||
| 18d50721cc | |||
| 967518a576 | |||
| ef8c1e1b3a | |||
| 6563ebdca4 | |||
| f85faecb49 | |||
| 0341e511ab | |||
| 8f9ab33897 | |||
| 372fbaaaa4 | |||
| a3c28f666c | |||
| 4c34d1e8a3 | |||
| 50c0d4b3c0 | |||
| 4b269b8c21 | |||
| 143b474aff | |||
| ca11a8def0 | |||
| 09cb204a24 | |||
| 20204946bc | |||
| 74dc33a654 | |||
| f738f18570 | |||
| 7af35c7c35 | |||
| ca2f7848f8 | |||
| 85097a5e20 | |||
| 60af4ce1e5 | |||
| 8c10abfd74 | |||
| 0f97c0704a | |||
| a9ea108054 | |||
| ac0a9a25d6 | |||
| 4ca0197532 | |||
| e021f815ca | |||
| 1492ab82c1 | |||
| 61408fc5f6 | |||
| 0459818613 | |||
| 99bdc3cbbc | |||
| c0b751d05a | |||
| d3ed706680 | |||
| d5175083ef | |||
| 721572a533 | |||
| 2494895cf9 | |||
| 160b7c5f71 | |||
| 1b5ea88e0f | |||
| dd42f4740e | |||
| 1f26d5c5a1 | |||
| 0a07f24bea | |||
| 224146f7fd | |||
| 5e7768483e | |||
| 66884c045b | |||
| e7ca832c3d | |||
| af3285f5f0 | |||
| 50cd9055ac | |||
| d0226f29f7 | |||
| eceeab053d | |||
| de0019054a | |||
| cbf9d7284f | |||
| 00183879ce | |||
| 58453f5df9 | |||
| 9c65780911 | |||
| f7f8e12d9d | |||
| ee494c5ed1 | |||
| 12a2d556c9 | |||
| ce6d4fe3ac | |||
| 05f2fe9db4 | |||
| 7e8175b8a2 | |||
| c3460d8aaa | |||
| 886bca74bf | |||
| 53bb638c69 | |||
| 3d40007df9 | |||
| 5070517ba3 | |||
| 5bca8eb66d | |||
| 4dff0189df | |||
| 9e1e855b70 | |||
| 4e7f24f1c2 | |||
| 3229df2a48 | |||
| 7bc407ee90 | |||
| 96e94155d7 | |||
| b8b5648c71 | |||
| 0471bf61d4 | |||
| a0ca62ec23 | |||
| aac14a3a1a | |||
| 696758bdc2 | |||
| 43b6585bfa | |||
| f47f9d824c | |||
| 9b4152ec6e | |||
| b58ae6139b | |||
| 7ba97f4089 | |||
| a2bfb9a15d | |||
| 17b7d7d4f9 | |||
| d9e2f54c41 | |||
| 9347ef6573 | |||
| ed4a50dacc | |||
| d8d3882a1c | |||
| ec8308b8bd | |||
| 9296fe90bc | |||
| df9170f9cf | |||
| 0deb8ba55e | |||
| b24310adbc | |||
| c138f6aa02 | |||
| 49e541d1b7 | |||
| f22676a8e7 | |||
| d34749e1ef | |||
| 0e4251d511 | |||
| 06ff988941 | |||
| 3b3c907d39 | |||
| b1515e0fc2 | |||
| f9924ddbc0 | |||
| bbde9865ee | |||
| cfa9e90d76 | |||
| 851bd4e2c5 | |||
| 9fbabf8f3b | |||
| 536a22f733 | |||
| e51c9b708a | |||
| b45425ba72 | |||
| 181651b365 | |||
| b8d2c6bf86 | |||
| feb5f11e3e | |||
| 8d4b73ae8f | |||
| 81e67dc6fa | |||
| e873af81d6 | |||
| 59681fbab5 | |||
| a3a53b7c68 | |||
| a28e2596bd | |||
| f5e8b40f6d | |||
| 55bc242896 | |||
| d3dee01678 | |||
| a6445546fd | |||
| 44a786d51d | |||
| 5954a6b254 | |||
| 06eecb054c | |||
| c38da58f6b | |||
| 17c35e6765 | |||
| 1074eefbbe | |||
| 6c54eea404 | |||
| daa267b8f0 | |||
| 7f66623c59 | |||
| 7854c619a3 | |||
| deef811bc9 | |||
| b9fff0e4c0 | |||
| f35e84189c | |||
| 5ff0f9324a | |||
| 35d33709b2 | |||
| 0d9c98dd56 | |||
| 2ac5e30d85 | |||
| d1e246bba4 | |||
| 0dc31e4593 | |||
| 521dba3d4e | |||
| 07e05298a0 | |||
| 9214c48d41 | |||
| ebdc7a218a | |||
| 3d9cc15c0b | |||
| f45722366c | |||
| 10152cfb79 | |||
| 038794df0d | |||
| d74bfe956f | |||
| af9588fd93 | |||
| 80ee2b8792 | |||
| 8dffe81ea7 | |||
| 95099e34de | |||
| 9dc22ff97e | |||
| df7a5d2ae4 | |||
| 475d84de73 | |||
| cc6380af67 | |||
| ebf3a5474d | |||
| f8a94a0f66 | |||
| 1afb67bf9e | |||
| 258afcb7df | |||
| 8a3882d85c | |||
| 2bd667bc6f | |||
| 7a4715dd72 | |||
| 8f7047220f | |||
| fda01e8dd0 | |||
| 8042df3127 | |||
| 153726633f | |||
| 4040d962a0 | |||
| 861211fa3b | |||
| a8f6be61f3 | |||
| 7c01903108 | |||
| 848800bcd6 | |||
| caa324ec79 | |||
| 27bf948f6d | |||
| ed60c84a2d | |||
| e70d56142d | |||
| a4c9286f29 | |||
| 97c5ada28f | |||
| fd0d4af750 | |||
| dc81d8af3a | |||
| deac6b5de0 | |||
| 2c504f4b01 | |||
| db4b19b229 | |||
| 129d2ac3df | |||
| 0790e52a5a | |||
| 9784c1425a | |||
| df13dca4d9 | |||
| acfce230b4 | |||
| bfac86d706 | |||
| c2e2951d6f | |||
| 36a578549b | |||
| 165e107ddc | |||
| e32efc4ec2 | |||
| 3479c35fb3 | |||
| 74fbc9af77 | |||
| ec91b7b139 | |||
| d4403704ce | |||
| 4919e0fc0c | |||
| 4c49283504 | |||
| 26a094e05f | |||
| d6504c68ef | |||
| f90af1ce01 | |||
| ae6fcf4ce4 | |||
| 8730a26be1 | |||
| 20d6defb6f | |||
| cf917dc208 | |||
| 47ad36d906 | |||
| e69228445a | |||
| 90619ac2af | |||
| 43c4b892b2 | |||
| 1c2028ce5d | |||
| 29942a01d5 | |||
| c159d6db61 | |||
| 7635528b2f | |||
| d5b6b0f87a | |||
| 328b8421a3 | |||
| b4a2cbebd0 | |||
| 9a749c5489 | |||
| 09272a78df | |||
| 92ae7cf071 | |||
| b5568e278b | |||
| 33864f1012 | |||
| 2534e6b20e | |||
| 56b50e9296 | |||
| 8eb756b96c | |||
| 52d5fcabe7 | |||
| bf07984430 | |||
| 0554cbd6f1 | |||
| b34d1ecd29 | |||
| f3c7c53e21 | |||
| 59045b27a5 | |||
| 14ea5cc282 | |||
| ec04a5c633 | |||
| f1e4941f3f | |||
| bb01f0bb4d | |||
| 7ebc03c93f | |||
| 413bc6682a | |||
| 9ccf4fb743 | |||
| 723075a61f | |||
| c659539a45 | |||
| 48661fe670 | |||
| b9fadc1b87 | |||
| 11dc10e66e | |||
| 6911fe614f | |||
| 452665de14 | |||
| 1f352c39d2 | |||
| e42c92004a | |||
| 17ac3f5177 | |||
| 400da43f42 | |||
| c0aed6d641 | |||
| f7e9c672e2 | |||
| 7f106898e2 | |||
| d3da59a4f6 | |||
| 8a78e95ebb | |||
| c113deb1a3 | |||
| 3097232bd5 | |||
| 04a55343bc | |||
| 485e6035e0 | |||
| 785ea40fc4 | |||
| 69319a3c5e | |||
| c10ab44036 | |||
| 3fdbad88b2 | |||
| 2287b99b86 | |||
| 3d6a7fa481 | |||
| 0759764889 | |||
| e3ab9d75ef | |||
| f401dcf94d | |||
| b941379779 | |||
| f2d13d1305 | |||
| 96e5f26f7d | |||
| cecef562a9 | |||
| 7d2d04239f | |||
| 2b649f9e12 | |||
| a9478c3cb0 | |||
| 9b64b9c0f0 | |||
| ac0f3c542c | |||
| 2e356cac03 | |||
| 91d4fa7890 | |||
| 18504e9ffe | |||
| d80ca89094 | |||
| 706900ff07 | |||
| 20cfa9f588 | |||
| 76aef1a069 | |||
| 4afe6dcf77 | |||
| 4d82dcb8f3 | |||
| d2c381fefb | |||
| fbfeb233fa | |||
| 2a4b1b370a | |||
| 52ef002a67 | |||
| 65e2946cba | |||
| 52fbba25b8 | |||
| 73c59a40b9 | |||
| 87c6feceaa | |||
| 4bfc412626 | |||
| 8621b38a89 | |||
| 8517354463 | |||
| 250d016306 | |||
| a2a73b1661 | |||
| 46e61011a2 | |||
| 13ab769dd8 | |||
| 30f3900307 | |||
| f85a4c0c06 | |||
| 32fc46f896 | |||
| f15940f83e | |||
| 310080fc8e | |||
| b41e0be80f | |||
| c7d1d99b32 | |||
| 684dd82c15 | |||
| c0ff5bc6e0 | |||
| d2381657fa | |||
| 3463f1a16f | |||
| 9878b34c2a | |||
| 81bc7221d1 | |||
| a5bb7b5506 | |||
| 921b280a3b | |||
| d313ab3a45 | |||
| 73657854d4 | |||
| 7dc91f531d | |||
| 294ec34d47 | |||
| bcfb0b4dbb | |||
| 82c325d6c7 | |||
| c7bd1f5c41 | |||
| bdb6849ab5 | |||
| 6d1d22d9fa | |||
| 393a2bf404 | |||
| f828fbd8c4 | |||
| 27193727e9 | |||
| a7173cc746 | |||
| b0977b55e0 | |||
| 6d792cfd83 | |||
| fb6b1ab2c5 | |||
| ab729432b4 | |||
| 5b5e53bdc3 | |||
| 2b6c69e01d | |||
| fb1c5e9b9a | |||
| f4b4d2aee1 | |||
| 96478c583d | |||
| a796e6cc1e | |||
| 8599553a79 | |||
| 8197c147e1 | |||
| 8bdb32fc7a | |||
| 16f9b1dbe1 | |||
| 9459e330d5 | |||
| 78be958aea | |||
| 3c9291ed24 | |||
| 5454cf83e1 | |||
| b4e8a91474 | |||
| 829f93523a | |||
| 98b0acaeb7 | |||
| 91a9acb9ef | |||
| 71bdaab7be | |||
| 3e18a08690 | |||
| 63b8bea246 | |||
| 7a1a1c6cdd | |||
| 2b7f05fd96 | |||
| 234f40306a | |||
| 66218e4e28 | |||
| 130898cafd | |||
| 7db4bf50c8 | |||
| feb26fa506 | |||
| c317b30e82 | |||
| af6b027b7c | |||
| 3c715cc211 | |||
| c7bddcf4d5 | |||
| ad9eefb19a | |||
| 537e67af78 | |||
| 1c16398bc7 | |||
| ca292c1987 | |||
| 7a9009759e | |||
| 2040bbd095 | |||
| 111ac5627a | |||
| 57f911a4bb | |||
| 17776d04a4 | |||
| 66b7806bce | |||
| c79b228fba | |||
| 0398fb3767 | |||
| f168e02e9e | |||
| 5d1ed481e3 | |||
| 7cd75c4d40 | |||
| aa58b643a0 | |||
| 3b0ce6478b | |||
| c545aa8cbb | |||
| 74ff026b92 | |||
| 257ee610b7 | |||
| 79b9351433 | |||
| 359f8f3387 | |||
| 1f7c856fdb | |||
| 58a5a2c3b2 | |||
| 54f064247e | |||
| 3f35a49f50 | |||
| c16c929054 | |||
| be6a922554 | |||
| 3711ab342c | |||
| 908f7b0f5f | |||
| 1be6949c2d | |||
| fc4e8a9915 | |||
| 413e607258 | |||
| 7847c813a5 | |||
| 956549c320 | |||
| 709e4b818c | |||
| 8a6f0a35bd | |||
| b218db0f63 | |||
| 8fe483a5e0 | |||
| 3a555592ff | |||
| 4fb39abac9 | |||
| fbaf85ae9b | |||
| 11a0427883 | |||
| d23e188d64 | |||
| d70ee216c8 | |||
| 0cd42556f5 | |||
| 3daec83a7f | |||
| ed44fd84d9 | |||
| be9d2e3486 | |||
| dc78de8fdd | |||
| 21f1dbb5af | |||
| 7bc31391fa | |||
| e932d118b9 | |||
| 874386987a | |||
| 2b27a9d038 | |||
| 293e0ed23c | |||
| c322e0455d | |||
| 9960b4376f | |||
| 1d3adbba45 | |||
| 07d1aba216 | |||
| b69d40a86b | |||
| 633f9eaaef | |||
| 2edf7bc481 | |||
| 92620b782b | |||
| 06ca255bb1 | |||
| 5baa3a423a | |||
| b56e622ea0 | |||
| 9fad01dd0c | |||
| f2c751a71f | |||
| 06d9244c80 | |||
| 6004058c50 | |||
| cc1e1ae435 | |||
| be6d351bb3 | |||
| eac89e0119 | |||
| e035236314 | |||
| ea0c6a1cf8 | |||
| 48cc58f04c | |||
| 6c97c550ed | |||
| 50b3f1d170 | |||
| 601b16a985 | |||
| ecb0fde9c5 | |||
| 81be7627eb | |||
| f94929daae | |||
| 8d7ab7861c | |||
| ce3f17d45f | |||
| 1354181f84 | |||
| 395d2727c6 | |||
| 7f67d9c4da | |||
| 2c5b5cda7d | |||
| 4fd95efe57 | |||
| fb223bb1bb | |||
| 831f136cd1 | |||
| 6ad9ad52af | |||
| b28d5e6456 | |||
| d7f9987ede | |||
| 86aaeae857 | |||
| 642bc3db80 | |||
| af0ca497d5 | |||
| 75f62ee5f7 | |||
| bd63d1576b | |||
| a4c521203c | |||
| b8aba5459e | |||
| f0507b973f | |||
| 58a9366bf5 | |||
| 5b298d17a7 | |||
| bca3ceae78 | |||
| d8c0ecbd1d | |||
| 133b154023 | |||
| 95baa8b943 | |||
| 0e433640c4 | |||
| e1faf6587c | |||
| 939d298809 | |||
| 1ae771ee80 | |||
| c5b14925bd | |||
| fa40e30ee3 | |||
| 8736dd5165 | |||
| 4917b34291 | |||
| 29d3e0da89 | |||
| 9b68f5188f | |||
| ed3503370d | |||
| 43480170aa | |||
| fdaf18198f | |||
| aee5c48875 | |||
| 521715f7dd | |||
| 68572aefe2 | |||
| f179e15e5c | |||
| 731b65d65c | |||
| b584258946 | |||
| 6ff53717a4 | |||
| d123688395 | |||
| ca4a954d08 | |||
| 57fbde9b46 | |||
| d46ab216f4 | |||
| b6bee7e9de | |||
| 0dd06a572b | |||
| 7272291eb7 | |||
| 57be36482c | |||
| c7d180b3ad | |||
| ed6913e2ad | |||
| 9f79e144ce | |||
| 185789dfb3 | |||
| 5445b67b41 | |||
| 66286a80c9 | |||
| a2e4d559bc | |||
| d9addcf469 | |||
| f32c069be5 | |||
| c55ce91fb9 | |||
| 7ba00fb853 | |||
| e4e2626823 | |||
| 48178b01c2 | |||
| 8b847e4de0 | |||
| 2a76ff2a2e | |||
| caa24b9ae6 | |||
| 0a3d938f8a | |||
| 1ea4960c29 | |||
| 7013e954bc | |||
| ec8c36260c | |||
| cfeb39ef5c | |||
| b56bda5aaa | |||
| a9857da213 | |||
| 3b3ec14acd | |||
| c499309897 | |||
| b813690c67 | |||
| 714f29e185 | |||
| d752d4d926 | |||
| 050cea0c51 | |||
| 5a20aeab03 | |||
| 748d13e56e | |||
| e9f3bc919b | |||
| b17dda9697 | |||
| 729ef4fa58 | |||
| e9cc5ea800 | |||
| 090b976083 | |||
| bd32b0ad24 | |||
| d86edbc3f2 | |||
| a7c7aa7466 | |||
| 60d2cbeb69 | |||
| 7bd76e6b5e | |||
| 9f198a12b6 | |||
| 86634b140c | |||
| 72da218778 | |||
| 89571ef876 | |||
| b925584f9c | |||
| dc6bb27f57 | |||
| 1c200251e8 | |||
| 6f0decb0ff | |||
| 812151c51d | |||
| 14715e2c32 | |||
| bdad58b3a7 | |||
| c79b0eebe9 | |||
| 1a7efab0e6 | |||
| 5df57bcbda | |||
| a6cea16fef | |||
| 76d4555f74 | |||
| de05c7a632 | |||
| 0d07828edc | |||
| bb50755b2e | |||
| 527609982a | |||
| 32b5e37486 | |||
| 01cb0e77be | |||
| 419f209346 | |||
| 2338e097c4 | |||
| 1fdd29c308 | |||
| c6a2660a2e | |||
| b6e185284b | |||
| ade8879d54 | |||
| 04a1ec81d7 | |||
| 015be2da3a | |||
| 2650360476 | |||
| a786a661b2 | |||
| d42e7eca38 | |||
| 074a00e2ae | |||
| f7b8901fd2 | |||
| db5e374880 | |||
| d2fed86772 | |||
| a8b1c3658e | |||
| c01d530cde | |||
| 85598e297b | |||
| 71b00ba7f6 | |||
| 82acd5aedb | |||
| 81f4b1e4cc | |||
| 7c80613f1b | |||
| d2c7ac3cd8 | |||
| ceec6adf1a | |||
| 2f6247a0ed | |||
| 87034055b4 | |||
| 0093289888 | |||
| f8037c08df | |||
| 24a7d469da | |||
| dffbaeee6b | |||
| 2507aa5804 | |||
| c45d2cb856 | |||
| 7e5b5b45ec | |||
| 739d22b5d3 | |||
| 070f449c57 | |||
| 43c544b4d0 | |||
| f76c960846 | |||
| a5640cb4b1 | |||
| 222c017e0d | |||
| 90c9e1de94 | |||
| 38e9b14a87 | |||
| e8bd259952 | |||
| 07736ac5f3 | |||
| 21bb2884b5 | |||
| be6811d311 | |||
| b323bd8980 | |||
| fb609dba72 | |||
| 7445f7388a | |||
| f5e2f54979 | |||
| 39ef6a21f0 | |||
| 0edc9292b3 | |||
| 215e4d6008 | |||
| 674a131060 | |||
| 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 | |||
| 778e8571d2 | |||
| e71356805c | |||
| 1b4569f316 | |||
| 1c5cab61aa | |||
| b01c7166c4 | |||
| 49166b553f | |||
| ddb815d2cb | |||
| 30aafbd50f | |||
| 25612dc872 | |||
| 6ce92f19e0 | |||
| 18adca5396 | |||
| 8087810ac2 | |||
| 3e99ad04c7 | |||
| 0a1eb9c8a2 | |||
| 5e0b696fef | |||
| adacb612a4 | |||
| f19fd6187a | |||
| 82630bdbf5 | |||
| c11e70585a | |||
| 045abdd919 | |||
| f9ffd5e5f1 | |||
| 407a3da80e | |||
| 68e3f11a74 | |||
| c786ed06e1 | |||
| 2765fa5575 | |||
| 835df17c5a | |||
| 68e0c685db | |||
| 352fa46668 | |||
| 87d8db3218 | |||
| 458f290956 | |||
| 0c268974f6 | |||
| 33927b17c7 | |||
| f8c492aadd | |||
| 22bcc07047 | |||
| ca7479609c | |||
| d1adbf1f50 | |||
| 321b19adbe | |||
| d86fba5c56 | |||
| 9f00bb182d | |||
| e69751361a | |||
| 57447fb562 | |||
| 9c022c300e | |||
| 9614d50af9 | |||
| 42f9f6f03d | |||
| 445d8af2e3 | |||
| 2769b268e8 | |||
| 5a4c9e172e | |||
| 1c0595f1dc | |||
| 61ba198cc2 | |||
| 43e4ae11b2 | |||
| 54d0ab0e3f | |||
| b74048502f | |||
| e86f3b6491 | |||
| 64a68729a5 | |||
| a289453eaf | |||
| 848c4dee8b | |||
| a750b2718d | |||
| 071242f217 | |||
| ceefc62cf0 | |||
| 738c6972a4 | |||
| e8ab35abb8 | |||
| efba2dabb3 | |||
| 64cb0d88bd | |||
| 4bfed38f92 | |||
| 96fd5872a7 | |||
| b53cdbc27a | |||
| ebdd27d251 | |||
| 7b97e2d9bb | |||
| afe1d8a8ee | |||
| 8833aa841c | |||
| 52aed1a5b6 | |||
| 2e736a7306 | |||
| 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 | |||
| 217f40e6ec | |||
| e8d4a17265 | |||
| 6dc4f5f714 | |||
| f2447ad1dc | |||
| 6064dc8253 | |||
| 59cbbf845a | |||
| fac25d9a1f | |||
| 3a277a1658 | |||
| d23f8ee8da | |||
| 4dde26d7fd | |||
| c3bb0ec1f8 | |||
| a36035f4fa | |||
| b4ad468974 | |||
| 04ae04d928 | |||
| 24cd69c075 | |||
| 597d3c6433 | |||
| 3b588e39b3 | |||
| 60d273db6e | |||
| 7ee10516db | |||
| 9468b6e806 | |||
| 56d76705ff | |||
| 3353ff3dd0 | |||
| 9e45652356 | |||
| d83e859d84 | |||
| 4195dfe755 | |||
| 55aa48b544 | |||
| 78593561d2 | |||
| 0075ae973c | |||
| 5d3c846ed7 | |||
| 17c82175c2 | |||
| ddeea33a02 | |||
| 85384bbc1d | |||
| e322c39942 | |||
| 64e8560bca | |||
| f4d8224164 | |||
| ae99b8254e | |||
| c62274c103 | |||
| 70d449ba1c | |||
| aa5e5b8394 | |||
| f914263d0e | |||
| a2987a4bea | |||
| 064af84e38 | |||
| 462543c95d | |||
| b1ff602e74 | |||
| 2d80083177 | |||
| f14d775a78 | |||
| d3634439af | |||
| f0083a3227 | |||
| b509908b2a | |||
| 6490a7a8c3 | |||
| 162764e508 | |||
| 5d99a54c58 | |||
| 7252b83ef2 | |||
| a0011f8e90 | |||
| 04ce2a3be9 | |||
| a10816efd4 | |||
| d47d1cc221 | |||
| 79a2005505 | |||
| 5f7a28ff3a | |||
| 5029179b83 | |||
| 8d65a9e01e | |||
| e00fc4461c | |||
| 98b6269f11 | |||
| 1bfb6f49e1 | |||
| b8ee219a5f | |||
| 40db9f36d5 | |||
| c2f1c033c8 | |||
| d8764a7be6 | |||
| 33879662e8 | |||
| 6be21169c6 | |||
| 2f00fa57b2 | |||
| 7a8780a59a | |||
| 230e07cc83 | |||
| 5dd9e4ae24 | |||
| 3194d9fcc9 | |||
| 5145a38e8d | |||
| c32eff2947 | |||
| 4070552d36 | |||
| ad1c024455 | |||
| 66b3f9689d | |||
| 7598debcf6 | |||
| 41a24aeffd | |||
| 01e8c561bb | |||
| 71a0646065 | |||
| 515bf1de51 | |||
| c8f5a1ccb6 | |||
| 014be09d5b | |||
| a5072c457c | |||
| dab245a415 | |||
| ad88999c7b | |||
| 1ab959d8a8 | |||
| 92028763ad | |||
| cd43c68dc6 | |||
| 714ba82c0c | |||
| fee323588e | |||
| d906b07c55 | |||
| 20739d6df5 | |||
| 2d1c697402 | |||
| c96a94c164 | |||
| 41d346853e | |||
| ad919639c7 | |||
| 5ebcc6d6b5 | |||
| 27c084707e | |||
| eeae8f3c1d | |||
| 36f86eb607 | |||
| e69460acfe | |||
| f7be4f6ae2 | |||
| ffd404ff6b | |||
| b99ec7b5ca | |||
| d7975ec9f2 | |||
| 0405f813a2 | |||
| ee66c66521 | |||
| 6e0ee1d079 | |||
| cda4216d0d | |||
| c2dd0a827c | |||
| 8492334353 | |||
| 9a7d04b684 | |||
| d90cd52913 | |||
| ce7ff64000 | |||
| 5f37a4d360 | |||
| 4215498fbc | |||
| f15b095e15 | |||
| 1e0c22e364 | |||
| fa66666033 | |||
| b034f195d3 | |||
| 8b943730b4 | |||
| fae4cbfb80 | |||
| a4966c12e6 | |||
| 378befbf4f | |||
| c9292ca9c3 | |||
| 579917ff58 | |||
| 399da05c61 | |||
| 20e5e6db03 | |||
| ca442e748d | |||
| 4e6e3c9ad3 | |||
| 09d7f1f9e4 | |||
| 7e6b7ba6f4 | |||
| aad109b533 | |||
| eeee427c97 | |||
| 2bc714f05f | |||
| a07aa08fd4 | |||
| 00ca93e14d | |||
| a5e641185b | |||
| ba7433dade | |||
| 99f4263417 | |||
| a5c16f9d0b | |||
| 2d82a2a904 | |||
| 286366333f | |||
| a90abe74ae | |||
| 9e72e3aaeb | |||
| 0af4b33056 | |||
| 62fc9cc6ea | |||
| 2b54f90b97 | |||
| cfd1724cb0 | |||
| f6d9feda07 | |||
| be44b0c39a | |||
| 5e3f4acd78 | |||
| fd4557a8e6 | |||
| 28f4205bcd | |||
| 7b70f5c362 | |||
| 7f25099acd | |||
| fd84d8d893 | |||
| 49bee20f8e | |||
| 49cbb27222 | |||
| 90b4c7b1b6 | |||
| aa57b8223c | |||
| db93a6ddc8 | |||
| 41457846e2 | |||
| 08f1b8e3d9 | |||
| dc5460c120 | |||
| 6a6cb929e1 | |||
| fb56d6b64b | |||
| 2ed0111866 | |||
| ccc615cea6 | |||
| 97be632ed8 | |||
| 588d9b6528 | |||
| 4d791e96e3 | |||
| deaa85ad09 | |||
| 415a937a7e | |||
| e15906aeb8 | |||
| 61f9470d84 | |||
| 853adebbcd | |||
| 0c39747462 | |||
| 311789b4dc | |||
| 6c52fec789 | |||
| 8b18c901f4 | |||
| a50ae54c6a | |||
| 7d9e48a8a6 | |||
| e75f76a1e6 | |||
| 6026246a4b | |||
| e5ac86f029 | |||
| 51f9006a59 | |||
| 1ef8b48d2a | |||
| b19f2e06a8 | |||
| 8912749a75 | |||
| 7c37a54bb2 | |||
| 537c0a03d5 | |||
| 033719036f | |||
| 6e5552d929 | |||
| 58edda4a9c | |||
| 78a32cacc9 | |||
| 2a77a97cc2 | |||
| 07f41ae7d1 | |||
| 4bde825ba0 | |||
| f222b97ca1 | |||
| 48387caea9 | |||
| 8832fc1a0c | |||
| ad5aa9912b | |||
| 43c6e8b72b | |||
| 650c9154b5 | |||
| deeec06e03 | |||
| 2737e60305 | |||
| 662e2f746b | |||
| 43488a1145 | |||
| 6d0bd6a66d | |||
| da76a7a174 | |||
| 36d57c9419 | |||
| 43c75d6d76 | |||
| e5165b8aa5 | |||
| 28b52a7114 | |||
| 8f4bf262b6 | |||
| c83351c201 | |||
| e9306c8965 | |||
| b0ca0c79db | |||
| 71912aeb1d | |||
| 62827cfefe | |||
| bfd209d238 | |||
| a6359e176a | |||
| 25378ac9e0 | |||
| 0fc9bc54af | |||
| d89734a682 | |||
| 318377993c | |||
| 97ed959b62 | |||
| de40dd7802 | |||
| a6d032db7b | |||
| 9453a0c4f7 | |||
| 39138f818b | |||
| ba62daa43c | |||
| d146c50cf0 | |||
| bd6d36fa31 | |||
| c502a8a06d | |||
| 952b837a09 | |||
| 4aa8a67e99 | |||
| 0f1a234076 | |||
| 1b61c97a44 | |||
| 43424a4727 | |||
| 27a777a3e7 | |||
| c5809e2f71 | |||
| 632bbd399b | |||
| 966f8e737d | |||
| d6297d2300 | |||
| a85ce60704 | |||
| b17f01425c | |||
| 1573b011b6 | |||
| 584394be4f | |||
| 5f568e7516 | |||
| 188a96bb4b | |||
| f066f4ff4b | |||
| 285a04853f | |||
| 255f810b6c | |||
| 84b8501b63 | |||
| 3e51248aae | |||
| 2054b39cc7 | |||
| 979ba19958 | |||
| 00d469ca9b | |||
| 268367bfbe | |||
| f7f6143f34 | |||
| df7428f8c0 | |||
| 019742bfc7 | |||
| 6d20008857 | |||
| 86adf5093f | |||
| fdedacaa51 | |||
| 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 | |||
| 8b9cfb3082 | |||
| df71dd575e | |||
| 23d42e1883 | |||
| 6ee6c36483 | |||
| 1e67f88a7f | |||
| d616b26fc0 | |||
| c291842226 | |||
| 544352a288 | |||
| 33e0f03c37 | |||
| 333053deb8 | |||
| 5a18717615 | |||
| 6719ad01f0 | |||
| 9d35a3842b | |||
| 5f73e4d1c2 | |||
| 0cca4e0852 | |||
| a823528eef | |||
| 3c413f3088 | |||
| d155da245a | |||
| 5908b3a07b | |||
| b09c054c32 | |||
| 88c207d285 | |||
| 442e55e14b | |||
| 3be88f7d38 | |||
| 955f1bede9 | |||
| ededc79153 | |||
| 1f3c0bd922 | |||
| 143c29ea10 | |||
| 1250da259d | |||
| 38707d4f67 | |||
| b5b3c61be0 | |||
| 64f702b826 | |||
| fdef4d29bd | |||
| 2e549a3825 | |||
| 7aa352d1ca | |||
| 94f82683de | |||
| 849b7cefa5 | |||
| f891adfaa9 | |||
| b29818cb96 | |||
| 21fdd1364c | |||
| 966790eb47 | |||
| 6b3dd56772 | |||
| 10f89bd445 | |||
| 32f580eca9 | |||
| b858656fde | |||
| accc78709c | |||
| 6c4aafb174 | |||
| 2ac8d2c5fd | |||
| fdd7a43d96 | |||
| e6a27ba94a | |||
| c3cb9e042a | |||
| 1194bafcad | |||
| 22124fc530 | |||
| 39d3e3e0f2 | |||
| 9ff14d9f08 | |||
| 65a7fa342c | |||
| ad72ce7347 | |||
| 360ccab3df | |||
| a05adeb264 | |||
| 6b0559b13b | |||
| 93bb589eb7 | |||
| f923bd7fe0 | |||
| af1ed4425c | |||
| c14dbe8fd5 |
+25
-18
@@ -57,7 +57,7 @@ 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 that in the ResearchKit framework.
|
||||
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
|
||||
@@ -75,13 +75,19 @@ 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.
|
||||
|
||||
When adding UI driven components, make sure that they are accessible.
|
||||
Follow the steps outlined in the [Best Practices](../../wiki/best-practices)
|
||||
section under Accessibility. Before submitting the pull request, you should
|
||||
audit your components with Voice Over (or other relevant assistive technologies)
|
||||
enabled.
|
||||
|
||||
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
|
||||
`ResearchKit.strings` table so that they can be picked up and
|
||||
localized in the next release cycle.
|
||||
|
||||
|
||||
@@ -160,21 +166,22 @@ The `master` branch is used for work in progress. On `master`:
|
||||
project:
|
||||
|
||||
1. If you are submitting a patch to the existing codebase, you
|
||||
represent that you have the right to license the patch to the
|
||||
community, and agree by submitting the patch that your changes are
|
||||
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)).
|
||||
Please also add 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 copied from another source), please include 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 the community, and agree that your file is licensed under the
|
||||
ResearchKit BSD license.
|
||||
3. If you aren't the author of the patch, you agree to include 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. Generally we can only take in patches that
|
||||
are BSD-licensed in order to maintain license compatibility within the
|
||||
project.
|
||||
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.
|
||||
|
||||
@@ -4,19 +4,19 @@ ResearchKit Framework
|
||||
The ResearchKit™ framework is an open source software framework that makes it easy to
|
||||
create apps for medical research or for other research projects.
|
||||
|
||||
* Getting Started: [Getting Started](#gettingstarted)
|
||||
* 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 Started](#gettingstarted)
|
||||
* Documentation:
|
||||
* [Programming Guide](http://researchkit.org/docs/docs/Overview/GuideOverview.html)
|
||||
* [Framework Reference](http://researchkit.org/docs/index.html)
|
||||
* [Best Practices](../../wiki/best-practices)
|
||||
* [Contributing to ResearchKit](CONTRIBUTING.md)
|
||||
* [Website](http://researchkit.org) and [Blog](http://researchkit.org/blog.html)
|
||||
* [ResearchKit BSD License](#license)
|
||||
|
||||
Getting More Information
|
||||
========================
|
||||
|
||||
* Join [researchkit-users](https://lists.apple.com/mailman/listinfo/researchkit-users) for discussing uses of the ResearchKit framework and related projects.
|
||||
* 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/)
|
||||
* Join the [ResearchKit Forum](https://forums.developer.apple.com/community/researchkit) for discussing uses of the ResearchKit framework and related projects.
|
||||
|
||||
Use Cases
|
||||
===========
|
||||
@@ -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.github.io/docs/docs/Survey/CreatingSurveys.html)
|
||||
presented modally on an iPhone, iPod Touch, or iPad. See *[Creating Surveys](http://researchkit.org/docs/docs/Survey/CreatingSurveys.html)* for more information.
|
||||
|
||||
|
||||
Consent
|
||||
----------------
|
||||
|
||||
The ResearchKit framework provides visual consent templates that you can customize to
|
||||
explain the details of your research study and obtain a signature if needed. [Consent](http://researchkit.github.io/docs/docs/InformedConsent/InformedConsent.html)
|
||||
explain the details of your research study and obtain a signature if needed. See *[Obtaining Consent](http://researchkit.org/docs/docs/InformedConsent/InformedConsent.html)* for more information.
|
||||
|
||||
|
||||
Active Tasks
|
||||
@@ -45,7 +45,7 @@ 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.github.io/docs/docs/ActiveTasks/ActiveTasks.html)
|
||||
under semi-controlled conditions, while iPhone sensors actively collect data. See *[Active Tasks](http://researchkit.org/docs/docs/ActiveTasks/ActiveTasks.html)* for more information.
|
||||
|
||||
|
||||
Getting Started<a name="gettingstarted"></a>
|
||||
@@ -55,8 +55,9 @@ Getting Started<a name="gettingstarted"></a>
|
||||
Requirements
|
||||
------------
|
||||
|
||||
The primary ResearchKit framework codebase supports iOS and requires Xcode 6.3
|
||||
or newer. The ResearchKit framework has a Base SDK version of 8.0, meaning that apps
|
||||
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.
|
||||
|
||||
|
||||
@@ -110,7 +111,7 @@ target as shown in the figure below.
|
||||
</figure>
|
||||
</center>
|
||||
|
||||
Note: You can also import ResearchKit into your project using a [dependency manager](./dependency_management.md) such as CocoaPods or Carthage.
|
||||
Note: You can also import ResearchKit into your project using a [dependency manager](./docs-standalone/dependency-management.md) such as CocoaPods or Carthage.
|
||||
|
||||
###2. Create a Step
|
||||
|
||||
@@ -124,7 +125,7 @@ the step `myStep`.
|
||||
|
||||
*Objective-C*
|
||||
|
||||
```objc
|
||||
```objc
|
||||
ORKInstructionStep *myStep =
|
||||
[[ORKInstructionStep alloc] initWithIdentifier:@"intro"];
|
||||
myStep.title = @"Welcome to ResearchKit";
|
||||
@@ -203,8 +204,8 @@ which you must implement in order to handle the completion of the task:
|
||||
*Swift*
|
||||
|
||||
```swift
|
||||
func taskViewController(taskViewController: ORKTaskViewController,
|
||||
didFinishWithReason reason: ORKTaskViewControllerFinishReason,
|
||||
func taskViewController(taskViewController: ORKTaskViewController,
|
||||
didFinishWithReason reason: ORKTaskViewControllerFinishReason,
|
||||
error: NSError?) {
|
||||
let taskResult = taskViewController.result
|
||||
// You could do something with the result here.
|
||||
@@ -214,7 +215,7 @@ func taskViewController(taskViewController: ORKTaskViewController,
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
If you now run your app, you should see your first ResearchKit framework
|
||||
instruction step:
|
||||
|
||||
@@ -245,22 +246,22 @@ following license unless another license is explicitly identified:
|
||||
|
||||
```
|
||||
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
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
# ResearchKit Release Notes
|
||||
|
||||
|
||||
## ResearchKit 1.3 Release Notes
|
||||
|
||||
*ResearchKit 1.3* supports *iOS* and requires *Xcode 7.2* or newer. The minimum supported *Base SDK* is *8.0*.
|
||||
|
||||
In addition to general stability and performance improvements, *ResearchKit 1.3* includes the following new features and enhancements.
|
||||
|
||||
- **New Active Tasks**
|
||||
|
||||
- **9-Hole Peg Test**
|
||||
|
||||
*Contributed by [Julien Therier](https://github.com/julientherier).*
|
||||
|
||||
The *[9-Hole Peg Test] task* is used to test upper extremity functionality.
|
||||
|
||||
The test involves putting a variable variable number of pegs in a hole, and then removing them.
|
||||
|
||||
The test is documented in the scientific literature to measure the *[MSFC score in Multiple Sclerosis](http://www.nationalmssociety.org/For-Professionals/Researchers/Resources-for-Researchers/Clinical-Study-Measures/9-Hole-Peg-Test-(9-HPT))* or *[Parkinson's Disease](http://www.ncbi.nlm.nih.gov/pubmed/22020457)*.
|
||||
|
||||
- **Sample App**
|
||||
|
||||
*Contributed by [Apple Inc](https://github.com/researchkit).*
|
||||
|
||||
The *[Sample App]* serves as a template application that combines different modules from the ResearchKit framework.
|
||||
|
||||
- **Account Module**
|
||||
|
||||
*Contributed by [Apple Inc](https://github.com/researchkit).*
|
||||
|
||||
The *[Account Module]* provides steps to facilitate account creation and login.
|
||||
|
||||
The module includes the following steps:
|
||||
|
||||
1. Registration to create a new account.
|
||||
2. Verification to verify email.
|
||||
3. Login to allow registered users to login.
|
||||
|
||||
- **Passcode with Touch ID**
|
||||
|
||||
*Contributed by [Apple Inc](https://github.com/researchkit).*
|
||||
|
||||
The *[Passcode with Touch ID] module* provides the ability to secure any ResearchKit application with a pin entry.
|
||||
|
||||
This module includes a *Keychain Wrapper* that stores the passcode on the device, as well as the option to use Touch ID on compatible devices. The passcode module supports 4-pin and 6-pin entries.
|
||||
|
||||
The passcode module can be used in the following scenarios:
|
||||
|
||||
1. Passcode creation step which can be used as part of onboarding to create a passcode and store it in the keychain.
|
||||
2. Passcode authentication view controller which can be presented modally when appropriate.
|
||||
3. Passcode modification view controller which allows the participant to change their passcode.
|
||||
|
||||
- **Other Improvements**
|
||||
|
||||
- **Optional Form Items**
|
||||
|
||||
*Contributed by [Ricardo Sánchez-Sáez](https://github.com/rsanchezsaez).*
|
||||
|
||||
Implements the `ORKFormItem` `optional` property.
|
||||
|
||||
The *Continue/Done* button of form steps enables only if:
|
||||
|
||||
- At least one form item has an answer.
|
||||
- All answered form items are valid.
|
||||
- All the non-optional form items have answers.
|
||||
|
||||
- **Location Question**
|
||||
|
||||
*Contributed by [Quintiles](https://github.com/QuintilesRK).*
|
||||
|
||||
A *Location Question* can be used to request details about the participant's current location or a specific address.
|
||||
|
||||
The question uses *MapKit* to provides a visual representation for the specified address.
|
||||
|
||||
- **Wait Step**
|
||||
|
||||
*Contributed by [Quintiles](https://github.com/QuintilesRK).*
|
||||
|
||||
The *Wait Step* provides a step to be used in-between steps when additional data processing is required.
|
||||
|
||||
The step supports both indeterminate and determinate progress views, as well as the ability to show text status updates.
|
||||
|
||||
- **Validated Text Answer Format**
|
||||
|
||||
*Contributed by [Quintiles](https://github.com/QuintilesRK).*
|
||||
|
||||
The *Validated Text Answer Format* enhances the existing *Text Answer Format* by providing input validation using a regular expression.
|
||||
|
||||
A valid *NSRegularExpression* object and an *error message* string are required to properly use this answer format.
|
||||
|
||||
|
||||
## ResearchKit 1.2 Release Notes
|
||||
|
||||
*ResearchKit 1.2* supports *iOS* and requires *Xcode 7.0* or newer. The minimum supported *Base SDK* is *8.0*.
|
||||
|
||||
In addition to general stability and performance improvements, *ResearchKit 1.2* includes the following new features and enhancements.
|
||||
|
||||
- **New Active Tasks**
|
||||
|
||||
- **Tower of Hanoi Task**
|
||||
|
||||
*Contributed by [coxy1989](https://github.com/coxy1989).*
|
||||
|
||||
The *[Tower of Hanoi](https://en.wikipedia.org/wiki/Tower_of_Hanoi#Applications) task* is frequently used in psychological research on problem solving.
|
||||
|
||||
It is a mathematical puzzle consisting of three rods and a number of disks of different sizes which can slide onto any rod. The puzzle starts with the disks in a stack in ascending order of size on one rod (the smallest at the top).
|
||||
|
||||
The objective of the puzzle is to move the entire stack to another rod, obeying the following rules:
|
||||
|
||||
1. Only one disk can be moved at a time.
|
||||
2. Each move consists of taking the upper disk from one of the stacks and placing it on top of another stack.
|
||||
3. No disk may be placed on top of a smaller disk.
|
||||
|
||||
- **Paced Serial Addition Test Task**
|
||||
|
||||
*Contributed by [Julien Therier](https://github.com/julientherier).*
|
||||
|
||||
The *Paced Serial Addition Test task* provides adaptations of both the *Paced Auditory Serial Addition Test (PASAT)* and the *Paced Visual Serial Addition Test (PVSAT)*.
|
||||
|
||||
The *[PASAT](https://en.wikipedia.org/wiki/Paced_Auditory_Serial_Addition_Test)* is a neuropsychological test used to assess capacity and rate of information processing and sustained and divided attention.
|
||||
|
||||
Both tests are documented in the scientific literature ([Fos et al., 2000](http://www.ncbi.nlm.nih.gov/pubmed/11125707); [Nagels et al., 2005](http://www.ncbi.nlm.nih.gov/pubmed/15823678)) as a measure of the [*Multiple Sclerosis Functional Score*](http://www.nationalmssociety.org/For-Professionals/Researchers/Resources-for-Researchers/Clinical-Study-Measures/Multiple-Sclerosis-Functional-Composite-%28MSFC%29).
|
||||
|
||||
This task generates a series of single digits (for example, 60 of them), at the specific frequency (for example, one new digit every 2 or 3 seconds). The user must add the newly presented digit to the one prior to it.
|
||||
|
||||
- **Timed Walk Task**
|
||||
|
||||
*Contributed by [Julien Therier](https://github.com/julientherier).*
|
||||
|
||||
The *Timed Walk task* measures gait speed and is an adaptation of the [*Timed 25-Foot Walk*](http://www.nationalmssociety.org/For-Professionals/Researchers/Resources-for-Researchers/Clinical-Study-Measures/Timed-25-Foot-Walk-%28T25-FW%29) in the context of *multiple sclerosis*.
|
||||
|
||||
Gait speed has been demonstrated to be a useful and reliable functional measure of walking ability. When administering the *Timed Walk Task*, patients are allowed to use assistive devices (canes, crutches, walkers).
|
||||
|
||||
- **Charts Module**
|
||||
|
||||
*Contributed by [coxy1989](https://github.com/coxy1989) and [Ricardo Sánchez-Sáez](https://github.com/rsanchezsaez).*
|
||||
|
||||
A *Charts module* has been implemented. It features three chart types: a *pie chart* (`ORKPieChartView`), a *line graph chart* (`ORKLineGraphChartView`), and a *discrete graph chart* (`ORKDiscreteGraphChartView`).
|
||||
|
||||
The views in the *Charts module* can be used independently of the rest of *ResearchKit*. It doesn't automatically connect with any other *ResearchKit* module: the developer has to supply the data to be displayed through the views' `dataSources`, which allows for maximum flexibility.
|
||||
|
||||
- **Other Improvements**
|
||||
|
||||
- **Scale Answer Format**
|
||||
|
||||
*Contributed by [Apple Inc](https://github.com/researchkit).*
|
||||
|
||||
*Discrete scales* now support *text choice* labels, and all *scales* support images in place of the minimum and maximum range labels.
|
||||
|
||||
- **Result Predicates**
|
||||
|
||||
*Contributed by [Ricardo Sánchez-Sáez](https://github.com/rsanchezsaez).*
|
||||
|
||||
The predicate-building methods in `ORKResultPredicate` now use the new `ORKResultSelector` class for unequivocally identifying a *question step result* or a *form item result*.
|
||||
|
||||
This eliminates ambiguity when matching results with the same inner scope identifier. For example, a *form item result* can have the same identifier as a *question step result* or as another *form item result* in a different *form step*, and you can now match them separately.
|
||||
|
||||
|
||||
## ResearchKit 1.1 Release Notes
|
||||
|
||||
*ResearchKit 1.1* supports *iOS* and requires *Xcode 6.3* or newer. The minimum supported *Base SDK* is *8.0*.
|
||||
|
||||
In addition to general stability and performance improvements, *ResearchKit 1.1* includes the following new features and enhancements.
|
||||
|
||||
- **Navigable Ordered Task**
|
||||
|
||||
*Contributed by [Ricardo Sánchez-Sáez](https://github.com/rsanchezsaez).*
|
||||
|
||||
A new type of *conditional ordered task* (`ORKNavigableOrderedTask`) has been implemented.
|
||||
|
||||
The developer can use the `ORKStepNavigationRule` subclasses to dynamically navigate between the task steps:
|
||||
- `ORKPredicateStepNavigationRule` allows to make conditional jumps by matching previous results (either those of the the ongoing task, or those of any previously stored task result tree). You typically use the class methods in the `ORKResultPredicate` class to match answers in the most commonly used result types.
|
||||
- `ORKDirectStepNavigationRule` provides support for unconditional jumps.
|
||||
|
||||
- **New Active Tasks**
|
||||
- **Reaction Time Task**
|
||||
|
||||
*Contributed by [coxy1989](https://github.com/coxy1989).*
|
||||
|
||||
The *Reaction Time Task* is an adaptation of the [*Simple Reaction Time test (SRT)*](http://www.cambridgecognition.com/tests/simple-reaction-time-srt). *SRT* measures reaction time through delivery of a known stimulus to a known location to elicit a known response.
|
||||
|
||||
This test is deployed in a range of research questions across fields including medicine, sports science and psychology.
|
||||
|
||||
Although it classically involves pressing the space bar or clicking a mouse in response to an event on screen, the *ResearchKit* implementation relies on the study participant shaking the device when she sees a blue circle on the screen, which we think is more correlatable to a true stimulus reaction test.
|
||||
|
||||
- **Tone Audiometry Task**
|
||||
|
||||
*Contributed by [Vincent Tourraine](https://github.com/vtourraine).*
|
||||
|
||||
The *Tone Audiometry Task* is an adaptation of the [*Pure Tone Audiometry test (PTA)*](https://en.wikipedia.org/wiki/Pure_tone_audiometry). *PTA* is a key hearing test used to identify hearing threshold levels of an individual, enabling determination of the degree, type and configuration of a hearing loss.
|
||||
|
||||
The *ResearchKit* implementation generates a series of pure sinusoid sounds, with different frequencies and on different channels (left or right). The test starts at the minimum volume and is gradually increased until the participant perceives it and taps a button. At that time, the current sound amplitude, frequency and channel are recorded.
|
||||
|
||||
- **Scale Answer Format Enhancements**
|
||||
|
||||
*Contributed by [Ricardo Sánchez-Sáez](https://github.com/rsanchezsaez) and [Bruce Duncan](https://github.com/brucehappy).*
|
||||
|
||||
Support for discrete and continuous *vertical scales* has been added. Some questions, like mood measurement or symptom severity measurement queries may be more naturally presented using a *vertical scale*.
|
||||
|
||||
The *Scale Answer Format* has also been improved by making it usable within forms.
|
||||
|
||||
- **Image Capture Step**
|
||||
|
||||
*Contributed by [Bruce Duncan](https://github.com/brucehappy).*
|
||||
|
||||
An *Image Capture Step* has been added. The researcher can ask the participant to take pictures of relevant body parts. The researcher can provide a body part image template to facilitate the scale and orientation of the taken pictures.
|
||||
|
||||
- **iPad Support**
|
||||
|
||||
*Contributed by [Ricardo Sánchez-Sáez](https://github.com/rsanchezsaez) and [Apple Inc](https://github.com/researchkit).*
|
||||
|
||||
*iPad support* for all orientations has been implemented.
|
||||
|
||||
- **iPhone Landscape Support**
|
||||
|
||||
*Contributed by [Apple Inc.](https://github.com/researchkit) and [Ricardo Sánchez-Sáez](https://github.com/rsanchezsaez).*
|
||||
|
||||
*iPhone landscape orientation support* has been implemented.
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:ResearchKit.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Testing/ORKTest/ORKTest.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:samples/ORKCatalog/ORKCatalog.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:samples/ORKSample/ORKSample.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'ResearchKit'
|
||||
s.version = '1.1.2'
|
||||
s.version = '1.3.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/'
|
||||
|
||||
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
|
||||
|
||||
@@ -32,3 +32,4 @@
|
||||
// Shared header for accessibility functionality.
|
||||
#import "UIView+ORKAccessibility.h"
|
||||
#import "ORKAccessibilityFunctions.h"
|
||||
#import "ORKLineGraphAccessibilityElement.h"
|
||||
|
||||
@@ -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
|
||||
@@ -33,13 +33,13 @@
|
||||
#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 *const ActivityUnknown = @"unknown";
|
||||
static NSString *const ActivityStationary = @"stationary";
|
||||
static NSString *const ActivityWalking = @"walking";
|
||||
static NSString *const ActivityRunning = @"running";
|
||||
static NSString *const ActivityAutomotive = @"automotive";
|
||||
static NSString *const StartDateKey = @"startDate";
|
||||
static NSString *const EndDateKey = @"endDate";
|
||||
|
||||
static NSString *stringFromActivityConfidence(CMMotionActivityConfidence confidence) {
|
||||
NSDictionary *confidences = @{@(CMMotionActivityConfidenceHigh) : @"high",
|
||||
@@ -51,33 +51,32 @@ static NSString *stringFromActivityConfidence(CMMotionActivityConfidence confide
|
||||
static NSArray *activityArray(CMMotionActivity *activity) {
|
||||
NSMutableArray *array = [NSMutableArray array];
|
||||
if (activity.unknown) {
|
||||
[array addObject:kActivityUnknown];
|
||||
[array addObject:ActivityUnknown];
|
||||
}
|
||||
if (activity.stationary) {
|
||||
[array addObject:kActivityStationary];
|
||||
[array addObject:ActivityStationary];
|
||||
}
|
||||
if (activity.walking) {
|
||||
[array addObject:kActivityWalking];
|
||||
[array addObject:ActivityWalking];
|
||||
}
|
||||
if (activity.running) {
|
||||
[array addObject:kActivityRunning];
|
||||
[array addObject:ActivityRunning];
|
||||
}
|
||||
if (activity.automotive) {
|
||||
[array addObject:kActivityAutomotive];
|
||||
[array addObject:ActivityAutomotive];
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
static NSString *const kActivityKey = @"activity";
|
||||
|
||||
static NSString *const kConfidenceKey = @"confidence";
|
||||
static NSString *const ActivityKey = @"activity";
|
||||
static NSString *const ConfidenceKey = @"confidence";
|
||||
|
||||
@implementation CMMotionActivity (ORKJSONDictionary)
|
||||
|
||||
- (NSDictionary *)ork_JSONDictionary {
|
||||
return @{kConfidenceKey : stringFromActivityConfidence(self.confidence),
|
||||
kActivityKey : activityArray(self),
|
||||
kStartDateKey : ORKStringFromDateISO8601(self.startDate)};
|
||||
return @{ConfidenceKey : stringFromActivityConfidence(self.confidence),
|
||||
ActivityKey : activityArray(self),
|
||||
StartDateKey : ORKStringFromDateISO8601(self.startDate)};
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -33,16 +33,16 @@
|
||||
#import "ORKHelpers.h"
|
||||
|
||||
|
||||
static NSString *const kHKSampleIdentifierKey = @"type"; // For compatibility with Health XML export
|
||||
static NSString *const kHKUUIDKey = @"uuid";
|
||||
static NSString *const kHKSampleStartDateKey = @"startDate";
|
||||
static NSString *const kHKSampleEndDateKey = @"endDate";
|
||||
static NSString *const kHKSampleValue = @"value";
|
||||
static NSString *const kHKMetadataKey = @"metadata";
|
||||
static NSString *const kHKSourceKey = @"source";
|
||||
static NSString *const kHKUnitKey = @"unit";
|
||||
static NSString *const kHKCorrelatedObjectsKey = @"objects";
|
||||
// static NSString *const kHKSourceIdentifierKey = @"sourceBundleIdentifier";
|
||||
static NSString *const HKSampleIdentifierKey = @"type"; // For compatibility with Health XML export
|
||||
static NSString *const HKUUIDKey = @"uuid";
|
||||
static NSString *const HKSampleStartDateKey = @"startDate";
|
||||
static NSString *const HKSampleEndDateKey = @"endDate";
|
||||
static NSString *const HKSampleValue = @"value";
|
||||
static NSString *const HKMetadataKey = @"metadata";
|
||||
static NSString *const HKSourceKey = @"source";
|
||||
static NSString *const HKUnitKey = @"unit";
|
||||
static NSString *const HKCorrelatedObjectsKey = @"objects";
|
||||
// static NSString *const HKSourceIdentifierKey = @"sourceBundleIdentifier";
|
||||
|
||||
|
||||
@implementation HKSample (ORKJSONDictionary)
|
||||
@@ -52,31 +52,31 @@ static NSString *const kHKCorrelatedObjectsKey = @"objects";
|
||||
|
||||
// Type identification
|
||||
HKSampleType *sampleType = [self sampleType];
|
||||
mutableDictionary[kHKSampleIdentifierKey] = [sampleType identifier];
|
||||
mutableDictionary[HKSampleIdentifierKey] = [sampleType identifier];
|
||||
|
||||
// consider adding @"class" : NSStringFromClass(sampleType) ?
|
||||
|
||||
// Start and end dates
|
||||
NSDate *startDate = [self startDate];
|
||||
if (startDate) {
|
||||
mutableDictionary[kHKSampleStartDateKey] = ORKStringFromDateISO8601(startDate);
|
||||
mutableDictionary[HKSampleStartDateKey] = ORKStringFromDateISO8601(startDate);
|
||||
}
|
||||
|
||||
NSDate *endDate = [self endDate];
|
||||
if (endDate) {
|
||||
mutableDictionary[kHKSampleEndDateKey] = ORKStringFromDateISO8601(endDate);
|
||||
mutableDictionary[HKSampleEndDateKey] = ORKStringFromDateISO8601(endDate);
|
||||
}
|
||||
if (unit) {
|
||||
mutableDictionary[kHKUnitKey] = [unit unitString];
|
||||
mutableDictionary[HKUnitKey] = [unit unitString];
|
||||
}
|
||||
if ((options & ORKSampleIncludeUUID)) {
|
||||
NSUUID *uuid = [self UUID];
|
||||
if (uuid) {
|
||||
mutableDictionary[kHKUUIDKey] = [uuid UUIDString];
|
||||
mutableDictionary[HKUUIDKey] = 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) {
|
||||
id obj = metadata[k];
|
||||
@@ -85,13 +85,13 @@ static NSString *const kHKCorrelatedObjectsKey = @"objects";
|
||||
}
|
||||
}
|
||||
|
||||
mutableDictionary[kHKMetadataKey] = metadata;
|
||||
mutableDictionary[HKMetadataKey] = metadata;
|
||||
}
|
||||
|
||||
if (options & ORKSampleIncludeSource) {
|
||||
HKSource *source = [self source];
|
||||
if (source.name) {
|
||||
mutableDictionary[kHKSourceKey] = source.name;
|
||||
mutableDictionary[HKSourceKey] = source.name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,8 +115,8 @@ static NSString *const kHKCorrelatedObjectsKey = @"objects";
|
||||
- (NSDictionary *)ork_JSONDictionaryWithOptions:(ORKSampleJSONOptions)options unit:(HKUnit *)unit {
|
||||
NSMutableDictionary *dictionary = [self ork_JSONMutableDictionaryWithOptions:options unit:unit];
|
||||
|
||||
NSInteger value = [self value];
|
||||
dictionary[kHKSampleValue] = @(value);
|
||||
NSInteger value = self.value;
|
||||
dictionary[HKSampleValue] = @(value);
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
@@ -136,7 +136,7 @@ static NSString *const kHKCorrelatedObjectsKey = @"objects";
|
||||
|
||||
HKQuantity *quantity = [self quantity];
|
||||
double value = [quantity doubleValueForUnit:unit];
|
||||
dictionary[kHKSampleValue] = @(value);
|
||||
dictionary[HKSampleValue] = @(value);
|
||||
|
||||
|
||||
return dictionary;
|
||||
@@ -151,7 +151,7 @@ static NSString *const kHKCorrelatedObjectsKey = @"objects";
|
||||
NSMutableDictionary *mutableDictionary = [self ork_JSONMutableDictionaryWithOptions:options unit:nil];
|
||||
|
||||
// The correlated objects
|
||||
NSMutableArray *correlatedObjects = [NSMutableArray arrayWithCapacity:[sampleTypes count]];
|
||||
NSMutableArray *correlatedObjects = [NSMutableArray arrayWithCapacity:sampleTypes.count];
|
||||
for (HKSample *sample in self.objects) {
|
||||
NSUInteger idx = [sampleTypes indexOfObject:sample.sampleType];
|
||||
if (idx == NSNotFound) {
|
||||
@@ -160,7 +160,7 @@ static NSString *const kHKCorrelatedObjectsKey = @"objects";
|
||||
|
||||
[correlatedObjects addObject:[sample ork_JSONDictionaryWithOptions:options unit:units[idx]]];
|
||||
}
|
||||
mutableDictionary[kHKCorrelatedObjectsKey] = correlatedObjects;
|
||||
mutableDictionary[HKCorrelatedObjectsKey] = correlatedObjects;
|
||||
|
||||
return mutableDictionary;
|
||||
}
|
||||
|
||||
@@ -86,16 +86,16 @@
|
||||
|
||||
self.motionManager = [self createMotionManager];
|
||||
|
||||
if (! _logger) {
|
||||
if (!_logger) {
|
||||
NSError *err = nil;
|
||||
_logger = [self makeJSONDataLoggerWithError:&err];
|
||||
if (! _logger) {
|
||||
if (!_logger) {
|
||||
[self finishRecordingWithError:err];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (! self.motionManager || ! self.motionManager.accelerometerAvailable) {
|
||||
if (!self.motionManager || !self.motionManager.accelerometerAvailable) {
|
||||
NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain
|
||||
code:NSFeatureUnsupportedError
|
||||
userInfo:@{@"recorder" : self}];
|
||||
@@ -103,23 +103,18 @@
|
||||
return;
|
||||
}
|
||||
|
||||
self.motionManager.accelerometerUpdateInterval = 1.0/_frequency;
|
||||
self.motionManager.accelerometerUpdateInterval = 1.0 / _frequency;
|
||||
|
||||
self.uptime = [NSProcessInfo processInfo].systemUptime;
|
||||
|
||||
[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];
|
||||
@@ -177,11 +172,6 @@
|
||||
@end
|
||||
|
||||
|
||||
@interface ORKAccelerometerRecorderConfiguration ()
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKAccelerometerRecorderConfiguration
|
||||
|
||||
#pragma clang diagnostic push
|
||||
|
||||
@@ -32,7 +32,9 @@
|
||||
#import <ResearchKit/ORKDefines.h>
|
||||
#import <ResearchKit/ORKStep.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <HealthKit/HealthKit.h>
|
||||
|
||||
@class ORKRecorderConfiguration;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@@ -191,7 +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)
|
||||
@@ -203,7 +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;
|
||||
@property (nonatomic, readonly, nullable) NSSet<HKObjectType *> *requestedHealthKitTypesForReading;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -156,10 +156,10 @@
|
||||
(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];
|
||||
}
|
||||
|
||||
@@ -84,15 +84,14 @@
|
||||
view.isAccessibilityElement = NO;
|
||||
}
|
||||
|
||||
[self setupConstraints];
|
||||
[self setNeedsUpdateConstraints];
|
||||
[self setUpConstraints];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setEnabled:(BOOL)enabled {
|
||||
_enabled = enabled;
|
||||
self.hidden = ! enabled;
|
||||
self.hidden = !enabled;
|
||||
[self setNeedsUpdateConstraints];
|
||||
}
|
||||
|
||||
@@ -111,91 +110,89 @@
|
||||
_imageView.image = image;
|
||||
}
|
||||
|
||||
- (void)setupConstraints {
|
||||
- (void)setUpConstraints {
|
||||
|
||||
const CGFloat TitleBaselineToValueBaseline = 40;
|
||||
const CGFloat ValueBaselineToBottom = 36;
|
||||
|
||||
NSMutableArray *additionalConstraints = [NSMutableArray array];
|
||||
NSMutableArray *constraints = [NSMutableArray array];
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(_titleLabel, _valueLabel, _imageView);
|
||||
[additionalConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_titleLabel]"
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_titleLabel]"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil
|
||||
views:views]];
|
||||
[additionalConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_titleLabel]|"
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_titleLabel]|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil
|
||||
views:views]];
|
||||
[additionalConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_imageView]-10-[_valueLabel]|"
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_imageView]-10-[_valueLabel]|"
|
||||
options:NSLayoutFormatAlignAllCenterY
|
||||
metrics:nil
|
||||
views:views]];
|
||||
[additionalConstraints addObject:[NSLayoutConstraint constraintWithItem:_valueLabel
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_valueLabel
|
||||
attribute:NSLayoutAttributeFirstBaseline
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_titleLabel
|
||||
attribute:NSLayoutAttributeLastBaseline
|
||||
multiplier:1.0
|
||||
constant:TitleBaselineToValueBaseline]];
|
||||
[additionalConstraints addObject:[NSLayoutConstraint constraintWithItem:self
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:self
|
||||
attribute:NSLayoutAttributeBottom
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_valueLabel
|
||||
attribute:NSLayoutAttributeLastBaseline
|
||||
multiplier:1.0
|
||||
constant:ValueBaselineToBottom]];
|
||||
[additionalConstraints addObject:[NSLayoutConstraint constraintWithItem:_valueHolder
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_valueHolder
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
[additionalConstraints addObject:[NSLayoutConstraint constraintWithItem:_valueLabel
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_valueLabel
|
||||
attribute:NSLayoutAttributeTop
|
||||
relatedBy:NSLayoutRelationGreaterThanOrEqual
|
||||
toItem:_valueHolder
|
||||
attribute:NSLayoutAttributeTop
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
[additionalConstraints addObject:[NSLayoutConstraint constraintWithItem:_valueLabel
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_valueLabel
|
||||
attribute:NSLayoutAttributeBottom
|
||||
relatedBy:NSLayoutRelationLessThanOrEqual
|
||||
toItem:_valueHolder
|
||||
attribute:NSLayoutAttributeBottom
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
[additionalConstraints addObject:[NSLayoutConstraint constraintWithItem:_valueHolder
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_valueHolder
|
||||
attribute:NSLayoutAttributeLeft
|
||||
relatedBy:NSLayoutRelationGreaterThanOrEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeLeft
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
[additionalConstraints addObject:[NSLayoutConstraint constraintWithItem:_valueHolder
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_valueHolder
|
||||
attribute:NSLayoutAttributeRight
|
||||
relatedBy:NSLayoutRelationLessThanOrEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeRight
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
for (NSLayoutConstraint *constraint in additionalConstraints) {
|
||||
constraint.priority = UILayoutPriorityRequired-2;
|
||||
for (NSLayoutConstraint *constraint in constraints) {
|
||||
constraint.priority = UILayoutPriorityRequired - 2;
|
||||
}
|
||||
|
||||
[NSLayoutConstraint activateConstraints:additionalConstraints];
|
||||
[NSLayoutConstraint activateConstraints:constraints];
|
||||
|
||||
|
||||
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;
|
||||
_zeroWidthConstraint = [NSLayoutConstraint constraintWithItem:self
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1.0
|
||||
constant:0.0];
|
||||
_zeroWidthConstraint.priority = UILayoutPriorityRequired - 1;
|
||||
[self setNeedsUpdateConstraints];
|
||||
}
|
||||
|
||||
- (void)updateConstraints {
|
||||
@@ -245,18 +242,18 @@
|
||||
[self addSubview:_leftView];
|
||||
[self addSubview:_rightView];
|
||||
[self addSubview:_metricKeyline];
|
||||
[self setupConstraints];
|
||||
[self setUpConstraints];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setupConstraints {
|
||||
- (void)setUpConstraints {
|
||||
|
||||
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];
|
||||
CGFloat scale = [UIScreen mainScreen].scale;
|
||||
NSArray *vertConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_leftView]|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil
|
||||
@@ -265,10 +262,10 @@
|
||||
|
||||
NSArray *horizConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_leftView]-s-[_rightView]-|"
|
||||
options:NSLayoutFormatAlignAllTop|NSLayoutFormatAlignAllBottom|NSLayoutFormatDirectionLeftToRight
|
||||
metrics:@{ @"s": @(1/scale) }
|
||||
metrics:@{ @"s": @(1.0 / scale) }
|
||||
views:views];
|
||||
for (NSLayoutConstraint *constraint in horizConstraints) {
|
||||
constraint.priority = UILayoutPriorityDefaultHigh+1;
|
||||
constraint.priority = UILayoutPriorityDefaultHigh + 1;
|
||||
}
|
||||
[constraints addObjectsFromArray:horizConstraints];
|
||||
|
||||
@@ -290,7 +287,7 @@
|
||||
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[_leftView][_metricKeyline(==s)]"
|
||||
options:NSLayoutFormatAlignAllTop|NSLayoutFormatDirectionLeftToRight
|
||||
metrics:@{ @"s": @(1/scale) }
|
||||
metrics:@{ @"s": @(1.0 / scale) }
|
||||
views:views]];
|
||||
NSLayoutConstraint *keylineBottom = [NSLayoutConstraint constraintWithItem:_metricKeyline
|
||||
attribute:NSLayoutAttributeBottom
|
||||
@@ -308,7 +305,7 @@
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1.0
|
||||
constant:ORKScreenMetricMaxDimension];
|
||||
maxWidthConstraint.priority = UILayoutPriorityRequired-2;
|
||||
maxWidthConstraint.priority = UILayoutPriorityRequired - 2;
|
||||
[constraints addObject:maxWidthConstraint];
|
||||
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ static NSTimeInterval timeIntervalFromMachTime(uint64_t delta) {
|
||||
- (instancetype)initWithDuration:(NSTimeInterval)duration interval:(NSTimeInterval)interval runtime:(NSTimeInterval)runtime handler:(ORKActiveStepTimerHandler)handler {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
if (! handler) {
|
||||
if (!handler) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Handler is required" userInfo:nil];
|
||||
}
|
||||
|
||||
@@ -224,7 +224,7 @@ static NSTimeInterval timeIntervalFromMachTime(uint64_t delta) {
|
||||
_preExistingRuntime += timeIntervalFromMachTime(now - _startTime);
|
||||
_startTime = 0;
|
||||
|
||||
if (! atFinish) {
|
||||
if (!atFinish) {
|
||||
// If we are atFinish, the task will be released after the handler completes
|
||||
[self queue_releaseBackgroundTask];
|
||||
}
|
||||
|
||||
@@ -45,6 +45,10 @@
|
||||
@implementation ORKActiveStepTimerView {
|
||||
BOOL _started;
|
||||
BOOL _registeredForNotifications;
|
||||
|
||||
NSLayoutConstraint *_countDownLabelBottomToStartTimerButtonTopConstraint;
|
||||
NSLayoutConstraint *_countDownLabelZeroHeightConstraint;
|
||||
NSLayoutConstraint *_startTimerButtonZeroHeightConstraint;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
@@ -70,7 +74,8 @@
|
||||
|
||||
_countDownLabel.accessibilityTraits |= UIAccessibilityTraitUpdatesFrequently;
|
||||
|
||||
[self setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
self.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[self setUpConstraints];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -88,11 +93,11 @@
|
||||
}
|
||||
|
||||
registered = _registeredForNotifications;
|
||||
NSNotificationCenter *nfc = [NSNotificationCenter defaultCenter];
|
||||
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
|
||||
if (registered) {
|
||||
[nfc addObserver:self selector:@selector(applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
|
||||
[notificationCenter addObserver:self selector:@selector(applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
|
||||
} else {
|
||||
[nfc removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];
|
||||
[notificationCenter removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,56 +149,80 @@
|
||||
- (void)finishStep:(ORKActiveStepViewController *)viewController {
|
||||
}
|
||||
|
||||
- (void)setNeedsUpdateConstraints {
|
||||
[NSLayoutConstraint deactivateConstraints:[self constraints]];
|
||||
[super setNeedsUpdateConstraints];
|
||||
static const CGFloat CountDownLabelToButtonMargin = 2.0;
|
||||
|
||||
- (void)setUpConstraints {
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(_countDownLabel, _startTimerButton);
|
||||
ORKEnableAutoLayoutForViews(views.allValues);
|
||||
|
||||
NSMutableArray *constraints = [NSMutableArray new];
|
||||
|
||||
for (UIView *view in views.allValues) {
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:view
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationLessThanOrEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeWidth
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:view
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
relatedBy:NSLayoutRelationLessThanOrEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
}
|
||||
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:self
|
||||
attribute:NSLayoutAttributeTop
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_countDownLabel
|
||||
attribute:NSLayoutAttributeTop
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
|
||||
_countDownLabelBottomToStartTimerButtonTopConstraint = [NSLayoutConstraint constraintWithItem:_startTimerButton
|
||||
attribute:NSLayoutAttributeTop
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_countDownLabel
|
||||
attribute:NSLayoutAttributeBottom
|
||||
multiplier:1.0
|
||||
constant:CountDownLabelToButtonMargin];
|
||||
[constraints addObject:_countDownLabelBottomToStartTimerButtonTopConstraint];
|
||||
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_startTimerButton
|
||||
attribute:NSLayoutAttributeBottom
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeBottom
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
|
||||
[NSLayoutConstraint activateConstraints:constraints];
|
||||
|
||||
_countDownLabelZeroHeightConstraint = [NSLayoutConstraint constraintWithItem:_countDownLabel
|
||||
attribute:NSLayoutAttributeHeight
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1.0
|
||||
constant:0.0];
|
||||
_startTimerButtonZeroHeightConstraint = [NSLayoutConstraint constraintWithItem:_startTimerButton
|
||||
attribute:NSLayoutAttributeHeight
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1.0
|
||||
constant:0.0];
|
||||
[self setNeedsUpdateConstraints];
|
||||
}
|
||||
|
||||
- (void)updateConstraints {
|
||||
NSDictionary *dictionary = NSDictionaryOfVariableBindings(_countDownLabel, _startTimerButton);
|
||||
NSDictionary *metrics = @{@"CS" : @(2)};
|
||||
ORKEnableAutoLayoutForViews([dictionary allValues]);
|
||||
|
||||
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) {
|
||||
NSMutableString *verticalLayout = [NSMutableString new];
|
||||
[verticalLayout appendString:@"V:|[_countDownLabel]"];
|
||||
if (! _startTimerButton.hidden) {
|
||||
[verticalLayout appendString:@"-CS-[_startTimerButton]"];
|
||||
}
|
||||
[verticalLayout appendString:@"|"];
|
||||
[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]];
|
||||
}
|
||||
_countDownLabelZeroHeightConstraint.active = _countDownLabel.hidden;
|
||||
_startTimerButtonZeroHeightConstraint.active = (_countDownLabel.hidden || _startTimerButton.hidden);
|
||||
_countDownLabelBottomToStartTimerButtonTopConstraint.constant =
|
||||
(_countDownLabel.hidden || _startTimerButton.hidden) ? 0.0 : CountDownLabelToButtonMargin;
|
||||
[super updateConstraints];
|
||||
}
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
[super viewDidLoad];
|
||||
|
||||
_activeStepView = [[ORKActiveStepView alloc] initWithFrame:self.view.bounds];
|
||||
[_activeStepView setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
_activeStepView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[_activeStepView setCustomView:_customView];
|
||||
[self updateContinueButtonItem];
|
||||
_activeStepView.headerView.learnMoreButtonItem = self.learnMoreButtonItem;
|
||||
@@ -111,9 +111,17 @@
|
||||
_activeStepView.continueSkipContainer.continueEnabled = _finished;
|
||||
[self.view addSubview:_activeStepView];
|
||||
|
||||
NSMutableArray *constraints = [NSMutableArray arrayWithArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[s]|" options:0 metrics:nil views:@{@"s":_activeStepView}]];
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[tg][s]|" options:0 metrics:nil views:@{@"s":_activeStepView,@"tg":self.topLayoutGuide}]];
|
||||
[self.view addConstraints:constraints];
|
||||
NSMutableArray *constraints = [NSMutableArray new];
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[activeStepView]|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil
|
||||
views:@{@"activeStepView": _activeStepView}]];
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[topLayoutGuide][activeStepView]|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil
|
||||
views:@{@"activeStepView": _activeStepView,
|
||||
@"topLayoutGuide": self.topLayoutGuide}]];
|
||||
[NSLayoutConstraint activateConstraints:constraints];
|
||||
|
||||
[self prepareStep];
|
||||
}
|
||||
@@ -155,7 +163,10 @@
|
||||
|
||||
// Wait for animation complete
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if ([[self activeStep] shouldStartTimerAutomatically]) {
|
||||
if(self.started){
|
||||
// Should call resume instead of start when the task has been started.
|
||||
[self resume];
|
||||
} else if ([[self activeStep] shouldStartTimerAutomatically]) {
|
||||
[self start];
|
||||
}
|
||||
});
|
||||
@@ -313,7 +324,7 @@
|
||||
|
||||
- (void)suspend {
|
||||
ORK_Log_Debug(@"%@",self);
|
||||
if (self.finished || ! self.started) {
|
||||
if (self.finished || !self.started) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -325,7 +336,7 @@
|
||||
|
||||
- (void)resume {
|
||||
ORK_Log_Debug(@"%@",self);
|
||||
if (self.finished || ! self.started) {
|
||||
if (self.finished || !self.started) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -351,7 +362,7 @@
|
||||
if (self.activeStep.shouldVibrateOnFinish) {
|
||||
AudioServicesPlayAlertSound(kSystemSoundID_Vibrate);
|
||||
}
|
||||
if (! self.activeStep.startsFinished) {
|
||||
if (!self.activeStep.startsFinished) {
|
||||
if (self.activeStep.shouldContinueOnFinish) {
|
||||
[self goForward];
|
||||
}
|
||||
|
||||
@@ -56,31 +56,15 @@ static const CGFloat GraphViewRedZoneHeight = 25;
|
||||
@end
|
||||
|
||||
|
||||
static const CGFloat kValueLineWidth = 4.5;
|
||||
static const CGFloat kValueLineMargin = 1.5;
|
||||
static const CGFloat ValueLineWidth = 4.5;
|
||||
static const CGFloat ValueLineMargin = 1.5;
|
||||
|
||||
@implementation ORKAudioGraphView
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
NSLayoutConstraint *constraint1 = [NSLayoutConstraint constraintWithItem:self
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1
|
||||
constant:CGFLOAT_MAX];
|
||||
constraint1.priority = UILayoutPriorityFittingSizeLevel;
|
||||
NSLayoutConstraint *constraint2 = [NSLayoutConstraint constraintWithItem:self
|
||||
attribute:NSLayoutAttributeHeight
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1
|
||||
constant:CGFLOAT_MAX];
|
||||
constraint2.priority = UILayoutPriorityFittingSizeLevel;
|
||||
[NSLayoutConstraint activateConstraints:@[constraint1, constraint2]];
|
||||
[self setUpConstraints];
|
||||
|
||||
#if TARGET_IPHONE_SIMULATOR
|
||||
_values = @[@(0.2),@(0.6),@(0.55), @(0.1), @(0.75), @(0.7)];
|
||||
@@ -89,6 +73,20 @@ static const CGFloat kValueLineMargin = 1.5;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setUpConstraints {
|
||||
|
||||
NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:self
|
||||
attribute:NSLayoutAttributeHeight
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1.0
|
||||
constant:CGFLOAT_MAX];
|
||||
heightConstraint.priority = UILayoutPriorityFittingSizeLevel;
|
||||
|
||||
[NSLayoutConstraint activateConstraints:@[heightConstraint]];
|
||||
}
|
||||
|
||||
- (void)setValues:(NSArray *)values {
|
||||
_values = [values copy];
|
||||
[self setNeedsDisplay];
|
||||
@@ -116,41 +114,41 @@ static const CGFloat kValueLineMargin = 1.5;
|
||||
CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
|
||||
CGContextFillRect(context, bounds);
|
||||
|
||||
CGFloat scale = [self.window.screen scale];
|
||||
CGFloat scale = self.window.screen.scale;
|
||||
|
||||
CGFloat midY = CGRectGetMidY(bounds);
|
||||
CGFloat maxX = CGRectGetMaxX(bounds);
|
||||
CGFloat halfHeight = bounds.size.height/2;
|
||||
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}];
|
||||
[centerLine moveToPoint:(CGPoint){.x = 0, .y = midY}];
|
||||
[centerLine addLineToPoint:(CGPoint){.x = maxX, .y = midY}];
|
||||
|
||||
CGContextSetLineWidth(context, 1/scale);
|
||||
CGContextSetLineWidth(context, 1.0 / scale);
|
||||
[_keyColor setStroke];
|
||||
CGFloat lengths[2] = {3,3};
|
||||
CGFloat lengths[2] = {3, 3};
|
||||
CGContextSetLineDash(context, 0, lengths, 2);
|
||||
|
||||
[centerLine stroke];
|
||||
}
|
||||
CGContextRestoreGState(context);
|
||||
|
||||
CGFloat lineStep = kValueLineMargin + kValueLineWidth;
|
||||
CGFloat lineStep = ValueLineMargin + ValueLineWidth;
|
||||
|
||||
CGContextSaveGState(context);
|
||||
{
|
||||
CGFloat x = maxX - lineStep/2;
|
||||
CGContextSetLineWidth(context, kValueLineWidth);
|
||||
CGFloat x = maxX - lineStep / 2;
|
||||
CGContextSetLineWidth(context, ValueLineWidth);
|
||||
CGContextSetLineCap(context, kCGLineCapRound);
|
||||
|
||||
UIBezierPath *path1 = [UIBezierPath new];
|
||||
path1.lineCapStyle = kCGLineCapRound;
|
||||
path1.lineWidth = kValueLineWidth;
|
||||
path1.lineWidth = ValueLineWidth;
|
||||
UIBezierPath *path2 = [path1 copy];
|
||||
|
||||
for (NSNumber *value in [_values reverseObjectEnumerator]) {
|
||||
CGFloat floatValue = [value doubleValue];
|
||||
CGFloat floatValue = value.doubleValue;
|
||||
|
||||
UIBezierPath *path = nil;
|
||||
if (floatValue > _alertThreshold) {
|
||||
@@ -160,8 +158,8 @@ static const CGFloat kValueLineMargin = 1.5;
|
||||
path = path2;
|
||||
[_keyColor setStroke];
|
||||
}
|
||||
[path moveToPoint:(CGPoint){.x=x,.y=midY-floatValue*halfHeight}];
|
||||
[path 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;
|
||||
|
||||
@@ -194,7 +192,7 @@ static const CGFloat kValueLineMargin = 1.5;
|
||||
+ (UIFont *)defaultFont {
|
||||
UIFontDescriptor *descriptor = [UIFontDescriptor preferredFontDescriptorWithTextStyle:UIFontTextStyleSubheadline];
|
||||
UIFontDescriptor *alternativeDescriptor = ORKFontDescriptorForLightStylisticAlternative(descriptor);
|
||||
return [UIFont fontWithDescriptor:alternativeDescriptor size:[alternativeDescriptor pointSize]+4];
|
||||
return [UIFont fontWithDescriptor:alternativeDescriptor size:[alternativeDescriptor pointSize] + 4];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -210,7 +208,6 @@ static const CGFloat kValueLineMargin = 1.5;
|
||||
|
||||
|
||||
@implementation ORKAudioContentView {
|
||||
NSArray *_constraints;
|
||||
NSMutableArray *_samples;
|
||||
UIColor *_keyColor;
|
||||
}
|
||||
@@ -238,11 +235,11 @@ static const CGFloat kValueLineMargin = 1.5;
|
||||
_timerLabel.text = @"06:00";
|
||||
_alertLabel.text = ORKLocalizedString(@"AUDIO_TOO_LOUD_LABEL", nil);
|
||||
|
||||
self.alertThreshold = GraphViewBlueZoneHeight/(GraphViewRedZoneHeight*2+GraphViewBlueZoneHeight);
|
||||
self.alertThreshold = GraphViewBlueZoneHeight / ((GraphViewRedZoneHeight * 2) + GraphViewBlueZoneHeight);
|
||||
|
||||
[self updateGraphSamples];
|
||||
[self applyKeyColor];
|
||||
[self setNeedsUpdateConstraints];
|
||||
[self setUpConstraints];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -251,7 +248,7 @@ static const CGFloat kValueLineMargin = 1.5;
|
||||
[self applyKeyColor];
|
||||
}
|
||||
|
||||
-(void)setFailed:(BOOL)failed {
|
||||
- (void)setFailed:(BOOL)failed {
|
||||
_failed = failed;
|
||||
_alertLabel.text = failed ? ORKLocalizedString(@"AUDIO_GENERIC_ERROR_LABEL", nil) : ORKLocalizedString(@"AUDIO_TOO_LOUD_LABEL", nil);
|
||||
[self updateAlertLabelHidden];
|
||||
@@ -283,27 +280,21 @@ static const CGFloat kValueLineMargin = 1.5;
|
||||
_graphView.alertColor = alertColor;
|
||||
}
|
||||
|
||||
- (void)updateConstraints {
|
||||
if ([_constraints count]) {
|
||||
[NSLayoutConstraint deactivateConstraints:_constraints];
|
||||
_constraints = nil;
|
||||
}
|
||||
|
||||
- (void)setUpConstraints {
|
||||
NSMutableArray *constraints = [NSMutableArray array];
|
||||
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(_timerLabel, _alertLabel, _graphView);
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_graphView]-[_alertLabel]|"
|
||||
options:0
|
||||
metrics:nil
|
||||
views:views]];
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_graphView]-[_alertLabel]|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil
|
||||
views:views]];
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_alertLabel
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
multiplier:1
|
||||
constant:0]];
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
|
||||
const CGFloat sideMargin = self.layoutMargins.left + (2 * ORKStandardLeftMarginForTableViewCell(self));
|
||||
const CGFloat innerMargin = 2;
|
||||
@@ -314,18 +305,15 @@ static const CGFloat kValueLineMargin = 1.5;
|
||||
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)]];
|
||||
multiplier:1.0
|
||||
constant:(GraphViewBlueZoneHeight + GraphViewRedZoneHeight * 2)]];
|
||||
|
||||
_constraints = constraints;
|
||||
[NSLayoutConstraint activateConstraints:constraints];
|
||||
[super updateConstraints];
|
||||
}
|
||||
|
||||
- (void)setAlertThreshold:(CGFloat)alertThreshold {
|
||||
@@ -361,8 +349,8 @@ static const CGFloat kValueLineMargin = 1.5;
|
||||
}
|
||||
|
||||
- (void)updateAlertLabelHidden {
|
||||
NSNumber *sample = [_samples lastObject];
|
||||
BOOL show = (! _finished && ([sample doubleValue] > _alertThreshold)) || _failed;
|
||||
NSNumber *sample = _samples.lastObject;
|
||||
BOOL show = (!_finished && (sample.doubleValue > _alertThreshold)) || _failed;
|
||||
_alertLabel.hidden = !show;
|
||||
}
|
||||
|
||||
@@ -373,13 +361,13 @@ static const CGFloat kValueLineMargin = 1.5;
|
||||
|
||||
- (void)addSample:(NSNumber *)sample {
|
||||
NSAssert(sample != nil, @"Sample should be non-nil");
|
||||
if (! _samples) {
|
||||
if (!_samples) {
|
||||
_samples = [NSMutableArray array];
|
||||
}
|
||||
[_samples addObject:sample];
|
||||
// Try to keep around 250 samples
|
||||
if ([_samples count] > 500) {
|
||||
_samples = [[_samples subarrayWithRange:(NSRange){250,_samples.count-250}] mutableCopy];
|
||||
if (_samples.count > 500) {
|
||||
_samples = [[_samples subarrayWithRange:(NSRange){250, _samples.count - 250}] mutableCopy];
|
||||
}
|
||||
[self updateGraphSamples];
|
||||
}
|
||||
|
||||
@@ -103,8 +103,7 @@ OSStatus ORKAudioGeneratorRenderTone(void *inRefCon,
|
||||
bufferActive[frame] = bufferValue;
|
||||
if (audioGenerator->_playsStereo) {
|
||||
bufferNonActive[frame] = bufferValue;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
bufferNonActive[frame] = 0;
|
||||
}
|
||||
|
||||
@@ -113,7 +112,7 @@ OSStatus ORKAudioGeneratorRenderTone(void *inRefCon,
|
||||
theta -= 2.0 * M_PI;
|
||||
}
|
||||
|
||||
fadeInFactor += 1/(ORKSineWaveToneGeneratorSampleRateDefault * audioGenerator->_fadeInDuration);
|
||||
fadeInFactor += 1.0 / (ORKSineWaveToneGeneratorSampleRateDefault * audioGenerator->_fadeInDuration);
|
||||
if (fadeInFactor >= 1) {
|
||||
fadeInFactor = 1;
|
||||
}
|
||||
|
||||
@@ -68,10 +68,10 @@
|
||||
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;
|
||||
@@ -84,18 +84,18 @@
|
||||
@throw [NSException exceptionWithName:NSDestinationInvalidException reason:@"audioRecorder requires an output directory" userInfo:nil];
|
||||
}
|
||||
// Only create the file when we should actually start recording.
|
||||
if (! _audioRecorder) {
|
||||
if (!_audioRecorder) {
|
||||
|
||||
NSError *error = nil;
|
||||
NSURL *soundFileURL = [self recordingFileURL];
|
||||
if (! [self recreateFileWithError:&error]) {
|
||||
if (![self recreateFileWithError:&error]) {
|
||||
[self finishRecordingWithError:error];
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
|
||||
if (! [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:&error]) {
|
||||
if (![audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:&error]) {
|
||||
[self finishRecordingWithError:error];
|
||||
return;
|
||||
}
|
||||
@@ -105,19 +105,19 @@
|
||||
initWithURL:soundFileURL
|
||||
settings:self.recorderSettings
|
||||
error:&error];
|
||||
if (! _audioRecorder) {
|
||||
if (!_audioRecorder) {
|
||||
[self finishRecordingWithError:error];
|
||||
return;
|
||||
}
|
||||
|
||||
#if ! TARGET_IPHONE_SIMULATOR
|
||||
#if !TARGET_IPHONE_SIMULATOR
|
||||
if (!_audioRecorder.recording) {
|
||||
[_audioRecorder prepareToRecord];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if ! TARGET_IPHONE_SIMULATOR
|
||||
#if !TARGET_IPHONE_SIMULATOR
|
||||
if (!_audioRecorder.recording) {
|
||||
[_audioRecorder prepareToRecord];
|
||||
[_audioRecorder record];
|
||||
@@ -128,7 +128,7 @@
|
||||
}
|
||||
|
||||
- (void)stop {
|
||||
if (! _audioRecorder) {
|
||||
if (!_audioRecorder) {
|
||||
// Error has already been returned.
|
||||
return;
|
||||
}
|
||||
@@ -136,7 +136,7 @@
|
||||
[self doStopRecording];
|
||||
|
||||
NSURL *fileUrl = [self recordingFileURL];
|
||||
if (! [[NSFileManager defaultManager] fileExistsAtPath:[[self recordingFileURL] path]]) {
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:[[self recordingFileURL] path]]) {
|
||||
fileUrl = nil;
|
||||
}
|
||||
|
||||
@@ -151,12 +151,12 @@
|
||||
|
||||
- (NSString *)mimeType {
|
||||
NSDictionary *recorderSettings = [self recorderSettings];
|
||||
unsigned int recorderFormat = [recorderSettings[AVFormatIDKey] unsignedIntValue];
|
||||
unsigned int recorderFormat = ((NSNumber *)recorderSettings[AVFormatIDKey]).unsignedIntValue;
|
||||
|
||||
NSString *contentType = @"audio";
|
||||
switch (recorderFormat) {
|
||||
case kAudioFormatLinearPCM: {
|
||||
int numBits = [recorderSettings[AVLinearPCMBitDepthKey] intValue] ? : 16;
|
||||
int numBits = ((NSNumber *)recorderSettings[AVLinearPCMBitDepthKey]).intValue ? : 16;
|
||||
contentType = [NSString stringWithFormat:@"audio/L%d", numBits];
|
||||
break;
|
||||
}
|
||||
@@ -202,7 +202,7 @@
|
||||
|
||||
- (NSString *)extension {
|
||||
NSDictionary *recorderSettings = [self recorderSettings];
|
||||
unsigned int recorderFormat = [recorderSettings[AVFormatIDKey] unsignedIntValue];
|
||||
unsigned int recorderFormat = ((NSNumber *)recorderSettings[AVFormatIDKey]).unsignedIntValue;
|
||||
|
||||
NSString *extension = @"au";
|
||||
switch (recorderFormat) {
|
||||
@@ -233,7 +233,7 @@
|
||||
|
||||
- (BOOL)recreateFileWithError:(NSError * __autoreleasing *)error {
|
||||
NSURL *url = [self recordingFileURL];
|
||||
if (! url) {
|
||||
if (!url) {
|
||||
if (error) {
|
||||
*error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteInvalidFileNameError userInfo:@{NSLocalizedDescriptionKey:ORKLocalizedString(@"ERROR_RECORDER_NO_OUTPUT_DIRECTORY", nil)}];
|
||||
}
|
||||
@@ -242,12 +242,12 @@
|
||||
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
|
||||
if (! [fileManager createDirectoryAtURL:url withIntermediateDirectories:YES attributes:nil error:error]) {
|
||||
if (![fileManager createDirectoryAtURL:url withIntermediateDirectories:YES attributes:nil error:error]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
if ([fileManager fileExistsAtPath:[url path]]) {
|
||||
if (! [fileManager removeItemAtPath:[url path] error:error]) {
|
||||
if (![fileManager removeItemAtPath:[url path] error:error]) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
@@ -266,11 +266,6 @@
|
||||
@end
|
||||
|
||||
|
||||
@interface ORKAudioRecorderConfiguration ()
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKAudioRecorderConfiguration
|
||||
|
||||
#pragma clang diagnostic push
|
||||
@@ -283,7 +278,7 @@
|
||||
recorderSettings:(NSDictionary *)recorderSettings {
|
||||
self = [super initWithIdentifier:identifier];
|
||||
if (self) {
|
||||
if (recorderSettings && ! [recorderSettings isKindOfClass:[NSDictionary class]]) {
|
||||
if (recorderSettings && ![recorderSettings isKindOfClass:[NSDictionary class]]) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"recorderSettings should be a dictionary" userInfo:recorderSettings];
|
||||
}
|
||||
_recorderSettings = recorderSettings;
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
NSTimeInterval const ORKAudioTaskMinimumDuration = 5.0;
|
||||
|
||||
if ( self.stepDuration < ORKAudioTaskMinimumDuration) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"duration can not be shorter than %@ seconds.", @(ORKAudioTaskMinimumDuration)] userInfo:nil];
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"duration cannot be shorter than %@ seconds.", @(ORKAudioTaskMinimumDuration)] userInfo:nil];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -107,16 +107,16 @@
|
||||
[_avAudioRecorder updateMeters];
|
||||
float value = [_avAudioRecorder averagePowerForChannel:0];
|
||||
// Assume value is in range roughly -60dB to 0dB
|
||||
float clampedValue = MAX(value/60.0, -1) + 1;
|
||||
float clampedValue = MAX(value / 60.0, -1) + 1;
|
||||
[_audioContentView addSample:@(clampedValue)];
|
||||
_audioContentView.timeLeft = [_timer duration] - [_timer runtime];
|
||||
}
|
||||
|
||||
- (void)startNewTimerIfNeeded {
|
||||
if (! _timer) {
|
||||
if (!_timer) {
|
||||
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) {
|
||||
_timer = [[ORKActiveStepTimer alloc] initWithDuration:duration interval:duration / 100 runtime:0 handler:^(ORKActiveStepTimer *timer, BOOL finished) {
|
||||
typeof(self) strongSelf = weakSelf;
|
||||
[strongSelf doSample];
|
||||
if (finished) {
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
NSTimeInterval const ORKCountdownStepMinimumDuration = 3.0;
|
||||
|
||||
if ( self.stepDuration < ORKCountdownStepMinimumDuration) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"duration can not be shorter than %@ seconds.", @(ORKCountdownStepMinimumDuration)] userInfo:nil];
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"duration cannot be shorter than %@ seconds.", @(ORKCountdownStepMinimumDuration)] userInfo:nil];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -69,6 +69,9 @@
|
||||
CAShapeLayer *_circleLayer;
|
||||
}
|
||||
|
||||
static const CGFloat ProgressIndicatorDiameter = 104.0;
|
||||
static const CGFloat ProgressIndicatorOuterMargin = 1.0;
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
@@ -88,55 +91,11 @@
|
||||
[self addSubview:_progressView];
|
||||
|
||||
self.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
|
||||
static const CGFloat ProgressIndicatorDiameter = 104;
|
||||
static const CGFloat ProgressIndicatorOuterMargin = 1;
|
||||
NSDictionary *metrics = @{@"d":@(ProgressIndicatorDiameter+2*ProgressIndicatorOuterMargin)};
|
||||
|
||||
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 addConstraint:[NSLayoutConstraint constraintWithItem:_progressView
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
multiplier:1.0 constant:0]];
|
||||
[self addConstraint:[NSLayoutConstraint constraintWithItem:_timeLabel
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_progressView
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
multiplier:1.0 constant:0]];
|
||||
|
||||
// Constant required in order to give appearance of vertical centering (compensating for leading on font)
|
||||
[self addConstraint:[NSLayoutConstraint constraintWithItem:_timeLabel
|
||||
attribute:NSLayoutAttributeCenterY
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_progressView
|
||||
attribute:NSLayoutAttributeCenterY
|
||||
multiplier:1.0 constant:-3]];
|
||||
[self addConstraint:[NSLayoutConstraint constraintWithItem:_progressView
|
||||
attribute:NSLayoutAttributeTop
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_textLabel
|
||||
attribute:NSLayoutAttributeLastBaseline
|
||||
multiplier:1 constant:16-ProgressIndicatorOuterMargin]];
|
||||
[self setUpConstraints];
|
||||
|
||||
_circleLayer = [CAShapeLayer layer];
|
||||
static const CGFloat ProgressIndicatorRadius = ProgressIndicatorDiameter/2;
|
||||
_circleLayer.path = [[UIBezierPath bezierPathWithArcCenter:CGPointMake(ProgressIndicatorRadius+ProgressIndicatorOuterMargin, ProgressIndicatorRadius+ProgressIndicatorOuterMargin)
|
||||
static const CGFloat ProgressIndicatorRadius = ProgressIndicatorDiameter / 2;
|
||||
_circleLayer.path = [[UIBezierPath bezierPathWithArcCenter:CGPointMake(ProgressIndicatorRadius + ProgressIndicatorOuterMargin, ProgressIndicatorRadius + ProgressIndicatorOuterMargin)
|
||||
radius:ProgressIndicatorRadius
|
||||
startAngle:M_PI + M_PI_2
|
||||
endAngle:-M_PI_2
|
||||
@@ -154,13 +113,65 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setUpConstraints {
|
||||
NSMutableArray *constraints = [NSMutableArray new];
|
||||
|
||||
NSDictionary *metrics = @{@"d": @(ProgressIndicatorDiameter + 2 * ProgressIndicatorOuterMargin)};
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(_textLabel, _timeLabel, _progressView);
|
||||
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_textLabel]-(>=0)-[_progressView(==d)]|"
|
||||
options:NSLayoutFormatDirectionLeadingToTrailing | NSLayoutFormatAlignAllCenterX
|
||||
metrics:metrics
|
||||
views:views]];
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(>=0)-[_textLabel]-(>=0)-|"
|
||||
options:NSLayoutFormatDirectionLeadingToTrailing
|
||||
metrics:metrics
|
||||
views:views]];
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(>=0)-[_progressView(==d)]-(>=0)-|"
|
||||
options:NSLayoutFormatDirectionLeadingToTrailing
|
||||
metrics:metrics
|
||||
views:views]];
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_progressView
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_timeLabel
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_progressView
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
|
||||
// Constant required in order to give appearance of vertical centering (compensating for leading on font)
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_timeLabel
|
||||
attribute:NSLayoutAttributeCenterY
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_progressView
|
||||
attribute:NSLayoutAttributeCenterY
|
||||
multiplier:1.0
|
||||
constant:-3.0]];
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_progressView
|
||||
attribute:NSLayoutAttributeTop
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_textLabel
|
||||
attribute:NSLayoutAttributeLastBaseline
|
||||
multiplier:1.0
|
||||
constant:16.0 - ProgressIndicatorOuterMargin]];
|
||||
|
||||
[NSLayoutConstraint activateConstraints:constraints];
|
||||
}
|
||||
|
||||
- (void)tintColorDidChange {
|
||||
_circleLayer.strokeColor = self.tintColor.CGColor;
|
||||
}
|
||||
|
||||
- (void)startAnimateWithDuration:(NSTimeInterval)duration {
|
||||
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"strokeEnd"];
|
||||
animation.duration = duration*2;
|
||||
animation.duration = duration * 2;
|
||||
animation.removedOnCompletion = YES;
|
||||
animation.values = @[@(1.0), @(0.0), @(0.0)];
|
||||
animation.keyTimes = @[@(0.0), @(0.5), @(1.0)];
|
||||
@@ -224,16 +235,16 @@
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
[super viewDidAppear:animated];
|
||||
|
||||
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, [@(_countDown) stringValue]);
|
||||
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, @(_countDown).stringValue);
|
||||
[_countdownView startAnimateWithDuration:[(ORKActiveStep *)self.step stepDuration]];
|
||||
}
|
||||
|
||||
- (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);
|
||||
_countDown = MAX((_countDown - 1), 0);
|
||||
[self updateCountdownLabel];
|
||||
|
||||
if (UIAccessibilityIsVoiceOverRunning()) {
|
||||
@@ -247,7 +258,7 @@
|
||||
}];
|
||||
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, ORKLocalizedString(@"AX_ANNOUNCE_BEGIN_TASK", nil));
|
||||
} else {
|
||||
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, [@(_countDown) stringValue]);
|
||||
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, @(_countDown).stringValue);
|
||||
[super countDownTimerFired:timer finished:finished];
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -101,6 +101,8 @@ ORK_CLASS_AVAILABLE
|
||||
*/
|
||||
+ (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.
|
||||
|
||||
@@ -223,7 +225,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@return `YES` if appending succeeds; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)appendObjects:(NSArray *)objects error:(NSError * __nullable __autoreleasing *)error;
|
||||
- (BOOL)appendObjects:(NSArray *)objects error:(NSError * _Nullable __autoreleasing *)error;
|
||||
|
||||
/**
|
||||
Checks whether a file has been marked as uploaded.
|
||||
@@ -249,7 +251,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@return `YES` if adding or removing the attribute succeeded; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)markFileUploaded:(BOOL)uploaded atURL:(NSURL *)url error:(NSError * __nullable __autoreleasing *)error;
|
||||
- (BOOL)markFileUploaded:(BOOL)uploaded atURL:(NSURL *)url error:(NSError * _Nullable __autoreleasing *)error;
|
||||
|
||||
/**
|
||||
Removes files if they are marked uploaded.
|
||||
@@ -263,7 +265,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@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).
|
||||
@@ -272,7 +274,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@return `YES` if removing the files succeeded.; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)removeAllFilesWithError:(NSError *__nullable __autoreleasing *)error;
|
||||
- (BOOL)removeAllFilesWithError:(NSError *_Nullable __autoreleasing *)error;
|
||||
|
||||
@end
|
||||
|
||||
@@ -318,7 +320,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@return `YES` if the write succeeds; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)beginLogWithFileHandle:(NSFileHandle *)fileHandle error:(NSError * __nullable __autoreleasing *)error;
|
||||
- (BOOL)beginLogWithFileHandle:(NSFileHandle *)fileHandle error:(NSError * _Nullable __autoreleasing *)error;
|
||||
|
||||
/**
|
||||
Appends the specified object to the log file.
|
||||
@@ -329,7 +331,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@return `YES` if the write succeeds; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)appendObject:(id)object fileHandle:(NSFileHandle *)fileHandle error:(NSError * __nullable __autoreleasing *)error;
|
||||
- (BOOL)appendObject:(id)object fileHandle:(NSFileHandle *)fileHandle error:(NSError * _Nullable __autoreleasing *)error;
|
||||
|
||||
/**
|
||||
Appends the specified objects to the log file.
|
||||
@@ -340,7 +342,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@return `YES` if the write succeeds; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)appendObjects:(NSArray *)objects fileHandle:(NSFileHandle *)fileHandle error:(NSError * __nullable __autoreleasing *)error;
|
||||
- (BOOL)appendObjects:(NSArray *)objects fileHandle:(NSFileHandle *)fileHandle error:(NSError * _Nullable __autoreleasing *)error;
|
||||
|
||||
@end
|
||||
|
||||
@@ -423,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.
|
||||
|
||||
@@ -489,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.
|
||||
@@ -514,7 +518,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@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.
|
||||
@@ -528,7 +532,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@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.
|
||||
@@ -541,7 +545,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@return `YES` if the operation succeeds; otherwise, `NO`.
|
||||
*/
|
||||
- (BOOL)removeOldAndUploadedLogsToThreshold:(unsigned long long)bytes error:(NSError * __nullable __autoreleasing *)error;
|
||||
- (BOOL)removeOldAndUploadedLogsToThreshold:(unsigned long long)bytes error:(NSError * _Nullable __autoreleasing *)error;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -39,13 +39,13 @@
|
||||
#import "ORKDefines_Private.h"
|
||||
|
||||
|
||||
static const char * kORKDataLoggerUploadedAttr = "com.apple.ResearchKit.uploaded";
|
||||
static const char *ORKDataLoggerUploadedAttr = "com.apple.ResearchKit.uploaded";
|
||||
|
||||
// Default per-logfile settings when a data logger is used in an ORKDataLoggerManager
|
||||
static const NSTimeInterval kORKDataLoggerManagerDefaultLogFileLifetime = 60*60*24*3; // 3 days
|
||||
static const unsigned long long kORKDataLoggerManagerDefaultLogFileSize = 1024*1024; // 1 MB
|
||||
static const NSTimeInterval ORKDataLoggerManagerDefaultLogFileLifetime = 60 * 60 * 24 * 3; // 3 days
|
||||
static const unsigned long long ORKDataLoggerManagerDefaultLogFileSize = 1024 * 1024; // 1 MB
|
||||
|
||||
static NSString *const kORKDataLoggerManagerConfigurationFilename = @".ORKDataLoggerManagerConfiguration";
|
||||
static NSString *const ORKDataLoggerManagerConfigurationFilename = @".ORKDataLoggerManagerConfiguration";
|
||||
|
||||
|
||||
@interface ORKDataLogger ()
|
||||
@@ -80,7 +80,7 @@ static NSString *const kORKDataLoggerManagerConfigurationFilename = @".ORKDataLo
|
||||
- (NSString *)ork_logName {
|
||||
NSString *lastComponent = [self lastPathComponent];
|
||||
NSRange idx = [lastComponent rangeOfString:@"-"];
|
||||
if (! idx.length) {
|
||||
if (!idx.length) {
|
||||
@throw [NSException exceptionWithName:NSGenericException reason:@"URL is not a completed log file" userInfo:@{@"url":self}];
|
||||
}
|
||||
|
||||
@@ -91,28 +91,28 @@ static NSString *const kORKDataLoggerManagerConfigurationFilename = @".ORKDataLo
|
||||
- (NSString *)ork_logDateComponent {
|
||||
NSString *lastComponent = [self lastPathComponent];
|
||||
NSRange idx = [lastComponent rangeOfString:@"-"];
|
||||
if (! idx.length) {
|
||||
if (!idx.length) {
|
||||
@throw [NSException exceptionWithName:NSGenericException reason:@"URL is not a completed log file" userInfo:@{@"url":self}];
|
||||
}
|
||||
|
||||
NSString *logDateComponent = [lastComponent substringFromIndex:idx.location+1];
|
||||
NSString *logDateComponent = [lastComponent substringFromIndex:idx.location + 1];
|
||||
return logDateComponent;
|
||||
}
|
||||
|
||||
- (BOOL)ork_isUploaded {
|
||||
NSData *data = [self ork_dataForAttr:kORKDataLoggerUploadedAttr];
|
||||
NSData *data = [self ork_dataForAttr:ORKDataLoggerUploadedAttr];
|
||||
if (!data) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
||||
return ([string integerValue] != 0);
|
||||
return (string.integerValue != 0);
|
||||
}
|
||||
|
||||
- (BOOL)ork_setUploaded:(BOOL)uploaded error:(NSError * __autoreleasing *)error {
|
||||
NSString *value = (uploaded ? @"1" : @"0");
|
||||
NSData *encodedString = [value dataUsingEncoding:NSUTF8StringEncoding];
|
||||
return [self ork_setData:encodedString forAttr:kORKDataLoggerUploadedAttr error:error];
|
||||
return [self ork_setData:encodedString forAttr:ORKDataLoggerUploadedAttr error:error];
|
||||
}
|
||||
|
||||
- (NSData *)ork_dataForAttr:(const char *)attr {
|
||||
@@ -125,7 +125,7 @@ static NSString *const kORKDataLoggerManagerConfigurationFilename = @".ORKDataLo
|
||||
}
|
||||
|
||||
NSMutableData *data = [NSMutableData dataWithLength:length];
|
||||
length = getxattr(path, attr, [data mutableBytes], length, 0, 0);
|
||||
length = getxattr(path, attr, data.mutableBytes, length, 0, 0);
|
||||
if (length <= 0) {
|
||||
return nil;
|
||||
}
|
||||
@@ -135,7 +135,7 @@ static NSString *const kORKDataLoggerManagerConfigurationFilename = @".ORKDataLo
|
||||
|
||||
- (BOOL)ork_setData:(NSData *)data forAttr:(const char *)attr error:(NSError * __autoreleasing *)error {
|
||||
const char *path = [self fileSystemRepresentation];
|
||||
int rc = setxattr(path, attr, [data bytes], [data length], 0, 0);
|
||||
int rc = setxattr(path, attr, data.bytes, data.length, 0, 0);
|
||||
if (rc != 0) {
|
||||
if (error) {
|
||||
*error = [NSError errorWithDomain:NSCocoaErrorDomain code:rc userInfo:@{NSLocalizedDescriptionKey : ORKLocalizedString(@"ERROR_DATALOGGER_SET_ATTRIBUTE", nil)}];
|
||||
@@ -145,13 +145,13 @@ static NSString *const kORKDataLoggerManagerConfigurationFilename = @".ORKDataLo
|
||||
}
|
||||
|
||||
- (NSString *)ork_logNameInDirectory:(NSURL *)directory {
|
||||
if (! [self isFileURL]) {
|
||||
if (![self isFileURL]) {
|
||||
@throw [NSException exceptionWithName:NSGenericException reason:@"URL is not a fileURL" userInfo:@{@"url":self}];
|
||||
}
|
||||
|
||||
NSString *lastComponent = [self lastPathComponent];
|
||||
NSRange idx = [lastComponent rangeOfString:@"-"];
|
||||
if (! idx.length) {
|
||||
if (!idx.length) {
|
||||
@throw [NSException exceptionWithName:NSGenericException reason:@"URL is not a completed log file" userInfo:@{@"url":self}];
|
||||
}
|
||||
|
||||
@@ -267,7 +267,7 @@ static void *ORKObjectObserverContext = &ORKObjectObserverContext;
|
||||
}
|
||||
|
||||
- (BOOL)appendObject:(id)object fileHandle:(NSFileHandle *)fileHandle error:(NSError * __autoreleasing *)error {
|
||||
if (! [self canAcceptLogObject:object]) {
|
||||
if (![self canAcceptLogObject:object]) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"ORKLogFormatter accepts NSData only" userInfo:nil];
|
||||
}
|
||||
return [self writeData:(NSData *)object fileHandle:fileHandle error:error];
|
||||
@@ -285,7 +285,7 @@ static void *ORKObjectObserverContext = &ORKObjectObserverContext;
|
||||
}
|
||||
}
|
||||
|
||||
if (! success) {
|
||||
if (!success) {
|
||||
[self rollbackToCheckpoint:checkpoint fileHandle:fileHandle];
|
||||
if (error) {
|
||||
*error = errorOut;
|
||||
@@ -312,8 +312,8 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
if (self) {
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
_ORKJSON_emptyLogLength = [[kJSONLogEmptyLogString dataUsingEncoding:NSUTF8StringEncoding] length];
|
||||
_ORKJSON_terminatorLength = [[kJSONLogFooterString dataUsingEncoding:NSUTF8StringEncoding] length];
|
||||
_ORKJSON_emptyLogLength = [kJSONLogEmptyLogString dataUsingEncoding:NSUTF8StringEncoding].length;
|
||||
_ORKJSON_terminatorLength = [kJSONLogFooterString dataUsingEncoding:NSUTF8StringEncoding].length;
|
||||
});
|
||||
}
|
||||
return self;
|
||||
@@ -361,15 +361,15 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
* object being appended, and the footer bytes.
|
||||
*/
|
||||
- (BOOL)appendObjects:(NSArray *)objects fileHandle:(NSFileHandle *)fileHandle error:(NSError * __autoreleasing *)error {
|
||||
if (! fileHandle) {
|
||||
if (!fileHandle) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Filehandle is nil" userInfo:nil];
|
||||
}
|
||||
NSInteger numObjects = [objects count];
|
||||
NSInteger numObjects = objects.count;
|
||||
if (numObjects == 0) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"No objects" userInfo:nil];
|
||||
}
|
||||
for (NSObject *object in objects) {
|
||||
if (! [self canAcceptLogObject:object]) {
|
||||
if (![self canAcceptLogObject:object]) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"ORKLogFormatter accepts JSON serializable objects only" userInfo:nil];
|
||||
}
|
||||
}
|
||||
@@ -406,7 +406,7 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
}
|
||||
}
|
||||
}];
|
||||
if (! success) {
|
||||
if (!success) {
|
||||
return success;
|
||||
}
|
||||
|
||||
@@ -417,7 +417,7 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
|
||||
success = [self writeData:outputData fileHandle:fileHandle error:error];
|
||||
|
||||
if (! success) {
|
||||
if (!success) {
|
||||
[self rollbackToCheckpoint:checkpoint fileHandle:fileHandle];
|
||||
}
|
||||
|
||||
@@ -446,17 +446,23 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
return [[ORKDataLogger alloc] initWithDirectory:url logName:logName formatter:[ORKJSONLogFormatter new] delegate:delegate];
|
||||
}
|
||||
|
||||
|
||||
- (instancetype)init {
|
||||
ORKThrowMethodUnavailableException();
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (instancetype)initWithDirectory:(NSURL *)url logName:(NSString *)logName formatter:(ORKLogFormatter *)formatter delegate:(id<ORKDataLoggerDelegate>)delegate {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_url = [url copy];
|
||||
if (! [[NSFileManager defaultManager] fileExistsAtPath:[url path]]) {
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:[url path]]) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"directory does not exist" userInfo:nil];
|
||||
}
|
||||
if ([logName hasSuffix:@"-"]) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"logName should not terminate with '-'" userInfo:nil];
|
||||
}
|
||||
if (! [logName length]) {
|
||||
if (!logName.length) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"logName must be non-empty" userInfo:nil];
|
||||
}
|
||||
|
||||
@@ -480,7 +486,7 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
|
||||
- (instancetype)initWithDirectory:(NSURL *)url configuration:(NSDictionary *)configuration delegate:(id<ORKDataLoggerDelegate>)delegate {
|
||||
Class formatterClass = NSClassFromString(configuration[@"formatterClass"]);
|
||||
if (! formatterClass) {
|
||||
if (!formatterClass) {
|
||||
@throw [NSException exceptionWithName:NSGenericException reason:[NSString stringWithFormat:@"%@ is not a class", configuration[@"formatterClass"]] userInfo:nil];
|
||||
}
|
||||
|
||||
@@ -488,8 +494,8 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
if (self) {
|
||||
// Don't notify about initial setup
|
||||
[_observer pause];
|
||||
self.maximumCurrentLogFileSize = [configuration[@"maximumCurrentLogFileSize"] unsignedLongValue];
|
||||
self.maximumCurrentLogFileLifetime = [configuration[@"maximumCurrentLogFileLifetime"] doubleValue];
|
||||
self.maximumCurrentLogFileSize = ((NSNumber *)configuration[@"maximumCurrentLogFileSize"]).unsignedLongValue;
|
||||
self.maximumCurrentLogFileLifetime = ((NSNumber *)configuration[@"maximumCurrentLogFileLifetime"]).doubleValue;
|
||||
[_observer resume];
|
||||
}
|
||||
return self;
|
||||
@@ -509,7 +515,7 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
- (void)setupDirectorySource {
|
||||
int dirFD = open([_url fileSystemRepresentation], O_EVTONLY);
|
||||
if (dirFD < 0) {
|
||||
ORK_Log_Oops(@"Could not track directory %s (%d)", [_url fileSystemRepresentation], [[NSFileManager defaultManager] fileExistsAtPath:[_url path]]);
|
||||
ORK_Log_Warning(@"Could not track directory %s (%d)", [_url fileSystemRepresentation], [[NSFileManager defaultManager] fileExistsAtPath:[_url path]]);
|
||||
} else {
|
||||
// Dispatch to a concurrent queue, so we don't store up blocks while our
|
||||
// queue is working.
|
||||
@@ -608,7 +614,7 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
}
|
||||
|
||||
- (BOOL)appendObjects:(NSArray *)objects error:(NSError * __autoreleasing *)error {
|
||||
if (![objects count]) {
|
||||
if (!objects.count) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Empty array" userInfo:nil];
|
||||
}
|
||||
__block BOOL success = NO;
|
||||
@@ -626,7 +632,7 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
return success;
|
||||
}
|
||||
|
||||
- (BOOL)removeUploadedFiles:(NSArray *)fileURLs withError:(NSError * __autoreleasing *)error {
|
||||
- (BOOL)removeUploadedFiles:(NSArray<NSURL *> *)fileURLs withError:(NSError * __autoreleasing *)error {
|
||||
__block BOOL success = NO;
|
||||
dispatch_sync(_queue, ^{
|
||||
success = [self queue_removeUploadedFiles:fileURLs withError:error];
|
||||
@@ -658,7 +664,7 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
}
|
||||
|
||||
- (void)queue_setNeedsUpdateBytes {
|
||||
if (! _directoryDirty) {
|
||||
if (!_directoryDirty) {
|
||||
_directoryDirty = YES;
|
||||
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.05 * NSEC_PER_SEC)), _queue, ^{
|
||||
@@ -698,7 +704,7 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
NSError *errorOut = nil;
|
||||
NSMutableArray *urls = [NSMutableArray array];
|
||||
for (NSURL *url in enumerator) {
|
||||
if (! [self urlMatchesLogName:url]) {
|
||||
if (![self urlMatchesLogName:url]) {
|
||||
continue;
|
||||
}
|
||||
if ( [[url lastPathComponent] isEqualToString:_logName]) {
|
||||
@@ -710,13 +716,13 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
// If there's been an error getting the resource values, give up
|
||||
break;
|
||||
}
|
||||
if (! [resources[NSURLIsRegularFileKey] boolValue]) {
|
||||
if (!((NSNumber *)resources[NSURLIsRegularFileKey]).boolValue) {
|
||||
continue;
|
||||
}
|
||||
[urls addObject:url];
|
||||
}
|
||||
|
||||
if (! errorOut) {
|
||||
if (!errorOut) {
|
||||
// Sort the URLs before beginning enumeration for the caller
|
||||
[urls sortUsingComparator:^NSComparisonResult(NSURL *obj1, NSURL *obj2) {
|
||||
// We can assume all relate to files in the same directory
|
||||
@@ -760,12 +766,12 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
NSNumber *fileExists = nil;
|
||||
[url getResourceValue:&fileExists forKey:NSURLIsRegularFileKey error:nil];
|
||||
|
||||
BOOL createNewFile = ! [fileExists boolValue];
|
||||
BOOL createNewFile = !fileExists.boolValue;
|
||||
|
||||
NSFileHandle *fileHandle = nil;
|
||||
if (!createNewFile) {
|
||||
fileHandle = [NSFileHandle fileHandleForWritingToURL:url error:error];
|
||||
if (! fileHandle) {
|
||||
if (!fileHandle) {
|
||||
// Assume it's because we can't open the file, perhaps for security reasons.
|
||||
// Close and rename the log.
|
||||
[self queue_closeAndRenameLog];
|
||||
@@ -852,19 +858,19 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
}
|
||||
|
||||
// Check if a non-empty file exists, and create the file handle if so
|
||||
NSDictionary *params = [url resourceValuesForKeys:@[NSURLIsRegularFileKey,NSURLFileSizeKey] error:nil];
|
||||
NSDictionary *parameters = [url resourceValuesForKeys:@[NSURLIsRegularFileKey,NSURLFileSizeKey] error:nil];
|
||||
|
||||
if ( [params[NSURLIsRegularFileKey] boolValue]) {
|
||||
if ([params[NSURLFileSizeKey] intValue] > 0) {
|
||||
if (((NSNumber *)parameters[NSURLIsRegularFileKey]).boolValue) {
|
||||
if (((NSNumber *)parameters[NSURLFileSizeKey]).intValue > 0) {
|
||||
NSURL *destinationUrl = [ORKDataLogger nextUrlForDirectoryUrl:_url logName:_logName];
|
||||
ORK_Log_Debug(@"Rollover: %@ to %@", [url lastPathComponent], [destinationUrl lastPathComponent]);
|
||||
[fileManager moveItemAtURL:url toURL:destinationUrl error:nil];
|
||||
if (self.fileProtectionMode == ORKFileProtectionCompleteUnlessOpen) {
|
||||
// Upgrade to complete file protection after roll-over
|
||||
NSError *error = nil;
|
||||
if (! [fileManager setAttributes:@{NSFileProtectionKey : NSFileProtectionComplete}
|
||||
if (![fileManager setAttributes:@{NSFileProtectionKey : NSFileProtectionComplete}
|
||||
ofItemAtPath:[destinationUrl path] error:&error]) {
|
||||
ORK_Log_Debug(@"Error setting NSFileProtectionComplete on %@: %@", destinationUrl, error);
|
||||
ORK_Log_Warning(@"Error setting NSFileProtectionComplete on %@: %@", destinationUrl, error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -883,7 +889,7 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
NSURL *url = [self currentLogFileURL];
|
||||
NSDictionary *parameters = [url resourceValuesForKeys:@[NSURLIsRegularFileKey, NSURLFileSizeKey, NSURLCreationDateKey] error:nil];
|
||||
|
||||
NSInteger fileSize = [parameters[NSURLFileSizeKey] integerValue];
|
||||
NSInteger fileSize = ((NSNumber *)parameters[NSURLFileSizeKey]).integerValue;
|
||||
NSDate *creationDate = parameters[NSURLCreationDateKey];
|
||||
|
||||
BOOL exceededSizeThreshold = ( (self.maximumCurrentLogFileSize > 0) && (fileSize >= self.maximumCurrentLogFileSize));
|
||||
@@ -942,7 +948,7 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
return success;
|
||||
}
|
||||
|
||||
- (BOOL)queue_removeUploadedFiles:(NSArray *)fileURLs withError:(NSError * __autoreleasing *)error {
|
||||
- (BOOL)queue_removeUploadedFiles:(NSArray<NSURL *> *)fileURLs withError:(NSError * __autoreleasing *)error {
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
__block NSMutableArray *errors = [NSMutableArray array];
|
||||
BOOL success = [self queue_enumerateLogs:^(NSURL *logFileUrl, BOOL *stop) {
|
||||
@@ -962,7 +968,7 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
} error:error];
|
||||
|
||||
// Reporting multiple errors
|
||||
if ([errors count]) {
|
||||
if (errors.count) {
|
||||
if (!success && error && *error) {
|
||||
[errors addObject:*error];
|
||||
*error = [NSError errorWithDomain:ORKErrorDomain code:ORKErrorMultipleErrors userInfo:@{NSLocalizedDescriptionKey : ORKLocalizedString(@"ERROR_DATALOGGER_MULTIPLE", nil), @"errors" : errors}];
|
||||
@@ -1036,11 +1042,17 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
|
||||
@implementation ORKDataLoggerManager
|
||||
|
||||
|
||||
- (instancetype)init {
|
||||
ORKThrowMethodUnavailableException();
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (instancetype)initWithDirectory:(NSURL *)directory delegate:(id<ORKDataLoggerManagerDelegate>)delegate {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_directory = directory;
|
||||
if (! [[NSFileManager defaultManager] fileExistsAtPath:[_directory path]]) {
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:[_directory path]]) {
|
||||
@throw [NSException exceptionWithName:NSGenericException reason:@"directory does not exist" userInfo:nil];
|
||||
}
|
||||
_delegate = delegate;
|
||||
@@ -1053,39 +1065,43 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSDictionary *configuration = [NSDictionary dictionaryWithContentsOfURL:[_directory URLByAppendingPathComponent:kORKDataLoggerManagerConfigurationFilename]];
|
||||
NSDictionary *configuration = [NSDictionary dictionaryWithContentsOfURL:[_directory URLByAppendingPathComponent:ORKDataLoggerManagerConfigurationFilename]];
|
||||
[self loadConfiguration:configuration];
|
||||
|
||||
_observer = [[ORKObjectObserver alloc] initWithObject:self keys:@[@"pendingUploadBytesThreshold",@"totalBytesThreshold"] selector:@selector(configurationDidChange)];
|
||||
_observer = [[ORKObjectObserver alloc] initWithObject:self keys:@[@"pendingUploadBytesThreshold", @"totalBytesThreshold"] selector:@selector(configurationDidChange)];
|
||||
|
||||
[self setNeedsUpdateBytes];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
static NSString *const PendingUploadBytesThresholdKey = @"pendingUploadBytesThreshold";
|
||||
static NSString *const TotalBytesThresholdKey = @"totalBytesThreshold";
|
||||
static NSString *const LoggerConfigurationsKey = @"loggers";
|
||||
|
||||
- (void)loadConfiguration:(NSDictionary *)configuration {
|
||||
self.pendingUploadBytesThreshold = [configuration[@"pendingUploadBytesThreshold"] unsignedLongLongValue];
|
||||
self.totalBytesThreshold = [configuration[@"totalBytesThreshold"] unsignedLongLongValue];
|
||||
self.pendingUploadBytesThreshold = ((NSNumber *)configuration[PendingUploadBytesThresholdKey]).unsignedLongLongValue;
|
||||
self.totalBytesThreshold = ((NSNumber *)configuration[TotalBytesThresholdKey]).unsignedLongLongValue;
|
||||
|
||||
NSMutableDictionary *records = [NSMutableDictionary dictionary];
|
||||
for (NSDictionary *loggerConfig in configuration[@"loggers"]) {
|
||||
ORKDataLogger *logger = [[ORKDataLogger alloc] initWithDirectory:_directory configuration:loggerConfig delegate:self];
|
||||
for (NSDictionary *loggerConfiguration in configuration[LoggerConfigurationsKey]) {
|
||||
ORKDataLogger *logger = [[ORKDataLogger alloc] initWithDirectory:_directory configuration:loggerConfiguration delegate:self];
|
||||
records[logger.logName] = logger;
|
||||
}
|
||||
_records = records;
|
||||
}
|
||||
|
||||
- (NSDictionary *)queue_configuration {
|
||||
NSMutableArray *loggerConfigurations = [[_records allValues] valueForKey:@"configuration"];
|
||||
NSMutableArray *loggerConfigurations = [_records.allValues valueForKey:@"configuration"];
|
||||
|
||||
return @{@"pendingUploadBytesThreshold" : @(self.pendingUploadBytesThreshold),
|
||||
@"totalBytesThreshold" : @(self.totalBytesThreshold),
|
||||
@"loggers" : loggerConfigurations };
|
||||
return @{PendingUploadBytesThresholdKey : @(self.pendingUploadBytesThreshold),
|
||||
TotalBytesThresholdKey : @(self.totalBytesThreshold),
|
||||
LoggerConfigurationsKey : loggerConfigurations };
|
||||
}
|
||||
|
||||
- (void)queue_synchronizeConfiguration {
|
||||
NSDictionary *configuration = [self queue_configuration];
|
||||
[configuration writeToURL:[_directory URLByAppendingPathComponent:kORKDataLoggerManagerConfigurationFilename] atomically:YES];
|
||||
[configuration writeToURL:[_directory URLByAppendingPathComponent:ORKDataLoggerManagerConfigurationFilename] atomically:YES];
|
||||
}
|
||||
|
||||
- (void)configurationDidChange {
|
||||
@@ -1102,8 +1118,8 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
ORKDataLogger *dataLogger = [[ORKDataLogger alloc] initWithDirectory:_directory logName:logName formatter:formatter delegate:self];
|
||||
dataLogger.delegate = nil;
|
||||
// Pick suitable defaults for a typical use pattern
|
||||
dataLogger.maximumCurrentLogFileLifetime = kORKDataLoggerManagerDefaultLogFileLifetime;
|
||||
dataLogger.maximumCurrentLogFileSize = kORKDataLoggerManagerDefaultLogFileSize;
|
||||
dataLogger.maximumCurrentLogFileLifetime = ORKDataLoggerManagerDefaultLogFileLifetime;
|
||||
dataLogger.maximumCurrentLogFileSize = ORKDataLoggerManagerDefaultLogFileSize;
|
||||
dataLogger.delegate = self;
|
||||
|
||||
_records[logName] = dataLogger;
|
||||
@@ -1151,10 +1167,10 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
return dataLogger;
|
||||
}
|
||||
|
||||
- (NSArray *)logNames {
|
||||
__block NSArray *logNames = nil;
|
||||
- (NSArray<NSString *> *)logNames {
|
||||
__block NSArray<NSString *> *logNames = nil;
|
||||
dispatch_sync(_queue, ^{
|
||||
logNames = [_records allKeys];
|
||||
logNames = _records.allKeys;
|
||||
});
|
||||
return logNames;
|
||||
}
|
||||
@@ -1163,7 +1179,7 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
BOOL success = YES;
|
||||
NSMutableArray *allFiles = [NSMutableArray array];
|
||||
// Collect all the log file URLs so we can sort them by date rather than enumerating by logger.
|
||||
for (ORKDataLogger *logger in [_records allValues]) {
|
||||
for (ORKDataLogger *logger in _records.allValues) {
|
||||
success = [logger enumerateLogsNeedingUpload:^(NSURL *logFileUrl, BOOL *stop) {
|
||||
[allFiles addObject:logFileUrl];
|
||||
} error:error];
|
||||
@@ -1210,13 +1226,13 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
return success;
|
||||
}
|
||||
|
||||
- (BOOL)queue_removeUploadedFiles:(NSArray *)fileURLs error:(NSError * __autoreleasing *)error {
|
||||
- (BOOL)queue_removeUploadedFiles:(NSArray<NSURL *> *)fileURLs error:(NSError * __autoreleasing *)error {
|
||||
BOOL success = YES;
|
||||
NSMutableArray *notRemoved = [NSMutableArray array];
|
||||
for (NSURL *url in fileURLs) {
|
||||
NSString *logName = [url ork_logNameInDirectory:_directory];
|
||||
|
||||
if (! _records[logName]) {
|
||||
if (!_records[logName]) {
|
||||
@throw [NSException exceptionWithName:NSGenericException reason:@"URL is not from a known logger" userInfo:@{@"url":url}];
|
||||
}
|
||||
|
||||
@@ -1227,13 +1243,13 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
success = NO;
|
||||
}
|
||||
}
|
||||
if (error && [notRemoved count]) {
|
||||
if (error && notRemoved.count) {
|
||||
*error = [NSError errorWithDomain:ORKErrorDomain code:ORKErrorMultipleErrors userInfo:@{@"notRemoved":notRemoved}];
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
- (BOOL)removeUploadedFiles:(NSArray *)fileURLs error:(NSError * __autoreleasing *)error {
|
||||
- (BOOL)removeUploadedFiles:(NSArray<NSURL *> *)fileURLs error:(NSError * __autoreleasing *)error {
|
||||
|
||||
__block BOOL success = YES;
|
||||
dispatch_sync(_queue, ^{
|
||||
@@ -1242,13 +1258,13 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
return success;
|
||||
}
|
||||
|
||||
- (BOOL)queue_unmarkUploadedFiles:(NSArray *)fileURLs error:(NSError * __autoreleasing *)error {
|
||||
- (BOOL)queue_unmarkUploadedFiles:(NSArray<NSURL *> *)fileURLs error:(NSError * __autoreleasing *)error {
|
||||
BOOL success = YES;
|
||||
NSMutableArray *notRemoved = [NSMutableArray array];
|
||||
NSMutableArray<NSURL *> *notRemoved = [NSMutableArray array];
|
||||
for (NSURL *url in fileURLs) {
|
||||
NSString *logName = [url ork_logNameInDirectory:_directory];
|
||||
ORKDataLogger *logger = _records[logName];
|
||||
if (! logger) {
|
||||
if (!logger) {
|
||||
@throw [NSException exceptionWithName:NSGenericException reason:@"URL is not from a known logger" userInfo:@{@"url":url}];
|
||||
}
|
||||
|
||||
@@ -1259,13 +1275,13 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
success = NO;
|
||||
}
|
||||
}
|
||||
if (error && [notRemoved count]) {
|
||||
if (error && notRemoved.count) {
|
||||
*error = [NSError errorWithDomain:ORKErrorDomain code:ORKErrorMultipleErrors userInfo:@{@"notRemoved":notRemoved}];
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
- (BOOL)unmarkUploadedFiles:(NSArray *)fileURLs error:(NSError *__autoreleasing *)error {
|
||||
- (BOOL)unmarkUploadedFiles:(NSArray<NSURL *> *)fileURLs error:(NSError *__autoreleasing *)error {
|
||||
__block BOOL success = YES;
|
||||
dispatch_sync(_queue, ^{
|
||||
success = [self queue_unmarkUploadedFiles:fileURLs error:error];
|
||||
@@ -1288,7 +1304,7 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
|
||||
if (totalBytes > bytes) {
|
||||
for (ORKDataLogger *logger in [_records allValues]) {
|
||||
for (ORKDataLogger *logger in _records.allValues) {
|
||||
[logger enumerateLogsAlreadyUploaded:^(NSURL *logFileUrl, BOOL *stop) {
|
||||
unsigned long long fileSize = [[fileManager attributesOfItemAtPath:[logFileUrl path] error:nil] fileSize];
|
||||
if (fileSize > 0) {
|
||||
@@ -1341,7 +1357,7 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
- (void)queue_updateBytes {
|
||||
unsigned long long pending = 0;
|
||||
unsigned long long uploaded = 0;
|
||||
for (ORKDataLogger *logger in [_records allValues]) {
|
||||
for (ORKDataLogger *logger in _records.allValues) {
|
||||
pending += logger.pendingBytes;
|
||||
uploaded += logger.uploadedBytes;
|
||||
}
|
||||
@@ -1355,14 +1371,14 @@ static NSInteger _ORKJSON_terminatorLength = 0;
|
||||
if (exceededPendingThreshold && !_pendingUploadDelegateSent) {
|
||||
[self.delegate dataLoggerManager:self pendingUploadBytesReachedThreshold:pending];
|
||||
_pendingUploadDelegateSent = YES;
|
||||
} else if (! exceededPendingThreshold) {
|
||||
} else if (!exceededPendingThreshold) {
|
||||
_pendingUploadDelegateSent = NO;
|
||||
}
|
||||
|
||||
if (exceededTotalThreshold && !_totalBytesDelegateSent) {
|
||||
[self.delegate dataLoggerManager:self totalBytesReachedThreshold:(pending + uploaded)];
|
||||
_totalBytesDelegateSent = YES;
|
||||
} else if (! exceededTotalThreshold) {
|
||||
} else if (!exceededTotalThreshold) {
|
||||
_totalBytesDelegateSent = NO;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,37 +84,32 @@
|
||||
- (void)start {
|
||||
[super start];
|
||||
|
||||
if (! _logger) {
|
||||
if (!_logger) {
|
||||
NSError *err = nil;
|
||||
_logger = [self makeJSONDataLoggerWithError:&err];
|
||||
if (! _logger) {
|
||||
if (!_logger) {
|
||||
[self finishRecordingWithError:err];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self.motionManager = [self createMotionManager];
|
||||
self.motionManager.deviceMotionUpdateInterval = 1.0/_frequency;
|
||||
self.motionManager.deviceMotionUpdateInterval = 1.0 / _frequency;
|
||||
|
||||
self.uptime = [NSProcessInfo processInfo].systemUptime;
|
||||
|
||||
[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];
|
||||
});
|
||||
@@ -170,11 +165,6 @@
|
||||
@end
|
||||
|
||||
|
||||
@interface ORKDeviceMotionRecorderConfiguration ()
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKDeviceMotionRecorderConfiguration
|
||||
|
||||
#pragma clang diagnostic push
|
||||
|
||||
@@ -48,8 +48,6 @@
|
||||
ORKTintedImageView *_imageView;
|
||||
NSLengthFormatter *_lengthFormatter;
|
||||
NSLayoutConstraint *_imageRatioConstraint;
|
||||
ORKScreenType _screenType;
|
||||
NSArray *_constraints;
|
||||
NSLayoutConstraint *_topConstraint;
|
||||
}
|
||||
|
||||
@@ -69,13 +67,12 @@
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
_screenType = ORKScreenTypeiPhone4;
|
||||
_timerLabel = [ORKQuantityLabel new];
|
||||
_quantityPairView = [ORKQuantityPairView new];
|
||||
_imageSpacer1 = [UIView new];
|
||||
[_imageSpacer1 setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
_imageSpacer1.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
_imageSpacer2 = [UIView new];
|
||||
[_imageSpacer2 setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
_imageSpacer2.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[self addSubview:_imageSpacer1];
|
||||
[self addSubview:_imageSpacer2];
|
||||
[self heartRateView].image = [UIImage imageNamed:@"heart-fitness" inBundle:[NSBundle bundleForClass:[self class]] compatibleWithTraitCollection:nil];
|
||||
@@ -96,7 +93,7 @@
|
||||
self.hasDistance = _hasDistance;
|
||||
|
||||
#if LAYOUT_TEST
|
||||
self.timeLeft = 60*5;
|
||||
self.timeLeft = 60 * 5;
|
||||
self.hasHeartRate = YES;
|
||||
self.hasDistance = YES;
|
||||
self.distanceInMeters = 100;
|
||||
@@ -113,7 +110,7 @@
|
||||
[self addSubview:_quantityPairView];
|
||||
[self addSubview:_imageView];
|
||||
[self addSubview:_timerLabel];
|
||||
[self setupConstraints];
|
||||
[self setUpConstraints];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(localeDidChange:) name:NSCurrentLocaleDidChangeNotification object:nil];
|
||||
|
||||
@@ -139,105 +136,114 @@
|
||||
|
||||
- (void)willMoveToWindow:(UIWindow *)newWindow {
|
||||
[super willMoveToWindow:newWindow];
|
||||
_screenType = ORKGetScreenTypeForWindow(newWindow);
|
||||
[self updateConstraintConstants];
|
||||
[self updateConstraintConstantsForWindow:newWindow];
|
||||
}
|
||||
|
||||
- (void)updateConstraintConstants {
|
||||
ORKScreenType screenType = _screenType;
|
||||
const CGFloat CaptionBaselineToTimerTop = ORKGetMetricForScreenType(ORKScreenMetricCaptionBaselineToFitnessTimerTop, screenType);
|
||||
const CGFloat CaptionBaselineToStepViewTop = ORKGetMetricForScreenType(ORKScreenMetricLearnMoreBaselineToStepViewTop, screenType);
|
||||
- (void)updateConstraintConstantsForWindow:(UIWindow *)window {
|
||||
const CGFloat CaptionBaselineToTimerTop = ORKGetMetricForWindow(ORKScreenMetricCaptionBaselineToFitnessTimerTop, window);
|
||||
const CGFloat CaptionBaselineToStepViewTop = ORKGetMetricForWindow(ORKScreenMetricLearnMoreBaselineToStepViewTop, window);
|
||||
_topConstraint.constant = (CaptionBaselineToTimerTop - CaptionBaselineToStepViewTop);
|
||||
}
|
||||
|
||||
- (void)setupConstraints {
|
||||
- (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]];
|
||||
|
||||
[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];
|
||||
multiplier:1.0
|
||||
constant:0.0];
|
||||
[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:NSLayoutAttributeCenterX
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_timerLabel
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationLessThanOrEqual
|
||||
toItem:self attribute:NSLayoutAttributeWidth
|
||||
multiplier:1
|
||||
constant:0]];
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_imageView
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationLessThanOrEqual
|
||||
toItem:self attribute:NSLayoutAttributeWidth
|
||||
multiplier:1
|
||||
constant:0]];
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_imageView][_imageSpacer2(>=0)][_quantityPairView]|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil
|
||||
views:views]];
|
||||
|
||||
[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]];
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_imageSpacer2
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1
|
||||
constant:0]];
|
||||
multiplier:1.0
|
||||
constant:0.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];
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_quantityPairView]|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil
|
||||
views:views]];
|
||||
NSLayoutConstraint *imageSpacerHeightConstraint = [NSLayoutConstraint constraintWithItem:_imageSpacer1
|
||||
attribute:NSLayoutAttributeHeight
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1.0
|
||||
constant:ORKScreenMetricMaxDimension];
|
||||
imageSpacerHeightConstraint.priority = UILayoutPriorityDefaultLow - 1;
|
||||
[constraints addObject:imageSpacerHeightConstraint];
|
||||
|
||||
NSLayoutConstraint *maxWidthConstraint = [NSLayoutConstraint constraintWithItem:self
|
||||
[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
|
||||
multiplier:1.0
|
||||
constant:ORKScreenMetricMaxDimension];
|
||||
maxWidthConstraint.priority = UILayoutPriorityRequired-1;
|
||||
maxWidthConstraint.priority = UILayoutPriorityRequired - 1;
|
||||
[constraints addObject:maxWidthConstraint];
|
||||
|
||||
[self addConstraints:constraints];
|
||||
[NSLayoutConstraint activateConstraints:constraints];
|
||||
[self updateConstraintConstantsForWindow:self.window];
|
||||
}
|
||||
|
||||
- (void)setImage:(UIImage *)image {
|
||||
@@ -253,8 +259,8 @@
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_imageView
|
||||
attribute:NSLayoutAttributeWidth
|
||||
multiplier:size
|
||||
.height/size.width constant:0];
|
||||
multiplier:size.height / size.width
|
||||
constant:0.0];
|
||||
_imageRatioConstraint.active = YES;
|
||||
}
|
||||
}
|
||||
@@ -277,7 +283,7 @@
|
||||
}
|
||||
|
||||
- (void)updateKeylineVisible {
|
||||
[_quantityPairView setKeylineHidden:! (_hasDistance && _hasHeartRate)];
|
||||
[_quantityPairView setKeylineHidden:!(_hasDistance && _hasHeartRate)];
|
||||
}
|
||||
|
||||
- (void)setDistanceInMeters:(double)distanceInMeters {
|
||||
@@ -304,7 +310,7 @@
|
||||
double conversionFactor = 1.0;
|
||||
if ([hkUnit isNull] && (unit == NSLengthFormatterUnitYard)) {
|
||||
hkUnit = [HKUnit footUnit];
|
||||
conversionFactor = 1/3.0;
|
||||
conversionFactor = 1.0 / 3.0;
|
||||
}
|
||||
HKQuantity *quantity = [HKQuantity quantityWithUnit:[HKUnit meterUnit] doubleValue:displayDistance];
|
||||
distanceString = [_lengthFormatter.numberFormatter stringFromNumber:@([quantity doubleValueForUnit:hkUnit]*conversionFactor)];
|
||||
|
||||
@@ -52,8 +52,8 @@
|
||||
|
||||
NSTimeInterval const ORKFitnessStepMinimumDuration = 5.0;
|
||||
|
||||
if ( self.stepDuration < ORKFitnessStepMinimumDuration) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"rest duration can not be shorter than %@ seconds.", @(ORKFitnessStepMinimumDuration)] userInfo:nil];
|
||||
if (self.stepDuration < ORKFitnessStepMinimumDuration) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"rest duration cannot be shorter than %@ seconds.", @(ORKFitnessStepMinimumDuration)] userInfo:nil];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ static const NSInteger _HealthAnchoredQueryLimit = 100;
|
||||
|
||||
- (void)query_logResults:(NSArray *)results withAnchor:(NSUInteger)newAnchor {
|
||||
|
||||
NSUInteger resultCount = [results count];
|
||||
NSUInteger resultCount = results.count;
|
||||
if (resultCount == 0) {
|
||||
return;
|
||||
}
|
||||
@@ -103,10 +103,10 @@ static const NSInteger _HealthAnchoredQueryLimit = 100;
|
||||
}];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self updateMostRecentSample:[results lastObject]];
|
||||
[self updateMostRecentSample:results.lastObject];
|
||||
|
||||
NSError *error = nil;
|
||||
if (! [_logger appendObjects:dictionaries error:&error]) {
|
||||
if (![_logger appendObjects:dictionaries error:&error]) {
|
||||
// Logger writes are unrecoverable
|
||||
[self finishRecordingWithError:error];
|
||||
return;
|
||||
@@ -122,7 +122,7 @@ static const NSInteger _HealthAnchoredQueryLimit = 100;
|
||||
}
|
||||
|
||||
- (void)doFetchNewData {
|
||||
if (! _healthStore || ! _isRecording) {
|
||||
if (!_healthStore || !_isRecording) {
|
||||
return;
|
||||
}
|
||||
NSAssert(_samplePredicate != nil, @"Sample predicate should be non-nil if recording");
|
||||
@@ -137,7 +137,7 @@ static const NSInteger _HealthAnchoredQueryLimit = 100;
|
||||
{
|
||||
if (error) {
|
||||
// An error in the query's not the end of the world: we'll probably get another chance. Just log it.
|
||||
ORK_Log_Debug(@"Anchored query error: %@", error);
|
||||
ORK_Log_Warning(@"Anchored query error: %@", error);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -151,16 +151,16 @@ static const NSInteger _HealthAnchoredQueryLimit = 100;
|
||||
- (void)start {
|
||||
[super start];
|
||||
|
||||
if (! _logger) {
|
||||
if (!_logger) {
|
||||
NSError *err = nil;
|
||||
_logger = [self makeJSONDataLoggerWithError:&err];
|
||||
if (! _logger) {
|
||||
if (!_logger) {
|
||||
[self finishRecordingWithError:err];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (! [HKHealthStore isHealthDataAvailable]) {
|
||||
if (![HKHealthStore isHealthDataAvailable]) {
|
||||
[self finishRecordingWithError:[NSError errorWithDomain:NSCocoaErrorDomain
|
||||
code:NSFeatureUnsupportedError
|
||||
userInfo:@{@"recorder" : self}]];
|
||||
@@ -178,16 +178,6 @@ static const NSInteger _HealthAnchoredQueryLimit = 100;
|
||||
}
|
||||
}
|
||||
|
||||
[_healthStore enableBackgroundDeliveryForType:_quantityType
|
||||
frequency:HKUpdateFrequencyImmediate
|
||||
withCompletion:^(BOOL success, NSError *error) {
|
||||
|
||||
// Doesn't really matter if this succeeds, but nice if it does.
|
||||
if (! success) {
|
||||
ORK_Log_Debug(@"Failed to enable background delivery: %@", error);
|
||||
}
|
||||
}];
|
||||
|
||||
_lastSample = nil;
|
||||
_samplePredicate = [HKQuery predicateForSamplesWithStartDate:[NSDate date] endDate:nil options:HKQueryOptionStrictStartDate];
|
||||
|
||||
@@ -222,7 +212,7 @@ static const NSInteger _HealthAnchoredQueryLimit = 100;
|
||||
}
|
||||
|
||||
- (void)stop {
|
||||
if (! _isRecording) {
|
||||
if (!_isRecording) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -275,11 +265,6 @@ static const NSInteger _HealthAnchoredQueryLimit = 100;
|
||||
@end
|
||||
|
||||
|
||||
@interface ORKHealthQuantityTypeRecorderConfiguration ()
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKHealthQuantityTypeRecorderConfiguration
|
||||
|
||||
#pragma clang diagnostic push
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
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 "ORKCustomStepView_Internal.h"
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol ORKHolePegTestPlaceContentViewDelegate;
|
||||
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKHolePegTestPlaceContentView : ORKActiveStepCustomView
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE;
|
||||
- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
|
||||
- (instancetype)initWithMovingDirection:(ORKBodySagittal)movingDirection rotated:(BOOL)rotated NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@property (nonatomic, assign) ORKBodySagittal movingDirection;
|
||||
@property (nonatomic, assign) double threshold;
|
||||
@property (nonatomic, assign, getter = isRotated) BOOL rotated;
|
||||
@property (nonatomic, weak) id<ORKHolePegTestPlaceContentViewDelegate> delegate;
|
||||
|
||||
- (void)setProgress:(CGFloat)progress animated:(BOOL)animated;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@protocol ORKHolePegTestPlaceContentViewDelegate <NSObject>
|
||||
|
||||
- (void)holePegTestPlaceDidProgress:(ORKHolePegTestPlaceContentView *)holePegTestPlaceContentView;
|
||||
- (void)holePegTestPlaceDidSucceed:(ORKHolePegTestPlaceContentView *)holePegTestPlaceContentView withDistance:(CGFloat)distance;
|
||||
- (void)holePegTestPlaceDidFail:(ORKHolePegTestPlaceContentView *)holePegTestPlaceContentView;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,397 @@
|
||||
/*
|
||||
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 "ORKHolePegTestPlaceContentView.h"
|
||||
#import "ORKHolePegTestPlacePegView.h"
|
||||
#import "ORKHolePegTestPlaceHoleView.h"
|
||||
#import "ORKDirectionView.h"
|
||||
#import "ORKHelpers.h"
|
||||
#import "ORKSkin.h"
|
||||
|
||||
|
||||
static const CGFloat ORKOrientationThreshold = 12.0f;
|
||||
static const CGFloat ORKHolePegViewDiameter = 88.0f;
|
||||
#define degreesToRadians(degrees) ((degrees) / 180.0 * M_PI)
|
||||
|
||||
|
||||
@interface ORKHolePegTestPlaceContentView () <UIGestureRecognizerDelegate>
|
||||
|
||||
@property (nonatomic, strong) UIProgressView *progressView;
|
||||
@property (nonatomic, strong) ORKHolePegTestPlacePegView *pegView;
|
||||
@property (nonatomic, strong) ORKHolePegTestPlaceHoleView *holeView;
|
||||
@property (nonatomic, strong) ORKDirectionView *directionView;
|
||||
@property (nonatomic, copy) NSArray *constraints;
|
||||
|
||||
@property (nonatomic, strong) UIPinchGestureRecognizer *pinchRecognizer;
|
||||
@property (nonatomic, strong) UIPanGestureRecognizer *panRecognizer;
|
||||
@property (nonatomic, strong) UIRotationGestureRecognizer *rotationRecognizer;
|
||||
@property (nonatomic, assign, getter = isMovable) BOOL movable;
|
||||
@property (nonatomic, assign, getter = hasMoveEnded) BOOL moveEnded;
|
||||
@property (nonatomic, assign) CGFloat rotation;
|
||||
@property (nonatomic, assign) CGFloat rotationOffset;
|
||||
@property (nonatomic, assign) CGPoint translation;
|
||||
@property (nonatomic, assign) CGPoint translationOffset;
|
||||
@property (nonatomic, assign) CGPoint startPoint;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKHolePegTestPlaceContentView
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
ORKThrowMethodUnavailableException();
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
ORKThrowMethodUnavailableException();
|
||||
}
|
||||
|
||||
- (instancetype)initWithMovingDirection:(ORKBodySagittal)movingDirection rotated:(BOOL)rotated {
|
||||
self = [super initWithFrame:CGRectZero];
|
||||
if (self) {
|
||||
self.movingDirection = movingDirection;
|
||||
self.rotated = rotated;
|
||||
|
||||
self.progressView = [UIProgressView new];
|
||||
self.progressView.progressTintColor = self.tintColor;
|
||||
[self.progressView setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
[self.progressView setAlpha:0];
|
||||
[self addSubview:self.progressView];
|
||||
|
||||
self.holeView = [[ORKHolePegTestPlaceHoleView alloc] initWithFrame:CGRectMake(0, 0, ORKHolePegViewDiameter, ORKHolePegViewDiameter)];
|
||||
self.holeView.rotated = self.isRotated;
|
||||
[self.holeView setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
[self addSubview:self.holeView];
|
||||
|
||||
self.pegView = [[ORKHolePegTestPlacePegView alloc] initWithFrame:CGRectMake(0, 0, ORKHolePegViewDiameter, ORKHolePegViewDiameter)];
|
||||
self.pegView.rotated = self.isRotated;
|
||||
[self.pegView setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
[self addSubview:self.pegView];
|
||||
|
||||
self.directionView = [[ORKDirectionView alloc] initWithOrientation:(self.movingDirection == ORKBodySagittalLeft) ? ORKBodySagittalRight : ORKBodySagittalLeft];
|
||||
[self.directionView setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
[self addSubview:self.directionView];
|
||||
|
||||
[self setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
[self setNeedsUpdateConstraints];
|
||||
|
||||
self.movable = NO;
|
||||
self.moveEnded = NO;
|
||||
self.startPoint = CGPointZero;
|
||||
|
||||
self.pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self
|
||||
action:@selector(handlePinch:)];
|
||||
self.pinchRecognizer.delegate = self;
|
||||
[self addGestureRecognizer:self.pinchRecognizer];
|
||||
|
||||
self.panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self
|
||||
action:@selector(handlePan:)];
|
||||
self.panRecognizer.delegate = self;
|
||||
[self addGestureRecognizer:self.panRecognizer];
|
||||
|
||||
if (rotated) {
|
||||
self.rotationRecognizer = [[UIRotationGestureRecognizer alloc] initWithTarget:self
|
||||
action:@selector(handleRotate:)];
|
||||
self.rotationRecognizer.delegate = self;
|
||||
[self addGestureRecognizer:self.rotationRecognizer];
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)tintColorDidChange {
|
||||
[super tintColorDidChange];
|
||||
self.progressView.progressTintColor = self.tintColor;
|
||||
}
|
||||
|
||||
- (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)updateLayoutMargins {
|
||||
CGFloat margin = ORKStandardHorizontalMarginForView(self);
|
||||
self.layoutMargins = (UIEdgeInsets){.left = margin * 2, .right = margin * 2};
|
||||
}
|
||||
|
||||
- (void)setFrame:(CGRect)frame {
|
||||
[super setFrame:frame];
|
||||
[self updateLayoutMargins];
|
||||
}
|
||||
|
||||
- (void)setBounds:(CGRect)bounds {
|
||||
[super setBounds:bounds];
|
||||
[self updateLayoutMargins];
|
||||
}
|
||||
|
||||
- (void)updateConstraints {
|
||||
if ([self.constraints count]) {
|
||||
[NSLayoutConstraint deactivateConstraints:self.constraints];
|
||||
self.constraints = nil;
|
||||
}
|
||||
|
||||
NSMutableArray *constraintsArray = [NSMutableArray array];
|
||||
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(_progressView, _pegView, _holeView, _directionView);
|
||||
NSDictionary *metrics = @{@"diameter" : @(ORKHolePegViewDiameter)};
|
||||
|
||||
[constraintsArray addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_progressView]-|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil views:views]];
|
||||
|
||||
[constraintsArray addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:(self.movingDirection == ORKBodySagittalLeft) ? @"H:|-[_pegView]->=0-[_holeView]-|" : @"H:|-[_holeView]->=0-[_pegView]-|"
|
||||
options:NSLayoutFormatAlignAllCenterY
|
||||
metrics:nil views:views]];
|
||||
|
||||
[constraintsArray addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_progressView]"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil views:views]];
|
||||
|
||||
[constraintsArray addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|->=0-[_pegView(diameter)]->=0-|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:metrics views:views]];
|
||||
|
||||
[constraintsArray addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|->=0-[_holeView]->=0-|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil views:views]];
|
||||
|
||||
[constraintsArray addObject:[NSLayoutConstraint constraintWithItem:self.pegView
|
||||
attribute:NSLayoutAttributeCenterY
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeCenterY
|
||||
multiplier:1
|
||||
constant:0]];
|
||||
|
||||
[constraintsArray addObject:[NSLayoutConstraint constraintWithItem:self.directionView
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
multiplier:1
|
||||
constant:0]];
|
||||
|
||||
[constraintsArray addObject:[NSLayoutConstraint constraintWithItem:self.directionView
|
||||
attribute:NSLayoutAttributeCenterY
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeCenterY
|
||||
multiplier:1
|
||||
constant:0]];
|
||||
|
||||
self.constraints = constraintsArray;
|
||||
[self addConstraints:self.constraints];
|
||||
|
||||
[NSLayoutConstraint activateConstraints:self.constraints];
|
||||
[super updateConstraints];
|
||||
}
|
||||
|
||||
#pragma mark - gesture recognizer methods
|
||||
|
||||
- (void)pickupPegWithGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer {
|
||||
CGPoint touch = [gestureRecognizer locationInView:self];
|
||||
CGPoint touch1 = [gestureRecognizer locationOfTouch:0 inView:self];
|
||||
CGPoint touch2 = [gestureRecognizer locationOfTouch:1 inView:self];
|
||||
double distance = hypot(touch1.x - touch2.x, touch1.y - touch2.y);
|
||||
|
||||
if (distance < 3 * CGRectGetWidth(self.pegView.frame) &&
|
||||
CGRectContainsPoint(self.pegView.frame, touch)) {
|
||||
self.movable = YES;
|
||||
} else {
|
||||
self.movable = NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handlePinch:(UIPinchGestureRecognizer *)pinchGestureRecognizer {
|
||||
if ([pinchGestureRecognizer numberOfTouches] == 2) {
|
||||
[self pickupPegWithGestureRecognizer:pinchGestureRecognizer];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handlePan:(UIPanGestureRecognizer *)panGestureRecognizer {
|
||||
if ([panGestureRecognizer numberOfTouches] != 2 ||
|
||||
panGestureRecognizer.state == UIGestureRecognizerStateEnded ||
|
||||
panGestureRecognizer.state == UIGestureRecognizerStateCancelled ||
|
||||
panGestureRecognizer.state == UIGestureRecognizerStateFailed) {
|
||||
[self resetTransformAtPoint:[panGestureRecognizer locationInView:self]];
|
||||
} else {
|
||||
if (self.isMovable) {
|
||||
self.translation = CGPointMake([panGestureRecognizer translationInView:self].x - self.translationOffset.x,
|
||||
[panGestureRecognizer translationInView:self].y - self.translationOffset.y);
|
||||
[self updateTransformAtPoint:[panGestureRecognizer locationInView:self]];
|
||||
} else {
|
||||
self.translationOffset = CGPointMake([panGestureRecognizer translationInView:self].x - self.translation.x,
|
||||
[panGestureRecognizer translationInView:self].y - self.translation.y);
|
||||
if (CGPointEqualToPoint(self.startPoint, CGPointZero)) {
|
||||
[self pickupPegWithGestureRecognizer:panGestureRecognizer];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleRotate:(UIRotationGestureRecognizer *)rotationGestureRecognizer {
|
||||
if ([rotationGestureRecognizer numberOfTouches] != 2 ||
|
||||
rotationGestureRecognizer.state == UIGestureRecognizerStateEnded ||
|
||||
rotationGestureRecognizer.state == UIGestureRecognizerStateCancelled ||
|
||||
rotationGestureRecognizer.state == UIGestureRecognizerStateFailed) {
|
||||
[self resetTransformAtPoint:[rotationGestureRecognizer locationInView:self]];
|
||||
} else {
|
||||
if (self.isMovable) {
|
||||
self.rotation = rotationGestureRecognizer.rotation - self.rotationOffset;
|
||||
[self updateTransformAtPoint:[rotationGestureRecognizer locationInView:self]];
|
||||
} else {
|
||||
self.rotationOffset = rotationGestureRecognizer.rotation - self.rotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateTransformAtPoint:(CGPoint)point {
|
||||
self.pegView.transform = CGAffineTransformMakeTranslation(self.translation.x, self.translation.y);
|
||||
self.pegView.transform = CGAffineTransformRotate(self.pegView.transform, self.rotation);
|
||||
[self pegViewDidMoveAtPoint:point];
|
||||
}
|
||||
|
||||
- (void)resetTransformAtPoint:(CGPoint)point {
|
||||
if (!self.hasMoveEnded) {
|
||||
self.movable = NO;
|
||||
self.moveEnded = YES;
|
||||
|
||||
self.pinchRecognizer.enabled = NO;
|
||||
self.panRecognizer.enabled = NO;
|
||||
self.rotationRecognizer.enabled = NO;
|
||||
|
||||
BOOL animated = ![self pegViewMoveDidEndAtPoint:point];
|
||||
self.pegView.hidden = !animated;
|
||||
|
||||
[UIView animateWithDuration:animated ? 0.15f : 0.0f
|
||||
delay:0.0f
|
||||
options:UIViewAnimationOptionCurveEaseOut
|
||||
animations:^(){
|
||||
self.pegView.transform = CGAffineTransformIdentity;
|
||||
self.pegView.alpha = 1.0f;
|
||||
}
|
||||
completion:^(BOOL finished){
|
||||
self.rotation = 0.0f;
|
||||
self.rotationOffset = 0.0f;
|
||||
self.translation = CGPointZero;
|
||||
self.translationOffset = CGPointZero;
|
||||
self.pinchRecognizer.enabled = YES;
|
||||
self.panRecognizer.enabled = YES;
|
||||
self.rotationRecognizer.enabled = YES;
|
||||
self.moveEnded = NO;
|
||||
self.pegView.hidden = NO;
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
|
||||
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - peg view delegate
|
||||
|
||||
- (void)pegViewDidMoveAtPoint:(CGPoint)point {
|
||||
self.directionView.hidden = YES;
|
||||
|
||||
if (CGPointEqualToPoint(self.startPoint, CGPointZero)) {
|
||||
self.startPoint = point;
|
||||
}
|
||||
|
||||
if ([self.delegate respondsToSelector:@selector(holePegTestPlaceDidProgress:)]) {
|
||||
[self.delegate holePegTestPlaceDidProgress:self];
|
||||
}
|
||||
|
||||
if (self.holeView.isSuccess) {
|
||||
self.holeView.success = NO;
|
||||
}
|
||||
|
||||
if ([self holeViewContainsPegView]) {
|
||||
self.pegView.alpha = 1.0f;
|
||||
} else {
|
||||
self.pegView.alpha = 0.2f;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)pegViewMoveDidEndAtPoint:(CGPoint)point {
|
||||
self.directionView.hidden = NO;
|
||||
|
||||
BOOL succeeded = NO;
|
||||
if ([self holeViewContainsPegView]) {
|
||||
if ([self.delegate respondsToSelector:@selector(holePegTestPlaceDidSucceed:withDistance:)]) {
|
||||
CGFloat distance = hypotf(point.x - self.startPoint.x, point.y - self.startPoint.y);
|
||||
[self.delegate holePegTestPlaceDidSucceed:self withDistance:distance];
|
||||
}
|
||||
self.holeView.success = YES;
|
||||
succeeded = YES;
|
||||
} else {
|
||||
if ([self.delegate respondsToSelector:@selector(holePegTestPlaceDidFail:)]) {
|
||||
[self.delegate holePegTestPlaceDidFail:self];
|
||||
}
|
||||
self.holeView.success = NO;
|
||||
}
|
||||
self.startPoint = CGPointZero;
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
- (BOOL)holeViewContainsPegView {
|
||||
CGRect detectionFrame = CGRectMake(CGRectGetMidX(self.holeView.frame) - (self.threshold * CGRectGetWidth(self.holeView.frame) / 2),
|
||||
CGRectGetMidY(self.holeView.frame) - (self.threshold * CGRectGetHeight(self.holeView.frame) / 2),
|
||||
self.threshold * CGRectGetWidth(self.holeView.frame),
|
||||
self.threshold * CGRectGetHeight(self.holeView.frame));
|
||||
|
||||
CGPoint pegCenter = CGPointMake(CGRectGetMaxX(self.pegView.frame) - CGRectGetWidth(self.pegView.frame) / 2,
|
||||
CGRectGetMaxY(self.pegView.frame) - CGRectGetHeight(self.pegView.frame) / 2);
|
||||
|
||||
if (CGRectContainsPoint(detectionFrame, pegCenter)) {
|
||||
if (self.isRotated) {
|
||||
double rotation = atan2(self.pegView.transform.b, self.pegView.transform.a);
|
||||
double angle = fmod(fabs(rotation), M_PI_2);
|
||||
if (angle > M_PI_4 - degreesToRadians(ORKOrientationThreshold) &&
|
||||
angle < M_PI_4 + degreesToRadians(ORKOrientationThreshold)) {
|
||||
return YES;
|
||||
}
|
||||
} else {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
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 ORKHolePegTestPlaceHoleView : UIView
|
||||
|
||||
@property (nonatomic, assign, getter = isRotated) BOOL rotated;
|
||||
@property (nonatomic, assign, getter = isSuccess) BOOL success;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
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 "ORKHolePegTestPlaceHoleView.h"
|
||||
|
||||
|
||||
static const CGFloat ORKPlaceHoleViewRotation = 45.0f;
|
||||
|
||||
|
||||
@interface ORKHolePegTestPlaceHoleView ()
|
||||
|
||||
@property (nonatomic, strong) CAShapeLayer *checkLayer;
|
||||
@property (nonatomic, strong) CAShapeLayer *crossLayer;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKHolePegTestPlaceHoleView
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
UIBezierPath *path = [[UIBezierPath alloc] init];
|
||||
[path moveToPoint:CGPointMake(27.7f, 46.9f)];
|
||||
[path addLineToPoint:CGPointMake(36.1f, 56.3f)];
|
||||
[path addLineToPoint:CGPointMake(62.8f, 30.3f)];
|
||||
path.lineCapStyle = kCGLineCapRound;
|
||||
path.lineWidth = 3.6f;
|
||||
|
||||
CAShapeLayer *checkLayer = [CAShapeLayer new];
|
||||
checkLayer.path = path.CGPath;
|
||||
checkLayer.lineWidth = 3.6f;
|
||||
checkLayer.lineCap = kCALineCapRound;
|
||||
checkLayer.lineJoin = kCALineJoinRound;
|
||||
checkLayer.frame = self.layer.bounds;
|
||||
checkLayer.strokeColor = self.tintColor.CGColor;
|
||||
checkLayer.backgroundColor = [UIColor clearColor].CGColor;
|
||||
checkLayer.fillColor = nil;
|
||||
self.checkLayer = checkLayer;
|
||||
|
||||
self.opaque = NO;
|
||||
self.success = NO;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CGSize)intrinsicContentSize {
|
||||
return CGSizeMake(self.frame.size.width, self.frame.size.height);
|
||||
}
|
||||
|
||||
#pragma mark - drawing method
|
||||
|
||||
- (void)tintColorDidChange {
|
||||
[super tintColorDidChange];
|
||||
self.checkLayer.strokeColor = self.tintColor.CGColor;
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)setSuccess:(BOOL)success
|
||||
{
|
||||
_success = success;
|
||||
[self.checkLayer removeFromSuperlayer];
|
||||
[self.crossLayer removeFromSuperlayer];
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)setRotated:(BOOL)rotated
|
||||
{
|
||||
_rotated = rotated;
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)rect {
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
CGContextSaveGState(context);
|
||||
|
||||
CGRect bounds = self.bounds;
|
||||
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectInset(bounds, 1.0f, 1.0f)];
|
||||
path.lineWidth = 2.0f;
|
||||
[self.tintColor setStroke];
|
||||
[path stroke];
|
||||
|
||||
if (self.isSuccess) {
|
||||
[self.layer addSublayer:self.checkLayer];
|
||||
|
||||
CAMediaTimingFunction *timing = [[CAMediaTimingFunction alloc] initWithControlPoints:0.180739998817444
|
||||
:0
|
||||
:0.577960014343262
|
||||
:0.918200016021729];
|
||||
|
||||
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
|
||||
[animation setTimingFunction:timing];
|
||||
[animation setFillMode:kCAFillModeBoth];
|
||||
animation.fromValue = @(0);
|
||||
animation.toValue = @(1);
|
||||
animation.duration = 0.3f;
|
||||
animation.delegate = self;
|
||||
[self.checkLayer addAnimation:animation forKey:@"strokeEnd"];
|
||||
} else if (self.isRotated) {
|
||||
UIBezierPath *crossPath = [[UIBezierPath alloc] init];
|
||||
[crossPath moveToPoint:CGPointMake(CGRectGetWidth(bounds) * 7/16, CGRectGetHeight(bounds) * 1/4)];
|
||||
[crossPath addLineToPoint:CGPointMake(CGRectGetWidth(bounds) * 7/16, CGRectGetHeight(bounds) * 7/16)];
|
||||
[crossPath addLineToPoint:CGPointMake(CGRectGetWidth(bounds) * 1/4, CGRectGetHeight(bounds) * 7/16)];
|
||||
[crossPath addLineToPoint:CGPointMake(CGRectGetWidth(bounds) * 1/4, CGRectGetHeight(bounds) * 9/16)];
|
||||
[crossPath addLineToPoint:CGPointMake(CGRectGetWidth(bounds) * 7/16, CGRectGetHeight(bounds) * 9/16)];
|
||||
[crossPath addLineToPoint:CGPointMake(CGRectGetWidth(bounds) * 7/16, CGRectGetHeight(bounds) * 3/4)];
|
||||
[crossPath addLineToPoint:CGPointMake(CGRectGetWidth(bounds) * 9/16, CGRectGetHeight(bounds) * 3/4)];
|
||||
[crossPath addLineToPoint:CGPointMake(CGRectGetWidth(bounds) * 9/16, CGRectGetHeight(bounds) * 9/16)];
|
||||
[crossPath addLineToPoint:CGPointMake(CGRectGetWidth(bounds) * 3/4, CGRectGetHeight(bounds) * 9/16)];
|
||||
[crossPath addLineToPoint:CGPointMake(CGRectGetWidth(bounds) * 3/4, CGRectGetHeight(bounds) * 7/16)];
|
||||
[crossPath addLineToPoint:CGPointMake(CGRectGetWidth(bounds) * 9/16, CGRectGetHeight(bounds) * 7/16)];
|
||||
[crossPath addLineToPoint:CGPointMake(CGRectGetWidth(bounds) * 9/16, CGRectGetHeight(bounds) * 1/4)];
|
||||
[crossPath closePath];
|
||||
|
||||
CAShapeLayer *crossLayer = [[CAShapeLayer alloc] init];
|
||||
crossLayer.path = crossPath.CGPath;
|
||||
crossLayer.bounds = CGPathGetBoundingBox(crossLayer.path);
|
||||
crossLayer.anchorPoint = CGPointMake(0.5, 0.5);
|
||||
crossLayer.fillColor = self.tintColor.CGColor;
|
||||
|
||||
CATransform3D transform = CATransform3DMakeTranslation(CGRectGetMidX(bounds), CGRectGetMidY(bounds), 1);
|
||||
transform = CATransform3DRotate(transform, ORKPlaceHoleViewRotation * (M_PI / 180), 0, 0, 1);
|
||||
crossLayer.transform = transform;
|
||||
|
||||
self.crossLayer = crossLayer;
|
||||
|
||||
[self.layer addSublayer:self.crossLayer];
|
||||
}
|
||||
|
||||
CGContextRestoreGState(context);
|
||||
}
|
||||
|
||||
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag {
|
||||
__weak typeof(self) weakSelf = self;
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.7f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
typeof(self) strongSelf = weakSelf;
|
||||
strongSelf.success = NO;
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
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 ORKHolePegTestPlacePegView : UIView
|
||||
|
||||
@property (nonatomic, assign, getter = isRotated) BOOL rotated;
|
||||
|
||||
@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 "ORKHolePegTestPlacePegView.h"
|
||||
|
||||
|
||||
@implementation ORKHolePegTestPlacePegView
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
self.opaque = NO;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CGSize)intrinsicContentSize {
|
||||
return CGSizeMake(self.frame.size.width, self.frame.size.height);
|
||||
}
|
||||
|
||||
#pragma mark - drawing method
|
||||
|
||||
- (void)tintColorDidChange {
|
||||
[super tintColorDidChange];
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)setRotated:(BOOL)rotated
|
||||
{
|
||||
_rotated = rotated;
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)rect {
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
CGContextSaveGState(context);
|
||||
|
||||
CGRect bounds = self.bounds;
|
||||
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:bounds];
|
||||
|
||||
if (self.isRotated) {
|
||||
[path moveToPoint:CGPointMake(CGRectGetWidth(bounds) * 7/16, CGRectGetHeight(bounds) * 1/4)];
|
||||
[path addLineToPoint:CGPointMake(CGRectGetWidth(bounds) * 7/16, CGRectGetHeight(bounds) * 7/16)];
|
||||
[path addLineToPoint:CGPointMake(CGRectGetWidth(bounds) * 1/4, CGRectGetHeight(bounds) * 7/16)];
|
||||
[path addLineToPoint:CGPointMake(CGRectGetWidth(bounds) * 1/4, CGRectGetHeight(bounds) * 9/16)];
|
||||
[path addLineToPoint:CGPointMake(CGRectGetWidth(bounds) * 7/16, CGRectGetHeight(bounds) * 9/16)];
|
||||
[path addLineToPoint:CGPointMake(CGRectGetWidth(bounds) * 7/16, CGRectGetHeight(bounds) * 3/4)];
|
||||
[path addLineToPoint:CGPointMake(CGRectGetWidth(bounds) * 9/16, CGRectGetHeight(bounds) * 3/4)];
|
||||
[path addLineToPoint:CGPointMake(CGRectGetWidth(bounds) * 9/16, CGRectGetHeight(bounds) * 9/16)];
|
||||
[path addLineToPoint:CGPointMake(CGRectGetWidth(bounds) * 3/4, CGRectGetHeight(bounds) * 9/16)];
|
||||
[path addLineToPoint:CGPointMake(CGRectGetWidth(bounds) * 3/4, CGRectGetHeight(bounds) * 7/16)];
|
||||
[path addLineToPoint:CGPointMake(CGRectGetWidth(bounds) * 9/16, CGRectGetHeight(bounds) * 7/16)];
|
||||
[path addLineToPoint:CGPointMake(CGRectGetWidth(bounds) * 9/16, CGRectGetHeight(bounds) * 1/4)];
|
||||
[path closePath];
|
||||
}
|
||||
|
||||
[self.tintColor setFill];
|
||||
[path fill];
|
||||
|
||||
CGContextRestoreGState(context);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
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 ORKHolePegTestPlaceStep : ORKActiveStep
|
||||
|
||||
@property (nonatomic, assign) ORKBodySagittal movingDirection;
|
||||
@property (nonatomic, assign, getter = isDominantHandTested) BOOL dominantHandTested;
|
||||
@property (nonatomic, assign) int numberOfPegs;
|
||||
@property (nonatomic, assign) double threshold;
|
||||
@property (nonatomic, assign, getter = isRotated) BOOL rotated;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
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 "ORKHolePegTestPlaceStep.h"
|
||||
#import "ORKHolePegTestPlaceStepViewController.h"
|
||||
|
||||
|
||||
@implementation ORKHolePegTestPlaceStep
|
||||
|
||||
+ (Class)stepViewControllerClass {
|
||||
return [ORKHolePegTestPlaceStepViewController class];
|
||||
}
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
||||
self = [super initWithIdentifier:identifier];
|
||||
if (self) {
|
||||
self.shouldShowDefaultTimer = NO;
|
||||
self.shouldContinueOnFinish = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)validateParameters {
|
||||
[super validateParameters];
|
||||
|
||||
int const ORKHolePegTestMinimumNumberOfPegs = 1;
|
||||
|
||||
double const ORKHolePegTestMinimumThreshold = 0.0f;
|
||||
double const ORKHolePegTestMaximumThreshold = 1.0f;
|
||||
|
||||
NSTimeInterval const ORKHolePegTestMinimumDuration = 1.0f;
|
||||
|
||||
if (self.movingDirection != ORKBodySagittalLeft &&
|
||||
self.movingDirection != ORKBodySagittalRight) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"moving direction should be left or right."] userInfo:nil];
|
||||
}
|
||||
|
||||
if (self.numberOfPegs < ORKHolePegTestMinimumNumberOfPegs) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"number of pegs must be greater than or equal to %@.", @(ORKHolePegTestMinimumNumberOfPegs)] userInfo:nil];
|
||||
}
|
||||
|
||||
if (self.threshold < ORKHolePegTestMinimumThreshold ||
|
||||
self.threshold > ORKHolePegTestMaximumThreshold) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"threshold must be greater than or equal to %@ and lower or equal to %@.", @(ORKHolePegTestMinimumThreshold), @(ORKHolePegTestMaximumThreshold)] userInfo:nil];
|
||||
}
|
||||
|
||||
if (self.stepDuration < ORKHolePegTestMinimumDuration) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"duration can not be shorter than %@ seconds.", @(ORKHolePegTestMinimumDuration)] userInfo:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)allowsBackNavigation {
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
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 ORKHolePegTestPlaceStepViewController : ORKActiveStepViewController
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
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 "ORKHolePegTestPlaceStepViewController.h"
|
||||
#import "ORKHolePegTestPlaceStep.h"
|
||||
#import "ORKHolePegTestPlaceContentView.h"
|
||||
#import "ORKActiveStepViewController_internal.h"
|
||||
#import "ORKStepViewController_internal.h"
|
||||
#import "ORKActiveStepView.h"
|
||||
|
||||
|
||||
@interface ORKHolePegTestPlaceStepViewController () <ORKHolePegTestPlaceContentViewDelegate>
|
||||
|
||||
@property (nonatomic, strong) NSMutableArray *samples;
|
||||
@property (nonatomic, strong) ORKHolePegTestPlaceContentView *holePegTestPlaceContentView;
|
||||
@property (nonatomic, assign) NSTimeInterval sampleStart;
|
||||
@property (nonatomic, assign) NSUInteger successes;
|
||||
@property (nonatomic, assign) NSUInteger failures;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKHolePegTestPlaceStepViewController
|
||||
|
||||
- (instancetype)initWithStep:(ORKStep *)step {
|
||||
self = [super initWithStep:step];
|
||||
if (self) {
|
||||
self.suspendIfInactive = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (ORKHolePegTestPlaceStep *)holePegTestPlaceStep {
|
||||
return (ORKHolePegTestPlaceStep *)self.step;
|
||||
}
|
||||
|
||||
- (void)initializeInternalButtonItems {
|
||||
[super initializeInternalButtonItems];
|
||||
|
||||
// Don't show next button
|
||||
self.internalContinueButtonItem = nil;
|
||||
self.internalDoneButtonItem = nil;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
self.holePegTestPlaceContentView = [[ORKHolePegTestPlaceContentView alloc] initWithMovingDirection:[self holePegTestPlaceStep].movingDirection
|
||||
rotated:[self holePegTestPlaceStep].rotated];
|
||||
self.holePegTestPlaceContentView.threshold = [self holePegTestPlaceStep].threshold;
|
||||
self.holePegTestPlaceContentView.delegate = self;
|
||||
self.activeStepView.activeCustomView = self.holePegTestPlaceContentView;
|
||||
self.activeStepView.stepViewFillsAvailableSpace = YES;
|
||||
}
|
||||
|
||||
#pragma mark - step life cycle methods
|
||||
|
||||
- (void)start {
|
||||
self.successes = 0;
|
||||
self.failures = 0;
|
||||
self.samples = [NSMutableArray array];
|
||||
[self.holePegTestPlaceContentView setProgress:0.001f animated:NO];
|
||||
|
||||
[super start];
|
||||
}
|
||||
|
||||
#pragma mark - result methods
|
||||
|
||||
- (ORKStepResult *)result {
|
||||
ORKStepResult *sResult = [super result];
|
||||
|
||||
NSMutableArray *results = [NSMutableArray arrayWithArray:sResult.results];
|
||||
|
||||
ORKHolePegTestResult *holePegTestResult = [[ORKHolePegTestResult alloc] initWithIdentifier:self.step.identifier];
|
||||
holePegTestResult.movingDirection = [self holePegTestPlaceStep].movingDirection;
|
||||
holePegTestResult.dominantHandTested = [self holePegTestPlaceStep].isDominantHandTested;
|
||||
holePegTestResult.numberOfPegs = [self holePegTestPlaceStep].numberOfPegs;
|
||||
holePegTestResult.threshold = [self holePegTestPlaceStep].threshold;
|
||||
holePegTestResult.rotated = [self holePegTestPlaceStep].isRotated;
|
||||
holePegTestResult.totalSuccesses = self.successes;
|
||||
holePegTestResult.totalFailures = self.failures;
|
||||
holePegTestResult.totalTime = [self holePegTestPlaceStep].stepDuration - self.timeRemaining;
|
||||
double totalDistance = 0.0;
|
||||
for (ORKHolePegTestSample *sample in self.samples) {
|
||||
totalDistance += sample.distance;
|
||||
}
|
||||
holePegTestResult.totalDistance = totalDistance;
|
||||
holePegTestResult.samples = self.samples;
|
||||
|
||||
[results addObject:holePegTestResult];
|
||||
|
||||
sResult.results = [results copy];
|
||||
|
||||
return sResult;
|
||||
}
|
||||
|
||||
- (void)saveSampleWithDistance:(CGFloat)distance {
|
||||
ORKHolePegTestSample *sample = [[ORKHolePegTestSample alloc] init];
|
||||
sample.time = CACurrentMediaTime() - self.sampleStart;
|
||||
sample.distance = distance;
|
||||
self.sampleStart = CACurrentMediaTime();
|
||||
|
||||
[self.samples addObject:sample];
|
||||
}
|
||||
|
||||
#pragma mark - hole peg test content view delegate
|
||||
|
||||
- (NSString *)stepTitle {
|
||||
NSString *title = ([self holePegTestPlaceStep].movingDirection == ORKBodySagittalLeft) ? ORKLocalizedString(@"HOLE_PEG_TEST_PLACE_INSTRUCTION_LEFT_HAND", nil) : ORKLocalizedString(@"HOLE_PEG_TEST_PLACE_INSTRUCTION_RIGHT_HAND", nil);
|
||||
return title;
|
||||
}
|
||||
|
||||
- (void)holePegTestPlaceDidProgress:(ORKHolePegTestPlaceContentView *)holePegTestPlaceContentView {
|
||||
if (!self.isStarted) {
|
||||
self.sampleStart = CACurrentMediaTime();
|
||||
[self start];
|
||||
}
|
||||
|
||||
[self.activeStepView updateTitle:[self stepTitle]
|
||||
text:ORKLocalizedString(@"HOLE_PEG_TEST_TEXT_2", nil)];
|
||||
}
|
||||
|
||||
- (void)holePegTestPlaceDidSucceed:(ORKHolePegTestPlaceContentView *)holePegTestPlaceContentView withDistance:(CGFloat)distance {
|
||||
self.successes++;
|
||||
|
||||
[self saveSampleWithDistance:distance];
|
||||
|
||||
[holePegTestPlaceContentView setProgress:((CGFloat)self.successes / [self holePegTestPlaceStep].numberOfPegs) animated:YES];
|
||||
[self.activeStepView updateTitle:[self stepTitle]
|
||||
text:ORKLocalizedString(@"HOLE_PEG_TEST_TEXT", nil)];
|
||||
|
||||
if (self.successes >= [self holePegTestPlaceStep].numberOfPegs) {
|
||||
[((ORKNavigableOrderedTask *)self.taskViewController.task) removeNavigationRuleForTriggerStepIdentifier:[self holePegTestPlaceStep].identifier];
|
||||
[self finish];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)holePegTestPlaceDidFail:(ORKHolePegTestPlaceContentView *)holePegTestPlaceContentView {
|
||||
self.failures++;
|
||||
|
||||
[self.activeStepView updateTitle:[self stepTitle]
|
||||
text:ORKLocalizedString(@"HOLE_PEG_TEST_TEXT", nil)];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
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 "ORKCustomStepView_Internal.h"
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@protocol ORKHolePegTestRemoveContentViewDelegate;
|
||||
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKHolePegTestRemoveContentView : ORKActiveStepCustomView
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE;
|
||||
- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
|
||||
- (instancetype)initWithMovingDirection:(ORKBodySagittal)movingDirection NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@property (nonatomic, assign) ORKBodySagittal movingDirection;
|
||||
@property (nonatomic, assign) double threshold;
|
||||
@property (nonatomic, weak) id<ORKHolePegTestRemoveContentViewDelegate> delegate;
|
||||
|
||||
- (void)setProgress:(CGFloat)progress animated:(BOOL)animated;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@protocol ORKHolePegTestRemoveContentViewDelegate <NSObject>
|
||||
|
||||
- (void)holePegTestRemoveDidProgress:(ORKHolePegTestRemoveContentView *)holePegTestRemoveContentView;
|
||||
- (void)holePegTestRemoveDidSucceed:(ORKHolePegTestRemoveContentView *)holePegTestRemoveContentView withDistance:(CGFloat)distance;
|
||||
- (void)holePegTestRemoveDidFail:(ORKHolePegTestRemoveContentView *)holePegTestRemoveContentView;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,356 @@
|
||||
/*
|
||||
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 "ORKHolePegTestRemoveContentView.h"
|
||||
#import "ORKHolePegTestRemovePegView.h"
|
||||
#import "ORKSeparatorView.h"
|
||||
#import "ORKDirectionView.h"
|
||||
#import "ORKHelpers.h"
|
||||
#import "ORKSkin.h"
|
||||
|
||||
|
||||
static const CGFloat PegViewDiameter = 88.0f;
|
||||
static const CGFloat PegViewSeparatorWidth = 2.0f;
|
||||
|
||||
|
||||
@interface ORKHolePegTestRemoveContentView () <UIGestureRecognizerDelegate>
|
||||
|
||||
@property (nonatomic, strong) UIProgressView *progressView;
|
||||
@property (nonatomic, strong) ORKHolePegTestRemovePegView *pegView;
|
||||
@property (nonatomic, strong) ORKSeparatorView *separatorView;
|
||||
@property (nonatomic, strong) ORKDirectionView *directionView;
|
||||
@property (nonatomic, strong) UIView *container;
|
||||
@property (nonatomic, copy) NSArray *constraints;
|
||||
|
||||
@property (nonatomic, strong) UIPinchGestureRecognizer *pinchRecognizer;
|
||||
@property (nonatomic, strong) UIPanGestureRecognizer *panRecognizer;
|
||||
@property (nonatomic, assign, getter = isMovable) BOOL movable;
|
||||
@property (nonatomic, assign, getter = hasMoveEnded) BOOL moveEnded;
|
||||
@property (nonatomic, assign) CGPoint translation;
|
||||
@property (nonatomic, assign) CGPoint translationOffset;
|
||||
@property (nonatomic, assign) CGPoint startPoint;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKHolePegTestRemoveContentView
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
ORKThrowMethodUnavailableException();
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
ORKThrowMethodUnavailableException();
|
||||
}
|
||||
|
||||
- (instancetype)initWithMovingDirection:(ORKBodySagittal)movingDirection {
|
||||
self = [super initWithFrame:CGRectZero];
|
||||
if (self) {
|
||||
self.movingDirection = movingDirection;
|
||||
self.opaque = NO;
|
||||
|
||||
self.container = [UIView new];
|
||||
self.container.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
|
||||
self.progressView = [UIProgressView new];
|
||||
self.progressView.progressTintColor = self.tintColor;
|
||||
[self.progressView setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
[self.progressView setAlpha:0];
|
||||
[self addSubview:self.progressView];
|
||||
|
||||
self.pegView = [[ORKHolePegTestRemovePegView alloc] initWithFrame:CGRectMake(0, 0, PegViewDiameter, PegViewDiameter)];
|
||||
[self.pegView setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
[self.container addSubview:self.pegView];
|
||||
|
||||
self.separatorView = [[ORKSeparatorView alloc] init];
|
||||
[self.separatorView setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
[self.container addSubview:self.separatorView];
|
||||
|
||||
self.directionView = [[ORKDirectionView alloc] initWithOrientation:(self.movingDirection == ORKBodySagittalLeft) ? ORKBodySagittalRight : ORKBodySagittalLeft];
|
||||
[self.directionView setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
[self addSubview:self.directionView];
|
||||
|
||||
[self addSubview:self.container];
|
||||
|
||||
[self setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
[self setNeedsUpdateConstraints];
|
||||
|
||||
self.movable = NO;
|
||||
self.moveEnded = NO;
|
||||
self.startPoint = CGPointZero;
|
||||
|
||||
self.pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self
|
||||
action:@selector(handlePinch:)];
|
||||
self.pinchRecognizer.delegate = self;
|
||||
[self addGestureRecognizer:self.pinchRecognizer];
|
||||
|
||||
self.panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self
|
||||
action:@selector(handlePan:)];
|
||||
self.panRecognizer.delegate = self;
|
||||
[self addGestureRecognizer:self.panRecognizer];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)tintColorDidChange {
|
||||
[super tintColorDidChange];
|
||||
self.progressView.progressTintColor = self.tintColor;
|
||||
}
|
||||
|
||||
- (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)updateLayoutMargins {
|
||||
CGFloat margin = ORKStandardHorizontalMarginForView(self);
|
||||
self.layoutMargins = (UIEdgeInsets){.left = margin * 2, .right = margin * 2};
|
||||
}
|
||||
|
||||
- (void)setFrame:(CGRect)frame {
|
||||
[super setFrame:frame];
|
||||
[self updateLayoutMargins];
|
||||
}
|
||||
|
||||
- (void)setBounds:(CGRect)bounds {
|
||||
[super setBounds:bounds];
|
||||
[self updateLayoutMargins];
|
||||
}
|
||||
|
||||
- (void)updateConstraints {
|
||||
if ([self.constraints count]) {
|
||||
[NSLayoutConstraint deactivateConstraints:self.constraints];
|
||||
self.constraints = nil;
|
||||
}
|
||||
|
||||
NSMutableArray *constraintsArray = [NSMutableArray array];
|
||||
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(_progressView, _container, _pegView, _separatorView, _directionView);
|
||||
NSDictionary *metrics = @{@"diameter" : @(PegViewDiameter), @"separator" : @(PegViewSeparatorWidth), @"margin" : @((1 + self.threshold) * PegViewDiameter)};
|
||||
|
||||
[constraintsArray addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_progressView]-|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil views:views]];
|
||||
|
||||
[constraintsArray addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:(self.movingDirection == ORKBodySagittalLeft) ? @"H:|-[_pegView(diameter)]->=0-[_separatorView(separator)]-(margin)-|" : @"H:|-(margin)-[_separatorView(separator)]->=0-[_pegView(diameter)]-|"
|
||||
options:NSLayoutFormatAlignAllCenterY
|
||||
metrics:metrics views:views]];
|
||||
|
||||
[constraintsArray addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_container]|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil views:views]];
|
||||
|
||||
[constraintsArray addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(>=0)-[_pegView(diameter)]-(>=0)-|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:metrics views:views]];
|
||||
|
||||
[constraintsArray addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[_separatorView]-|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil views:views]];
|
||||
|
||||
[constraintsArray addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_progressView][_container]|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:metrics views:views]];
|
||||
|
||||
[constraintsArray addObject:[NSLayoutConstraint constraintWithItem:self.directionView
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
multiplier:1
|
||||
constant:0]];
|
||||
|
||||
[constraintsArray addObject:[NSLayoutConstraint constraintWithItem:self.directionView
|
||||
attribute:NSLayoutAttributeCenterY
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeCenterY
|
||||
multiplier:1
|
||||
constant:0]];
|
||||
|
||||
self.constraints = constraintsArray;
|
||||
[self addConstraints:self.constraints];
|
||||
|
||||
[NSLayoutConstraint activateConstraints:self.constraints];
|
||||
[super updateConstraints];
|
||||
}
|
||||
|
||||
#pragma mark - gesture recognizer methods
|
||||
|
||||
- (void)pickupPegWithGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer {
|
||||
CGPoint touch = [gestureRecognizer locationInView:self];
|
||||
CGPoint touch1 = [gestureRecognizer locationOfTouch:0 inView:self];
|
||||
CGPoint touch2 = [gestureRecognizer locationOfTouch:1 inView:self];
|
||||
double distance = hypot(touch1.x - touch2.x, touch1.y - touch2.y);
|
||||
|
||||
if (distance < 3 * CGRectGetWidth(self.pegView.frame) &&
|
||||
CGRectContainsPoint(self.pegView.frame, touch)) {
|
||||
self.movable = YES;
|
||||
} else {
|
||||
self.movable = NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handlePinch:(UIPinchGestureRecognizer *)pinchGestureRecognizer {
|
||||
if ([pinchGestureRecognizer numberOfTouches] == 2) {
|
||||
[self pickupPegWithGestureRecognizer:pinchGestureRecognizer];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handlePan:(UIPanGestureRecognizer *)panGestureRecognizer {
|
||||
if ([panGestureRecognizer numberOfTouches] != 2 ||
|
||||
panGestureRecognizer.state == UIGestureRecognizerStateEnded ||
|
||||
panGestureRecognizer.state == UIGestureRecognizerStateCancelled ||
|
||||
panGestureRecognizer.state == UIGestureRecognizerStateFailed) {
|
||||
[self resetTransformAtPoint:[panGestureRecognizer locationInView:self]];
|
||||
} else {
|
||||
if (self.isMovable) {
|
||||
self.translation = CGPointMake([panGestureRecognizer translationInView:self].x - self.translationOffset.x,
|
||||
[panGestureRecognizer translationInView:self].y - self.translationOffset.y);
|
||||
[self updateTransformAtPoint:[panGestureRecognizer locationInView:self]];
|
||||
} else {
|
||||
self.translationOffset = CGPointMake([panGestureRecognizer translationInView:self].x - self.translation.x,
|
||||
[panGestureRecognizer translationInView:self].y - self.translation.y);
|
||||
if (CGPointEqualToPoint(self.startPoint, CGPointZero)) {
|
||||
[self pickupPegWithGestureRecognizer:panGestureRecognizer];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateTransformAtPoint:(CGPoint)point {
|
||||
self.pegView.transform = CGAffineTransformMakeTranslation(self.translation.x, self.translation.y);
|
||||
[self pegViewDidMoveAtPoint:point];
|
||||
}
|
||||
|
||||
- (void)resetTransformAtPoint:(CGPoint)point {
|
||||
if (!self.hasMoveEnded) {
|
||||
self.movable = NO;
|
||||
self.moveEnded = YES;
|
||||
|
||||
self.pinchRecognizer.enabled = NO;
|
||||
self.panRecognizer.enabled = NO;
|
||||
|
||||
BOOL animated = ![self pegViewMoveDidEndAtPoint:point];
|
||||
|
||||
[UIView animateWithDuration:animated ? 0.15f : 0.0f
|
||||
delay:animated ? 0.0f : 0.30f
|
||||
options:UIViewAnimationOptionCurveEaseOut
|
||||
animations:^(){
|
||||
self.pegView.transform = CGAffineTransformIdentity;
|
||||
self.pegView.alpha = 1.0f;
|
||||
}
|
||||
completion:^(BOOL finished){
|
||||
self.translation = CGPointZero;
|
||||
self.translationOffset = CGPointZero;
|
||||
self.pinchRecognizer.enabled = YES;
|
||||
self.panRecognizer.enabled = YES;
|
||||
self.moveEnded = NO;
|
||||
self.pegView.hidden = NO;
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
|
||||
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - peg view delegate
|
||||
|
||||
- (void)pegViewDidMoveAtPoint:(CGPoint)point {
|
||||
self.directionView.hidden = YES;
|
||||
|
||||
if (CGPointEqualToPoint(self.startPoint, CGPointZero)) {
|
||||
self.startPoint = point;
|
||||
}
|
||||
|
||||
if ([self.delegate respondsToSelector:@selector(holePegTestRemoveDidProgress:)]) {
|
||||
[self.delegate holePegTestRemoveDidProgress:self];
|
||||
}
|
||||
|
||||
if (self.pegView.isSuccess) {
|
||||
self.pegView.success = NO;
|
||||
}
|
||||
|
||||
if ([self pegViewBehindLine]) {
|
||||
self.pegView.alpha = 1.0f;
|
||||
} else {
|
||||
self.pegView.alpha = 0.2f;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)pegViewMoveDidEndAtPoint:(CGPoint)point {
|
||||
self.directionView.hidden = NO;
|
||||
|
||||
BOOL succeeded = NO;
|
||||
if ([self pegViewBehindLine]) {
|
||||
if ([self.delegate respondsToSelector:@selector(holePegTestRemoveDidSucceed:withDistance:)]) {
|
||||
CGFloat distance = hypotf(point.x - self.startPoint.x, point.y - self.startPoint.y);
|
||||
[self.delegate holePegTestRemoveDidSucceed:self withDistance:distance];
|
||||
}
|
||||
self.pegView.success = YES;
|
||||
succeeded = YES;
|
||||
} else {
|
||||
if ([self.delegate respondsToSelector:@selector(holePegTestRemoveDidFail:)]) {
|
||||
[self.delegate holePegTestRemoveDidFail:self];
|
||||
}
|
||||
self.pegView.success = NO;
|
||||
}
|
||||
self.startPoint = CGPointZero;
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
- (BOOL)pegViewBehindLine {
|
||||
if (self.movingDirection == ORKBodySagittalLeft) {
|
||||
if (CGRectGetMinX(self.pegView.frame) > CGRectGetMaxX(self.separatorView.frame)) {
|
||||
return YES;
|
||||
} else {
|
||||
return NO;
|
||||
}
|
||||
} else {
|
||||
if (CGRectGetMaxX(self.pegView.frame) < CGRectGetMinX(self.separatorView.frame)) {
|
||||
return YES;
|
||||
} else {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
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 ORKHolePegTestRemovePegView : UIView
|
||||
|
||||
@property (nonatomic, assign, getter = isSuccess) BOOL success;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
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 "ORKHolePegTestRemovePegView.h"
|
||||
|
||||
|
||||
@interface ORKHolePegTestRemovePegView ()
|
||||
|
||||
@property (nonatomic, strong) CAShapeLayer *checkLayer;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKHolePegTestRemovePegView
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
UIBezierPath *path = [[UIBezierPath alloc] init];
|
||||
[path moveToPoint:CGPointMake(27.7f, 46.9f)];
|
||||
[path addLineToPoint:CGPointMake(36.1f, 56.3f)];
|
||||
[path addLineToPoint:CGPointMake(62.8f, 30.3f)];
|
||||
path.lineCapStyle = kCGLineCapRound;
|
||||
path.lineWidth = 3.6f;
|
||||
|
||||
CAShapeLayer *checkLayer = [CAShapeLayer new];
|
||||
checkLayer.path = path.CGPath;
|
||||
checkLayer.lineWidth = 3.6f;
|
||||
checkLayer.lineCap = kCALineCapRound;
|
||||
checkLayer.lineJoin = kCALineJoinRound;
|
||||
checkLayer.frame = self.layer.bounds;
|
||||
checkLayer.strokeColor = [UIColor whiteColor].CGColor;
|
||||
checkLayer.backgroundColor = [UIColor clearColor].CGColor;
|
||||
checkLayer.fillColor = nil;
|
||||
self.checkLayer = checkLayer;
|
||||
|
||||
self.opaque = NO;
|
||||
self.success = NO;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)tintColorDidChange {
|
||||
[super tintColorDidChange];
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
#pragma mark - drawing method
|
||||
|
||||
- (void)setSuccess:(BOOL)success
|
||||
{
|
||||
_success = success;
|
||||
[self.checkLayer removeFromSuperlayer];
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)rect {
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
CGContextSaveGState(context);
|
||||
|
||||
CGRect bounds = self.bounds;
|
||||
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:bounds];
|
||||
path.lineWidth = 2.0f;
|
||||
[self.tintColor setFill];
|
||||
[path fill];
|
||||
|
||||
if (self.isSuccess) {
|
||||
[self.layer addSublayer:self.checkLayer];
|
||||
|
||||
CAMediaTimingFunction *timing = [[CAMediaTimingFunction alloc] initWithControlPoints:0.180739998817444
|
||||
:0
|
||||
:0.577960014343262
|
||||
:0.918200016021729];
|
||||
|
||||
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
|
||||
[animation setTimingFunction:timing];
|
||||
[animation setFillMode:kCAFillModeBoth];
|
||||
animation.fromValue = @(0);
|
||||
animation.toValue = @(1);
|
||||
animation.duration = 0.25f;
|
||||
animation.delegate = self;
|
||||
[self.checkLayer addAnimation:animation forKey:@"strokeEnd"];
|
||||
}
|
||||
|
||||
CGContextRestoreGState(context);
|
||||
}
|
||||
|
||||
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag {
|
||||
self.success = NO;
|
||||
}
|
||||
|
||||
@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 ORKHolePegTestRemoveStep : ORKActiveStep
|
||||
|
||||
@property (nonatomic, assign) ORKBodySagittal movingDirection;
|
||||
@property (nonatomic, assign, getter = isDominantHandTested) BOOL dominantHandTested;
|
||||
@property (nonatomic, assign) int numberOfPegs;
|
||||
@property (nonatomic, assign) double threshold;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
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 "ORKHolePegTestRemoveStep.h"
|
||||
#import "ORKHolePegTestRemoveStepViewController.h"
|
||||
|
||||
|
||||
@implementation ORKHolePegTestRemoveStep
|
||||
|
||||
+ (Class)stepViewControllerClass {
|
||||
return [ORKHolePegTestRemoveStepViewController class];
|
||||
}
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
||||
self = [super initWithIdentifier:identifier];
|
||||
if (self) {
|
||||
self.shouldShowDefaultTimer = NO;
|
||||
self.shouldContinueOnFinish = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)validateParameters {
|
||||
[super validateParameters];
|
||||
|
||||
int const ORKHolePegTestMinimumNumberOfPegs = 1;
|
||||
|
||||
double const ORKHolePegTestMinimumThreshold = 0.0f;
|
||||
double const ORKHolePegTestMaximumThreshold = 1.0f;
|
||||
|
||||
NSTimeInterval const ORKHolePegTestMinimumDuration = 1.0f;
|
||||
|
||||
if (self.movingDirection != ORKBodySagittalLeft &&
|
||||
self.movingDirection != ORKBodySagittalRight) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"moving direction should be left or right."] userInfo:nil];
|
||||
}
|
||||
|
||||
if (self.numberOfPegs < ORKHolePegTestMinimumNumberOfPegs) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"number of pegs must be greater than or equal to %@.", @(ORKHolePegTestMinimumNumberOfPegs)] userInfo:nil];
|
||||
}
|
||||
|
||||
if (self.threshold < ORKHolePegTestMinimumThreshold ||
|
||||
self.threshold > ORKHolePegTestMaximumThreshold) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"threshold must be greater than or equal to %@ and lower or equal to %@.", @(ORKHolePegTestMinimumThreshold), @(ORKHolePegTestMaximumThreshold)] userInfo:nil];
|
||||
}
|
||||
|
||||
if (self.stepDuration < ORKHolePegTestMinimumDuration) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"duration can not be shorter than %@ seconds.", @(ORKHolePegTestMinimumDuration)] userInfo:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)allowsBackNavigation {
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
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 ORKHolePegTestRemoveStepViewController : ORKActiveStepViewController
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
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 "ORKHolePegTestRemoveStepViewController.h"
|
||||
#import "ORKHolePegTestRemoveStep.h"
|
||||
#import "ORKHolePegTestRemoveContentView.h"
|
||||
#import "ORKActiveStepViewController_internal.h"
|
||||
#import "ORKStepViewController_internal.h"
|
||||
#import "ORKActiveStepView.h"
|
||||
|
||||
|
||||
@interface ORKHolePegTestRemoveStepViewController () <ORKHolePegTestRemoveContentViewDelegate>
|
||||
|
||||
@property (nonatomic, strong) NSMutableArray *samples;
|
||||
@property (nonatomic, strong) ORKHolePegTestRemoveContentView *holePegTestRemoveContentView;
|
||||
@property (nonatomic, assign) NSTimeInterval sampleStart;
|
||||
@property (nonatomic, assign) NSUInteger successes;
|
||||
@property (nonatomic, assign) NSUInteger failures;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKHolePegTestRemoveStepViewController
|
||||
|
||||
- (instancetype)initWithStep:(ORKStep *)step {
|
||||
self = [super initWithStep:step];
|
||||
if (self) {
|
||||
self.suspendIfInactive = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (ORKHolePegTestRemoveStep *)holePegTestRemoveStep {
|
||||
return (ORKHolePegTestRemoveStep *)self.step;
|
||||
}
|
||||
|
||||
- (void)initializeInternalButtonItems {
|
||||
[super initializeInternalButtonItems];
|
||||
|
||||
// Don't show next button
|
||||
self.internalContinueButtonItem = nil;
|
||||
self.internalDoneButtonItem = nil;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
self.holePegTestRemoveContentView = [[ORKHolePegTestRemoveContentView alloc] initWithMovingDirection:[self holePegTestRemoveStep].movingDirection];
|
||||
self.holePegTestRemoveContentView.threshold = [self holePegTestRemoveStep].threshold;
|
||||
self.holePegTestRemoveContentView.delegate = self;
|
||||
self.activeStepView.activeCustomView = self.holePegTestRemoveContentView;
|
||||
self.activeStepView.stepViewFillsAvailableSpace = YES;
|
||||
|
||||
NSString *identifier = [[self holePegTestRemoveStep].identifier stringByReplacingOccurrencesOfString:@"remove" withString:@"place"];
|
||||
NSTimeInterval placeStepDuration = ((ORKHolePegTestResult *)[[self.taskViewController.result stepResultForStepIdentifier:identifier].results firstObject]).totalTime;
|
||||
[self holePegTestRemoveStep].stepDuration -= placeStepDuration;
|
||||
|
||||
[self start];
|
||||
}
|
||||
|
||||
#pragma mark - step life cycle methods
|
||||
|
||||
- (void)start {
|
||||
self.sampleStart = CACurrentMediaTime();
|
||||
self.successes = 0;
|
||||
self.failures = 0;
|
||||
self.samples = [NSMutableArray array];
|
||||
[self.holePegTestRemoveContentView setProgress:0.001f animated:NO];
|
||||
|
||||
[super start];
|
||||
}
|
||||
|
||||
#pragma mark - result methods
|
||||
|
||||
- (ORKStepResult *)result {
|
||||
ORKStepResult *sResult = [super result];
|
||||
|
||||
NSMutableArray *results = [NSMutableArray arrayWithArray:sResult.results];
|
||||
|
||||
ORKHolePegTestResult *holePegTestResult = [[ORKHolePegTestResult alloc] initWithIdentifier:self.step.identifier];
|
||||
holePegTestResult.movingDirection = [self holePegTestRemoveStep].movingDirection;
|
||||
holePegTestResult.dominantHandTested = [self holePegTestRemoveStep].isDominantHandTested;
|
||||
holePegTestResult.numberOfPegs = [self holePegTestRemoveStep].numberOfPegs;
|
||||
holePegTestResult.threshold = [self holePegTestRemoveStep].threshold;
|
||||
holePegTestResult.rotated = NO;
|
||||
holePegTestResult.totalSuccesses = self.successes;
|
||||
holePegTestResult.totalFailures = self.failures;
|
||||
holePegTestResult.totalTime = [self holePegTestRemoveStep].stepDuration - self.timeRemaining;
|
||||
double totalDistance = 0.0;
|
||||
for (ORKHolePegTestSample *sample in self.samples) {
|
||||
totalDistance += sample.distance;
|
||||
}
|
||||
holePegTestResult.totalDistance = totalDistance;
|
||||
holePegTestResult.samples = self.samples;
|
||||
|
||||
[results addObject:holePegTestResult];
|
||||
|
||||
sResult.results = [results copy];
|
||||
|
||||
return sResult;
|
||||
}
|
||||
|
||||
- (void)saveSampleWithDistance:(CGFloat)distance {
|
||||
ORKHolePegTestSample *sample = [[ORKHolePegTestSample alloc] init];
|
||||
sample.time = CACurrentMediaTime() - self.sampleStart;
|
||||
sample.distance = distance;
|
||||
self.sampleStart = CACurrentMediaTime();
|
||||
|
||||
[self.samples addObject:sample];
|
||||
}
|
||||
|
||||
#pragma mark - hole peg test content view delegate
|
||||
|
||||
- (NSString *)stepTitle {
|
||||
NSString *title = ([self holePegTestRemoveStep].movingDirection == ORKBodySagittalLeft) ? ORKLocalizedString(@"HOLE_PEG_TEST_REMOVE_INSTRUCTION_RIGHT_HAND", nil) : ORKLocalizedString(@"HOLE_PEG_TEST_REMOVE_INSTRUCTION_LEFT_HAND", nil);
|
||||
return title;
|
||||
}
|
||||
|
||||
- (void)holePegTestRemoveDidProgress:(ORKHolePegTestRemoveContentView *)holePegTestRemoveContentView {
|
||||
[self.activeStepView updateTitle:[self stepTitle]
|
||||
text:ORKLocalizedString(@"HOLE_PEG_TEST_TEXT_2", nil)];
|
||||
}
|
||||
|
||||
- (void)holePegTestRemoveDidSucceed:(ORKHolePegTestRemoveContentView *)holePegTestRemoveContentView withDistance:(CGFloat)distance {
|
||||
self.successes++;
|
||||
|
||||
[self saveSampleWithDistance:distance];
|
||||
|
||||
[holePegTestRemoveContentView setProgress:((CGFloat)self.successes / [self holePegTestRemoveStep].numberOfPegs) animated:YES];
|
||||
[self.activeStepView updateTitle:[self stepTitle]
|
||||
text:ORKLocalizedString(@"HOLE_PEG_TEST_TEXT", nil)];
|
||||
|
||||
if (self.successes >= [self holePegTestRemoveStep].numberOfPegs) {
|
||||
[self finish];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)holePegTestRemoveDidFail:(ORKHolePegTestRemoveContentView *)holePegTestRemoveContentView {
|
||||
self.failures++;
|
||||
|
||||
[self.activeStepView updateTitle:[self stepTitle]
|
||||
text:ORKLocalizedString(@"HOLE_PEG_TEST_TEXT", nil)];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -30,6 +30,7 @@
|
||||
|
||||
|
||||
#import <ResearchKit/ORKRecorder.h>
|
||||
#import <CoreLocation/CoreLocation.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
@@ -58,6 +59,12 @@ ORK_CLASS_AVAILABLE
|
||||
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
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
BOOL _started;
|
||||
}
|
||||
|
||||
@property (nonatomic, strong) CLLocationManager *locationManager;
|
||||
@property (nonatomic, strong, nullable) CLLocationManager *locationManager;
|
||||
|
||||
@property (nonatomic) NSTimeInterval uptime;
|
||||
|
||||
@@ -75,10 +75,10 @@
|
||||
- (void)start {
|
||||
[super start];
|
||||
|
||||
if (! _logger) {
|
||||
if (!_logger) {
|
||||
NSError *err = nil;
|
||||
_logger = [self makeJSONDataLoggerWithError:&err];
|
||||
if (! _logger) {
|
||||
if (!_logger) {
|
||||
[self finishRecordingWithError:err];
|
||||
return;
|
||||
}
|
||||
@@ -91,7 +91,7 @@
|
||||
self.locationManager.pausesLocationUpdatesAutomatically = NO;
|
||||
self.locationManager.delegate = self;
|
||||
|
||||
if (! self.locationManager) {
|
||||
if (!self.locationManager) {
|
||||
NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain
|
||||
code:NSFeatureUnsupportedError
|
||||
userInfo:@{@"recorder" : self}];
|
||||
@@ -128,10 +128,10 @@
|
||||
- (void)locationManager:(CLLocationManager *)manager
|
||||
didUpdateLocations:(NSArray *)locations {
|
||||
BOOL success = YES;
|
||||
NSParameterAssert([locations count] >= 0);
|
||||
NSParameterAssert(locations.count >= 0);
|
||||
NSError *error = nil;
|
||||
if (locations) {
|
||||
NSMutableArray *dictionaries = [NSMutableArray arrayWithCapacity:[locations count]];
|
||||
NSMutableArray *dictionaries = [NSMutableArray arrayWithCapacity:locations.count];
|
||||
[locations enumerateObjectsUsingBlock:^(CLLocation *obj, NSUInteger idx, BOOL *stop) {
|
||||
NSDictionary *d = [obj ork_JSONDictionary];
|
||||
[dictionaries addObject:d];
|
||||
@@ -169,11 +169,6 @@
|
||||
@end
|
||||
|
||||
|
||||
@interface ORKLocationRecorderConfiguration ()
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKLocationRecorderConfiguration
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
||||
|
||||
@@ -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,162 @@
|
||||
/*
|
||||
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;
|
||||
|
||||
@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) {
|
||||
|
||||
_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)tintColorDidChange {
|
||||
[super tintColorDidChange];
|
||||
self.progressView.progressTintColor = self.tintColor;
|
||||
}
|
||||
|
||||
- (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 {
|
||||
[NSLayoutConstraint deactivateConstraints:self.constraints];
|
||||
|
||||
const CGFloat ORKPSATKeyboardWidth = ORKGetMetricForWindow(ORKScreenMetricPSATKeyboardViewWidth, self.window);
|
||||
const CGFloat ORKPSATKeyboardHeight = ORKGetMetricForWindow(ORKScreenMetricPSATKeyboardViewHeight, self.window);
|
||||
|
||||
NSMutableArray *constraints = [NSMutableArray array];
|
||||
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(_progressView, _digitLabel, _keyboardView);
|
||||
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_progressView]-|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil
|
||||
views:views]];
|
||||
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_keyboardView(==keyboardWidth)]-|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:@{ @"keyboardWidth": @(ORKPSATKeyboardWidth) }
|
||||
views:views]];
|
||||
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_keyboardView(==keyboardHeight)]"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:@{ @"keyboardHeight": @(ORKPSATKeyboardHeight) }
|
||||
views:views]];
|
||||
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_progressView]-[_digitLabel]-(>=10)-[_keyboardView]-|"
|
||||
options:NSLayoutFormatAlignAllCenterX
|
||||
metrics:nil
|
||||
views:views]];
|
||||
|
||||
[NSLayoutConstraint activateConstraints: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,152 @@
|
||||
/*
|
||||
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 {
|
||||
[NSLayoutConstraint deactivateConstraints:self.constraints];
|
||||
|
||||
NSMutableArray *constraints = [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
|
||||
[constraints 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
|
||||
[constraints 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
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[answer13Button]-[answer14Button(==answer13Button)]-[answer15Button(==answer13Button)]-[answer16Button(==answer13Button)]-[answer17Button(==answer13Button)]-|"
|
||||
options:NSLayoutFormatAlignAllCenterY|NSLayoutFormatAlignAllTop|NSLayoutFormatAlignAllBottom
|
||||
metrics:nil views:views]];
|
||||
|
||||
// Align vertically
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[answer3Button]-[answer8Button(==answer3Button)]-[answer13Button(==answer3Button)]-|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil views:views]];
|
||||
|
||||
[NSLayoutConstraint activateConstraints: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
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
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 ORKPSATStepViewController : ORKActiveStepViewController
|
||||
|
||||
@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<NSNumber *> *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: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 = self.digits[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[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[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 = self.digits[self.currentDigitIndex - 1].integerValue;
|
||||
NSInteger currentDigit = self.digits[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
|
||||
@@ -66,9 +66,9 @@
|
||||
|
||||
- (void)updateStatisticsWithData:(CMPedometerData *)pedometerData {
|
||||
_lastUpdateDate = pedometerData.endDate;
|
||||
_totalNumberOfSteps = [pedometerData.numberOfSteps integerValue];
|
||||
_totalNumberOfSteps = pedometerData.numberOfSteps.integerValue;
|
||||
if (pedometerData.distance) {
|
||||
_totalDistance = [pedometerData.distance doubleValue];
|
||||
_totalDistance = pedometerData.distance.doubleValue;
|
||||
} else {
|
||||
_totalDistance = -1;
|
||||
}
|
||||
@@ -90,10 +90,10 @@
|
||||
_totalNumberOfSteps = 0;
|
||||
_totalDistance = -1;
|
||||
|
||||
if (! _logger) {
|
||||
if (!_logger) {
|
||||
NSError *err = nil;
|
||||
_logger = [self makeJSONDataLoggerWithError:&err];
|
||||
if (! _logger) {
|
||||
if (!_logger) {
|
||||
[self finishRecordingWithError:err];
|
||||
return;
|
||||
}
|
||||
@@ -101,7 +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}]];
|
||||
@@ -179,11 +179,6 @@
|
||||
@end
|
||||
|
||||
|
||||
@interface ORKPedometerRecorderConfiguration ()
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKPedometerRecorderConfiguration
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
||||
|
||||
@@ -34,14 +34,9 @@
|
||||
#import "ORKNavigationContainerView.h"
|
||||
|
||||
|
||||
@interface ORKReactionTimeContentView ()
|
||||
|
||||
@property (nonatomic, strong) ORKReactionTimeStimulusView *stimulusView;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKReactionTimeContentView
|
||||
@implementation ORKReactionTimeContentView {
|
||||
ORKReactionTimeStimulusView *_stimulusView;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
}
|
||||
if (self.maximumStimulusInterval < self.minimumStimulusInterval) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException
|
||||
reason:@"maximumStimulusInterval can not be less than minimumStimulusInterval"
|
||||
reason:@"maximumStimulusInterval cannot be less than minimumStimulusInterval"
|
||||
userInfo:nil];
|
||||
}
|
||||
if (self.thresholdAcceleration <= 0) {
|
||||
|
||||
@@ -84,10 +84,8 @@ static const NSTimeInterval OutcomeAnimationDuration = 0.3;
|
||||
}
|
||||
|
||||
#if TARGET_IPHONE_SIMULATOR
|
||||
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
|
||||
{
|
||||
if(event.type == UIEventSubtypeMotionShake)
|
||||
{
|
||||
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event {
|
||||
if (event.type == UIEventSubtypeMotionShake) {
|
||||
if (_validResult) {
|
||||
ORKReactionTimeResult *reactionTimeResult = [[ORKReactionTimeResult alloc] initWithIdentifier:self.step.identifier];
|
||||
reactionTimeResult.timestamp = _stimulusTimestamp;
|
||||
@@ -147,13 +145,13 @@ static const NSTimeInterval OutcomeAnimationDuration = 0.3;
|
||||
|
||||
- (void)configureTitle {
|
||||
NSString *format = ORKLocalizedString(@"REACTION_TIME_TASK_ATTEMPTS_FORMAT", nil);
|
||||
NSString *text = [NSString stringWithFormat:format, _results.count + 1, [self reactionTimeStep].numberOfAttempts];
|
||||
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) {
|
||||
if (_results.count == [self reactionTimeStep].numberOfAttempts) {
|
||||
[self finish];
|
||||
} else {
|
||||
[self resetAfterDelay:2];
|
||||
|
||||
@@ -104,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
|
||||
|
||||
|
||||
@@ -40,12 +40,14 @@
|
||||
|
||||
@implementation ORKRecorderConfiguration
|
||||
|
||||
- (instancetype)init {
|
||||
ORKThrowMethodUnavailableException();
|
||||
}
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
if (nil == identifier) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"identifier cannot be nil." userInfo:nil];
|
||||
}
|
||||
ORKThrowInvalidArgumentExceptionIfNil(identifier);
|
||||
_identifier = [identifier copy];
|
||||
}
|
||||
return self;
|
||||
@@ -82,7 +84,7 @@
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSSet *)requestedHealthKitTypesForReading {
|
||||
- (NSSet<HKObjectType *> *)requestedHealthKitTypesForReading {
|
||||
return nil;
|
||||
}
|
||||
- (ORKPermissionMask)requestedPermissionMask {
|
||||
@@ -167,10 +169,10 @@
|
||||
}
|
||||
|
||||
- (NSURL *)recordingDirectoryURL {
|
||||
if (! _outputDirectory) {
|
||||
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 {
|
||||
@@ -178,12 +180,12 @@
|
||||
}
|
||||
|
||||
- (NSString *)logName {
|
||||
return [NSString stringWithFormat:@"%@_%@", [self recorderType],self.identifier];
|
||||
return [NSString stringWithFormat:@"%@_%@", [self recorderType], _recorderUUID.UUIDString];
|
||||
}
|
||||
|
||||
- (ORKDataLogger *)makeJSONDataLoggerWithError:(NSError * __autoreleasing *)error {
|
||||
NSURL *workingDir = [self recordingDirectoryURL];
|
||||
if (! workingDir) {
|
||||
if (!workingDir) {
|
||||
if (error) {
|
||||
*error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteInvalidFileNameError userInfo:@{NSLocalizedDescriptionKey:ORKLocalizedString(@"ERROR_RECORDER_NO_OUTPUT_DIRECTORY", nil)}];
|
||||
}
|
||||
@@ -219,7 +221,7 @@
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
NSError *error = nil;
|
||||
if (! [fileManager setAttributes:@{NSFileProtectionKey : ORKFileProtectionFromMode(fileProtection)} ofItemAtPath:[url path] error:&error]) {
|
||||
ORK_Log_Debug(@"Error setting %@ on %@: %@", ORKFileProtectionFromMode(fileProtection), url, error);
|
||||
ORK_Log_Warning(@"Error setting %@ on %@: %@", ORKFileProtectionFromMode(fileProtection), url, error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,7 +233,7 @@
|
||||
ORKFileResult *result = [[ORKFileResult alloc] initWithIdentifier:self.identifier];
|
||||
result.contentType = [self mimeType];
|
||||
result.fileURL = fileUrl;
|
||||
result.userInfo = [self userInfo];
|
||||
result.userInfo = self.userInfo;
|
||||
result.startDate = self.startDate;
|
||||
|
||||
[localDelegate recorder:self didCompleteWithResult:result];
|
||||
@@ -240,7 +242,7 @@
|
||||
[self reset];
|
||||
}
|
||||
} else {
|
||||
if (! error) {
|
||||
if (!error) {
|
||||
error = [NSError errorWithDomain:NSCocoaErrorDomain
|
||||
code:NSFileReadNoSuchFileError
|
||||
userInfo:@{NSLocalizedDescriptionKey:ORKLocalizedString(@"ERROR_RECORDER_NO_DATA", nil)}];
|
||||
|
||||
@@ -36,11 +36,6 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class ORKDataLogger;
|
||||
|
||||
@interface ORKRecorderConfiguration ()
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface ORKRecorder ()
|
||||
|
||||
@property (nonatomic, strong, nullable) ORKStep *step;
|
||||
|
||||
@@ -35,11 +35,13 @@
|
||||
/**
|
||||
The `ORKSpatialSpanGame` class represents a model object that represents one game-like experience in a spatial span memory task.
|
||||
|
||||
A game consists of a subset of a permutation of the integers [0 .. gameSize-1],
|
||||
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.
|
||||
|
||||
|
||||
@@ -30,12 +30,16 @@
|
||||
|
||||
|
||||
#import "ORKSpatialSpanGame.h"
|
||||
|
||||
#import "ORKHelpers.h"
|
||||
|
||||
@implementation ORKSpatialSpanGame {
|
||||
NSInteger *_sequence;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
ORKThrowMethodUnavailableException();
|
||||
}
|
||||
|
||||
- (void)generateSequence {
|
||||
_sequence = calloc(_gameSize, sizeof(NSInteger));
|
||||
if (_sequence == NULL) {
|
||||
|
||||
@@ -45,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;
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
|
||||
#import "ORKSpatialSpanGameState.h"
|
||||
#import "ORKSpatialSpanGame.h"
|
||||
#import "ORKHelpers.h"
|
||||
|
||||
|
||||
@implementation ORKSpatialSpanGameState {
|
||||
@@ -38,6 +39,10 @@
|
||||
ORKSpatialSpanTargetState *_states;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
ORKThrowMethodUnavailableException();
|
||||
}
|
||||
|
||||
- (instancetype)initWithGame:(ORKSpatialSpanGame *)game {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
@@ -84,13 +89,13 @@
|
||||
return ORKSpatialSpanResultIgnore;
|
||||
}
|
||||
|
||||
NSInteger sequencePosition = [_plays count];
|
||||
NSInteger sequencePosition = _plays.count;
|
||||
BOOL correct = ([_game tileIndexForStep:sequencePosition] == tileIndex);
|
||||
_states[tileIndex] = correct ? ORKSpatialSpanTargetStateCorrect : ORKSpatialSpanTargetStateIncorrect;
|
||||
if (correct) {
|
||||
[_plays addObject:@(tileIndex)];
|
||||
}
|
||||
if ([_plays count] >= [_game sequenceLength]) {
|
||||
if (_plays.count >= [_game sequenceLength]) {
|
||||
_complete = YES;
|
||||
}
|
||||
|
||||
@@ -98,14 +103,14 @@
|
||||
}
|
||||
|
||||
- (NSInteger)currentStepIndex {
|
||||
return [_plays count];
|
||||
return _plays.count;
|
||||
}
|
||||
|
||||
- (NSInteger)lastSuccessfulTileIndex {
|
||||
if (! [_plays count]) {
|
||||
if (!_plays.count) {
|
||||
return NSNotFound;
|
||||
}
|
||||
return [[_plays lastObject] integerValue];
|
||||
return ((NSNumber *)_plays.lastObject).integerValue;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -63,8 +63,8 @@
|
||||
|
||||
- (void)resetTilesAnimated:(BOOL)animated {
|
||||
NSArray *currentViews = _tileViews;
|
||||
NSInteger numberOfTilesOld = [_tileViews count];
|
||||
NSInteger numberOfTilesNew = _gridSize.width*_gridSize.height;
|
||||
NSInteger numberOfTilesOld = _tileViews.count;
|
||||
NSInteger numberOfTilesNew = _gridSize.width * _gridSize.height;
|
||||
NSMutableArray *newViews = [NSMutableArray arrayWithCapacity:numberOfTilesNew];
|
||||
NSArray *viewsToRemove = nil;
|
||||
if (numberOfTilesOld <= numberOfTilesNew) {
|
||||
@@ -123,15 +123,15 @@
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
|
||||
CGRect bounds = [self bounds];
|
||||
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*(bounds.size.width - (gridItemSize.width * _gridSize.width));
|
||||
centeringOffset.y = 0.5*(bounds.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++) {
|
||||
@@ -168,8 +168,6 @@
|
||||
@implementation ORKSpatialSpanMemoryContentView {
|
||||
ORKQuantityPairView *_quantityPairView;
|
||||
ORKNavigationContainerView *_continueView;
|
||||
NSArray *_constraints;
|
||||
NSLayoutConstraint *_topConstraint;
|
||||
}
|
||||
|
||||
- (ORKActiveStepQuantityView *)countView {
|
||||
@@ -221,7 +219,7 @@
|
||||
[self countView].backgroundColor = [[UIColor purpleColor] colorWithAlphaComponent:0.2];
|
||||
#endif
|
||||
|
||||
[self setNeedsUpdateConstraints];
|
||||
[self setUpConstraints];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -258,39 +256,41 @@
|
||||
[self updateFooterHidden];
|
||||
}
|
||||
|
||||
|
||||
- (void)updateMargins {
|
||||
self.layoutMargins = (UIEdgeInsets){.left=ORKStandardHorizMarginForView(self), .right=ORKStandardHorizMarginForView(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)updateConstraints {
|
||||
if (_constraints) {
|
||||
[NSLayoutConstraint deactivateConstraints:_constraints];
|
||||
_constraints = nil;
|
||||
}
|
||||
|
||||
NSMutableArray *constraints = [NSMutableArray array];
|
||||
- (void)setBounds:(CGRect)bounds {
|
||||
[super setBounds:bounds];
|
||||
[self updateMargins];
|
||||
}
|
||||
|
||||
- (void)setUpConstraints {
|
||||
NSMutableArray *constraints = [NSMutableArray new];
|
||||
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(_gameView, _quantityPairView, _continueView);
|
||||
|
||||
[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:@"V:|-(>=0)-[_gameView][_quantityPairView]|"
|
||||
options:NSLayoutFormatAlignAllCenterX
|
||||
metrics:nil
|
||||
views:views]];
|
||||
NSLayoutConstraint *gameViewHeightConstraint = [NSLayoutConstraint constraintWithItem:_gameView
|
||||
attribute:NSLayoutAttributeHeight
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1.0
|
||||
constant:ORKScreenMetricMaxDimension];
|
||||
gameViewHeightConstraint.priority = UILayoutPriorityDefaultLow - 1;
|
||||
[constraints addObject:gameViewHeightConstraint];
|
||||
|
||||
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_gameView]-|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
@@ -327,13 +327,10 @@
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1.0
|
||||
constant:ORKScreenMetricMaxDimension];
|
||||
maxWidthConstraint.priority = UILayoutPriorityRequired-1;
|
||||
maxWidthConstraint.priority = UILayoutPriorityRequired - 1;
|
||||
[constraints addObject:maxWidthConstraint];
|
||||
|
||||
[NSLayoutConstraint activateConstraints:constraints];
|
||||
_constraints = constraints;
|
||||
|
||||
[super updateConstraints];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -76,45 +76,45 @@
|
||||
|
||||
if ( self.initialSpan < ORKSpatialSpanMemoryTaskMinimumInitialSpan) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException
|
||||
reason:[NSString stringWithFormat:@"initialSpan can not be less than %@.", @(ORKSpatialSpanMemoryTaskMinimumInitialSpan)]
|
||||
reason:[NSString stringWithFormat:@"initialSpan cannot be less than %@.", @(ORKSpatialSpanMemoryTaskMinimumInitialSpan)]
|
||||
userInfo:nil];
|
||||
}
|
||||
|
||||
if ( self.minimumSpan > self.initialSpan) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"initialSpan can not be less than minimumSpan." userInfo:nil];
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"initialSpan cannot be less than minimumSpan." userInfo:nil];
|
||||
}
|
||||
|
||||
if ( self.initialSpan > self.maximumSpan) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"maximumSpan can not be less than initialSpan." userInfo:nil];
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"maximumSpan cannot be less than initialSpan." userInfo:nil];
|
||||
}
|
||||
|
||||
if ( self.maximumSpan > ORKSpatialSpanMemoryTaskMaximumSpan) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException
|
||||
reason:[NSString stringWithFormat:@"maximumSpan can not be more than %@.", @(ORKSpatialSpanMemoryTaskMaximumSpan)]
|
||||
reason:[NSString stringWithFormat:@"maximumSpan cannot be more than %@.", @(ORKSpatialSpanMemoryTaskMaximumSpan)]
|
||||
userInfo:nil];
|
||||
}
|
||||
|
||||
if (self.playSpeed < ORKSpatialSpanMemoryTaskMinimumPlaySpeed) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException
|
||||
reason:[NSString stringWithFormat:@"playSpeed can not be shorter than %@ seconds.", @(ORKSpatialSpanMemoryTaskMinimumPlaySpeed)]
|
||||
reason:[NSString stringWithFormat:@"playSpeed cannot be shorter than %@ seconds.", @(ORKSpatialSpanMemoryTaskMinimumPlaySpeed)]
|
||||
userInfo:nil];
|
||||
}
|
||||
|
||||
if (self.playSpeed > ORKSpatialSpanMemoryTaskMaximumPlaySpeed) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException
|
||||
reason:[NSString stringWithFormat:@"playSpeed can not be longer than %@ seconds.", @(ORKSpatialSpanMemoryTaskMaximumPlaySpeed)]
|
||||
reason:[NSString stringWithFormat:@"playSpeed cannot be longer than %@ seconds.", @(ORKSpatialSpanMemoryTaskMaximumPlaySpeed)]
|
||||
userInfo:nil];
|
||||
}
|
||||
|
||||
if (self.maxTests < ORKSpatialSpanMemoryTaskMinimumMaxTests) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException
|
||||
reason:[NSString stringWithFormat:@"maxTests can not be less than %@.", @(ORKSpatialSpanMemoryTaskMinimumMaxTests)]
|
||||
reason:[NSString stringWithFormat:@"maxTests cannot be less than %@.", @(ORKSpatialSpanMemoryTaskMinimumMaxTests)]
|
||||
userInfo:nil];
|
||||
}
|
||||
|
||||
if (self.maxConsecutiveFailures < ORKSpatialSpanMemoryTaskMinimumMaxConsecutiveFailures) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException
|
||||
reason:[NSString stringWithFormat:@"maxConsecutiveFailures can not be less than %@.", @(ORKSpatialSpanMemoryTaskMinimumMaxConsecutiveFailures)]
|
||||
reason:[NSString stringWithFormat:@"maxConsecutiveFailures cannot be less than %@.", @(ORKSpatialSpanMemoryTaskMinimumMaxConsecutiveFailures)]
|
||||
userInfo:nil];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
#import "ORKActiveStepView.h"
|
||||
|
||||
|
||||
static const NSTimeInterval kMemoryGameActivityTimeout = 20;
|
||||
static const NSTimeInterval MemoryGameActivityTimeout = 20;
|
||||
|
||||
typedef NS_ENUM(NSInteger, ORKSpatialSpanStepState) {
|
||||
ORKSpatialSpanStepStateInitial,
|
||||
@@ -172,7 +172,7 @@ typedef void (^_ORKStateHandler)(ORKState *fromState, ORKState *_toState, id con
|
||||
- (void)start {
|
||||
[super start];
|
||||
|
||||
if (! _state) {
|
||||
if (!_state) {
|
||||
[self transitionToState:ORKSpatialSpanStepStateInitial];
|
||||
}
|
||||
|
||||
@@ -245,7 +245,7 @@ typedef void (^_ORKStateHandler)(ORKState *fromState, ORKState *_toState, id con
|
||||
#pragma mark UpdateGameRecord
|
||||
|
||||
- (ORKSpatialSpanMemoryGameRecord *)currentGameRecord {
|
||||
return _gameRecords? [_gameRecords lastObject] : nil;
|
||||
return _gameRecords? _gameRecords.lastObject : nil;
|
||||
}
|
||||
|
||||
- (void)createGameRecord {
|
||||
@@ -341,7 +341,7 @@ typedef void (^_ORKStateHandler)(ORKState *fromState, ORKState *_toState, id con
|
||||
#pragma mark ORKSpatialSpanStepStateInitial
|
||||
|
||||
- (ORKGridSize)gridSizeForSpan:(NSInteger)span {
|
||||
NSInteger numberOfGridEntriesDesired = span*2;
|
||||
NSInteger numberOfGridEntriesDesired = span * 2;
|
||||
NSInteger value = (NSInteger)ceil(sqrt(numberOfGridEntriesDesired));
|
||||
value = MAX(value, 2);
|
||||
value = MIN(value, 6);
|
||||
@@ -377,7 +377,7 @@ typedef void (^_ORKStateHandler)(ORKState *fromState, ORKState *_toState, id con
|
||||
NSInteger sequenceLength = _nextGameSequenceLength;
|
||||
_gridSize = [self gridSizeForSpan:sequenceLength];
|
||||
|
||||
ORKSpatialSpanGame *game = [[ORKSpatialSpanGame alloc] initWithGameSize:_gridSize.width*_gridSize.height sequenceLength:sequenceLength seed:0];
|
||||
ORKSpatialSpanGame *game = [[ORKSpatialSpanGame alloc] initWithGameSize:_gridSize.width * _gridSize.height sequenceLength:sequenceLength seed:0];
|
||||
ORKSpatialSpanGameState *gameState = [[ORKSpatialSpanGameState alloc] initWithGame:game];
|
||||
|
||||
_currentGameState = gameState;
|
||||
@@ -413,7 +413,7 @@ typedef void (^_ORKStateHandler)(ORKState *fromState, ORKState *_toState, id con
|
||||
ORKSpatialSpanMemoryStep *step = [self spatialSpanStep];
|
||||
|
||||
NSInteger index = _playbackIndex;
|
||||
NSInteger previousIndex = index-1;
|
||||
NSInteger previousIndex = index - 1;
|
||||
if (step.requireReversal) {
|
||||
// Play the indexes in reverse order when we require reversal. The participant
|
||||
// is then required to tap the sequence in the forward direction, which
|
||||
@@ -426,7 +426,7 @@ typedef void (^_ORKStateHandler)(ORKState *fromState, ORKState *_toState, id con
|
||||
[self applyTargetState:ORKSpatialSpanTargetStateQuiescent toSequenceIndex:previousIndex duration:0];
|
||||
|
||||
// The active display should be visible for half the timer interval
|
||||
[self applyTargetState:ORKSpatialSpanTargetStateActive toSequenceIndex:index duration:step.playSpeed/2];
|
||||
[self applyTargetState:ORKSpatialSpanTargetStateActive toSequenceIndex:index duration:(step.playSpeed / 2)];
|
||||
}
|
||||
_playbackIndex++;
|
||||
}
|
||||
@@ -471,7 +471,7 @@ typedef void (^_ORKStateHandler)(ORKState *fromState, ORKState *_toState, id con
|
||||
[_activityTimer invalidate];
|
||||
_activityTimer = nil;
|
||||
|
||||
_activityTimer = [NSTimer scheduledTimerWithTimeInterval:kMemoryGameActivityTimeout target:self selector:@selector(activityTimeout) userInfo:nil repeats:NO];
|
||||
_activityTimer = [NSTimer scheduledTimerWithTimeInterval:MemoryGameActivityTimeout target:self selector:@selector(activityTimeout) userInfo:nil repeats:NO];
|
||||
}
|
||||
|
||||
- (void)startGameplay {
|
||||
@@ -522,8 +522,8 @@ typedef void (^_ORKStateHandler)(ORKState *fromState, ORKState *_toState, id con
|
||||
[gameView setState:ORKSpatialSpanTargetStateCorrect forTileIndex:tileIndex animated:YES];
|
||||
NSInteger stepIndex = [_currentGameState currentStepIndex];
|
||||
|
||||
[self setNumberOfItems:_numberOfItems+1];
|
||||
[self setScore:_score + (round(log2(stepIndex))+1)*5];
|
||||
[self setNumberOfItems:_numberOfItems + 1];
|
||||
[self setScore:_score + (round(log2(stepIndex)) + 1) * 5];
|
||||
|
||||
[self resetActivityTimer];
|
||||
if ([_currentGameState isComplete]) {
|
||||
@@ -544,7 +544,7 @@ typedef void (^_ORKStateHandler)(ORKState *fromState, ORKState *_toState, id con
|
||||
ORKSpatialSpanMemoryStep *step = [self spatialSpanStep];
|
||||
if (success) {
|
||||
NSInteger sequenceLength = [_currentGameState.game sequenceLength];
|
||||
[self setScore:_score + (round(log2(sequenceLength))+1)*5];
|
||||
[self setScore:_score + (round(log2(sequenceLength)) + 1) * 5];
|
||||
_gamesCounter++;
|
||||
_consecutiveGamesFailed = 0;
|
||||
_nextGameSequenceLength = MIN(_nextGameSequenceLength + 1, step.maximumSpan);
|
||||
|
||||
@@ -144,7 +144,7 @@ static UIBezierPath *ORKErrorBezierPath() {
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)rect {
|
||||
CGRect bounds = [self bounds];
|
||||
CGRect bounds = self.bounds;
|
||||
|
||||
CGContextRef ctx = UIGraphicsGetCurrentContext();
|
||||
[[UIColor clearColor] setFill];
|
||||
@@ -284,7 +284,7 @@ static UIBezierPath *ORKErrorBezierPath() {
|
||||
|
||||
case ORKSpatialSpanTargetStateIncorrect:
|
||||
_flowerView.tintColor = [UIColor ork_redColor];
|
||||
newTransform = CGAffineTransformMakeScale(0.9*_flowerScaleFactor, 0.9*_flowerScaleFactor);
|
||||
newTransform = CGAffineTransformMakeScale(0.9 * _flowerScaleFactor, 0.9 * _flowerScaleFactor);
|
||||
oldCircleAlpha = 0;
|
||||
newCircleAlpha = 1;
|
||||
oldCircleTransform = CGAffineTransformMakeScale(0.2, 0.2);
|
||||
@@ -296,7 +296,7 @@ static UIBezierPath *ORKErrorBezierPath() {
|
||||
|
||||
case ORKSpatialSpanTargetStateCorrect:
|
||||
_flowerView.tintColor = [self tintColor];
|
||||
newTransform = CGAffineTransformMakeScale(1.1*_flowerScaleFactor, 1.1*_flowerScaleFactor);
|
||||
newTransform = CGAffineTransformMakeScale(1.1 * _flowerScaleFactor, 1.1 * _flowerScaleFactor);
|
||||
oldCircleAlpha = 0;
|
||||
newCircleAlpha = 1;
|
||||
oldCircleTransform = CGAffineTransformMakeScale(0.2, 0.2);
|
||||
@@ -336,14 +336,14 @@ static UIBezierPath *ORKErrorBezierPath() {
|
||||
|
||||
CGFloat designWidth = ORKFlowerBezierPathSize.width + _ORKFlowerMargins.left + _ORKFlowerMargins.right;
|
||||
CGFloat scaleFactor = bounds.size.width / designWidth;
|
||||
CGAffineTransform tfm = CGAffineTransformMakeScale(scaleFactor, scaleFactor);
|
||||
CGAffineTransform transform = CGAffineTransformMakeScale(scaleFactor, scaleFactor);
|
||||
|
||||
CGRect checkRect = CGRectApplyAffineTransform((CGRect){CGPointZero,ORKCheckBezierPathSize}, tfm);
|
||||
CGRect checkRect = CGRectApplyAffineTransform((CGRect){CGPointZero, ORKCheckBezierPathSize}, transform);
|
||||
[_checkView setBounds:checkRect];
|
||||
_checkView.layer.cornerRadius = checkRect.size.width/2;
|
||||
CGRect errorRect = CGRectApplyAffineTransform((CGRect){CGPointZero,ORKErrorBezierPathSize}, tfm);
|
||||
_checkView.layer.cornerRadius = checkRect.size.width / 2;
|
||||
CGRect errorRect = CGRectApplyAffineTransform((CGRect){CGPointZero, ORKErrorBezierPathSize}, transform);
|
||||
[_errorView setBounds:errorRect];
|
||||
_errorView.layer.cornerRadius = errorRect.size.width/2;
|
||||
_errorView.layer.cornerRadius = errorRect.size.width / 2;
|
||||
_errorView.center = _flowerView.center;
|
||||
_checkView.center = _flowerView.center;
|
||||
}
|
||||
|
||||
@@ -51,16 +51,17 @@
|
||||
|
||||
|
||||
@implementation ORKTappingContentView {
|
||||
|
||||
NSArray *_constraints;
|
||||
ORKScreenType _screenType;
|
||||
UIView *_buttonContainer;
|
||||
NSNumberFormatter *_formatter;
|
||||
NSLayoutConstraint *_topToProgressViewConstraint;
|
||||
NSLayoutConstraint *_topToCaptionLabelConstraint;
|
||||
NSLayoutConstraint *_captionLabelToTapCountLabelConstraint;
|
||||
NSLayoutConstraint *_tapButtonToBottomConstraint;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_screenType = ORKScreenTypeiPhone4;
|
||||
_tapCaptionLabel = [ORKSubheadlineLabel new];
|
||||
_tapCaptionLabel.textAlignment = NSTextAlignmentCenter;
|
||||
_tapCaptionLabel.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
@@ -96,7 +97,8 @@
|
||||
_tapCaptionLabel.text = ORKLocalizedString(@"TOTAL_TAPS_LABEL", nil);
|
||||
[self setTapCount:0];
|
||||
|
||||
[self setNeedsUpdateConstraints];
|
||||
[self setUpConstraints];
|
||||
[self updateConstraintConstantsForWindow:self.window];
|
||||
|
||||
_tapCountLabel.accessibilityTraits |= UIAccessibilityTraitUpdatesFrequently;
|
||||
|
||||
@@ -116,7 +118,12 @@
|
||||
}
|
||||
|
||||
- (void)setTapCount:(NSUInteger)tapCount {
|
||||
_tapCountLabel.text = [NSString stringWithFormat:@"%02lu", (unsigned long)tapCount];
|
||||
if (_formatter == nil) {
|
||||
_formatter = [NSNumberFormatter new];
|
||||
_formatter.locale = [NSLocale currentLocale];
|
||||
_formatter.minimumIntegerDigits = 2;
|
||||
}
|
||||
_tapCountLabel.text = [_formatter stringFromNumber:@(tapCount)];
|
||||
}
|
||||
|
||||
- (void)setProgress:(CGFloat)progress animated:(BOOL)animated {
|
||||
@@ -141,110 +148,114 @@
|
||||
|
||||
- (void)willMoveToWindow:(UIWindow *)newWindow {
|
||||
[super willMoveToWindow:newWindow];
|
||||
_screenType = ORKGetScreenTypeForWindow(newWindow);
|
||||
[self setNeedsUpdateConstraints];
|
||||
[self updateConstraintConstantsForWindow:newWindow];
|
||||
}
|
||||
|
||||
- (void)updateConstraints {
|
||||
if ([_constraints count]) {
|
||||
[NSLayoutConstraint deactivateConstraints:_constraints];
|
||||
_constraints = nil;
|
||||
}
|
||||
|
||||
ORKScreenType screenType = _screenType;
|
||||
const CGFloat HeaderBaselineToCaptionTop = ORKGetMetricForScreenType(ORKScreenMetricCaptionBaselineToTappingLabelTop, screenType);
|
||||
const CGFloat AssumedHeaderBaselineToStepViewTop = ORKGetMetricForScreenType(ORKScreenMetricLearnMoreBaselineToStepViewTop, screenType);
|
||||
CGFloat margin = ORKStandardHorizMarginForView(self);
|
||||
self.layoutMargins = (UIEdgeInsets) { .left=margin*2, .right=margin*2 };
|
||||
|
||||
static const CGFloat CaptionBaselineToTapCountBaseline = 56;
|
||||
static const CGFloat TapButtonBottomToBottom = 36;
|
||||
|
||||
// On the iPhone, _progressView is positioned outside the bounds of this view, to be in-between the header and this view.
|
||||
// On the iPad, we want to stretch this out a bit so it feels less compressed.
|
||||
CGFloat progressViewOffset, topCaptionLabelOffset;
|
||||
if (screenType == ORKScreenTypeiPad) {
|
||||
progressViewOffset = 0;
|
||||
topCaptionLabelOffset = AssumedHeaderBaselineToStepViewTop;
|
||||
} else {
|
||||
progressViewOffset = (HeaderBaselineToCaptionTop/3) - AssumedHeaderBaselineToStepViewTop;
|
||||
topCaptionLabelOffset = HeaderBaselineToCaptionTop - AssumedHeaderBaselineToStepViewTop;
|
||||
}
|
||||
|
||||
- (void)updateLayoutMargins {
|
||||
CGFloat margin = ORKStandardHorizontalMarginForView(self);
|
||||
self.layoutMargins = (UIEdgeInsets){.left = margin * 2, .right=margin * 2};
|
||||
}
|
||||
|
||||
- (void)setFrame:(CGRect)frame {
|
||||
[super setFrame:frame];
|
||||
[self updateLayoutMargins];
|
||||
}
|
||||
|
||||
- (void)setBounds:(CGRect)bounds {
|
||||
[super setBounds:bounds];
|
||||
[self updateLayoutMargins];
|
||||
}
|
||||
|
||||
- (void)setUpConstraints {
|
||||
NSMutableArray *constraints = [NSMutableArray array];
|
||||
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(_buttonContainer, _tapCaptionLabel, _tapCountLabel, _progressView, _tapButton1, _tapButton2);
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_progressView
|
||||
attribute:NSLayoutAttributeTop
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeTop
|
||||
multiplier:1 constant:progressViewOffset]];
|
||||
_topToProgressViewConstraint = [NSLayoutConstraint constraintWithItem:_progressView
|
||||
attribute:NSLayoutAttributeTop
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeTop
|
||||
multiplier:1.0
|
||||
constant:0.0]; // constant set in updateConstraintConstantsForWindow:
|
||||
[constraints addObject:_topToProgressViewConstraint];
|
||||
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_tapCaptionLabel
|
||||
attribute:NSLayoutAttributeTop
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeTop
|
||||
multiplier:1 constant:topCaptionLabelOffset]];
|
||||
_topToCaptionLabelConstraint = [NSLayoutConstraint constraintWithItem:_tapCaptionLabel
|
||||
attribute:NSLayoutAttributeTop
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeTop
|
||||
multiplier:1.0
|
||||
constant:0.0]; // constant set in updateConstraintConstantsForWindow:
|
||||
[constraints addObject:_topToCaptionLabelConstraint];
|
||||
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_tapCountLabel
|
||||
attribute:NSLayoutAttributeFirstBaseline
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_tapCaptionLabel
|
||||
attribute:NSLayoutAttributeFirstBaseline
|
||||
multiplier:1 constant:CaptionBaselineToTapCountBaseline]];
|
||||
_captionLabelToTapCountLabelConstraint = [NSLayoutConstraint constraintWithItem:_tapCountLabel
|
||||
attribute:NSLayoutAttributeFirstBaseline
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_tapCaptionLabel
|
||||
attribute:NSLayoutAttributeFirstBaseline
|
||||
multiplier:1.0
|
||||
constant:0.0]; // constant set in updateConstraintConstantsForWindow:
|
||||
[constraints addObject:_captionLabelToTapCountLabelConstraint];
|
||||
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:self
|
||||
attribute:NSLayoutAttributeBottom
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_buttonContainer
|
||||
attribute:NSLayoutAttributeBottom
|
||||
multiplier:1 constant:TapButtonBottomToBottom]];
|
||||
_tapButtonToBottomConstraint = [NSLayoutConstraint constraintWithItem:self
|
||||
attribute:NSLayoutAttributeBottom
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_buttonContainer
|
||||
attribute:NSLayoutAttributeBottom
|
||||
multiplier:1.0
|
||||
constant:0.0]; // constant set in updateConstraintConstantsForWindow:
|
||||
[constraints addObject:_tapButtonToBottomConstraint];
|
||||
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_tapCountLabel]-(>=10)-[_buttonContainer]"
|
||||
options:NSLayoutFormatAlignAllCenterX
|
||||
metrics:nil views:views]];
|
||||
metrics:nil
|
||||
views:views]];
|
||||
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_progressView]-|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil views:views]];
|
||||
NSLayoutConstraint *wideProgress = [NSLayoutConstraint constraintWithItem:_progressView
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1
|
||||
constant:2000];
|
||||
wideProgress.priority = UILayoutPriorityRequired-1;
|
||||
[constraints addObject:wideProgress];
|
||||
metrics:nil
|
||||
views:views]];
|
||||
NSLayoutConstraint *progressViewWidthConstraint = [NSLayoutConstraint constraintWithItem:_progressView
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1.0
|
||||
constant:ORKScreenMetricMaxDimension];
|
||||
progressViewWidthConstraint.priority = UILayoutPriorityRequired - 1;
|
||||
[constraints addObject:progressViewWidthConstraint];
|
||||
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_tapCaptionLabel]-|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil views:views]];
|
||||
metrics:nil
|
||||
views:views]];
|
||||
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_tapCountLabel]-|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil views:views]];
|
||||
metrics:nil
|
||||
views:views]];
|
||||
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_tapButton1]|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil views:views]];
|
||||
metrics:nil
|
||||
views:views]];
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_tapButton2]|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil views:views]];
|
||||
metrics:nil
|
||||
views:views]];
|
||||
|
||||
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_tapButton1]-(>=24)-[_tapButton2(==_tapButton1)]|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil views:views]];
|
||||
metrics:nil
|
||||
views:views]];
|
||||
|
||||
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_tapButton1
|
||||
@@ -252,13 +263,42 @@
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_tapButton2
|
||||
attribute:NSLayoutAttributeCenterY
|
||||
multiplier:1 constant:0]];
|
||||
|
||||
|
||||
_constraints = constraints;
|
||||
[self addConstraints:_constraints];
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
|
||||
[NSLayoutConstraint activateConstraints:constraints];
|
||||
}
|
||||
|
||||
- (void)updateConstraintConstantsForWindow:(UIWindow *)window {
|
||||
const CGFloat HeaderBaselineToCaptionTop = ORKGetMetricForWindow(ORKScreenMetricCaptionBaselineToTappingLabelTop, window);
|
||||
const CGFloat AssumedHeaderBaselineToStepViewTop = ORKGetMetricForWindow(ORKScreenMetricLearnMoreBaselineToStepViewTop, window);
|
||||
CGFloat margin = ORKStandardHorizontalMarginForView(self);
|
||||
self.layoutMargins = (UIEdgeInsets){.left = margin * 2, .right = margin * 2};
|
||||
|
||||
static const CGFloat CaptionBaselineToTapCountBaseline = 56;
|
||||
static const CGFloat TapButtonBottomToBottom = 36;
|
||||
|
||||
// On the iPhone, _progressView is positioned outside the bounds of this view, to be in-between the header and this view.
|
||||
// On the iPad, we want to stretch this out a bit so it feels less compressed.
|
||||
CGFloat topToProgressViewOffset = 0.0;
|
||||
CGFloat topToCaptionLabelOffset = 0.0;
|
||||
ORKScreenType screenType = ORKGetVerticalScreenTypeForWindow(window);
|
||||
if (screenType == ORKScreenTypeiPad) {
|
||||
topToProgressViewOffset = 0;
|
||||
topToCaptionLabelOffset = AssumedHeaderBaselineToStepViewTop;
|
||||
} else {
|
||||
topToProgressViewOffset = (HeaderBaselineToCaptionTop / 3) - AssumedHeaderBaselineToStepViewTop;
|
||||
topToCaptionLabelOffset = HeaderBaselineToCaptionTop - AssumedHeaderBaselineToStepViewTop;
|
||||
}
|
||||
|
||||
_topToProgressViewConstraint.constant = topToProgressViewOffset;
|
||||
_topToCaptionLabelConstraint.constant = topToCaptionLabelOffset;
|
||||
_captionLabelToTapCountLabelConstraint.constant = CaptionBaselineToTapCountBaseline;
|
||||
_tapButtonToBottomConstraint.constant = TapButtonBottomToBottom;
|
||||
}
|
||||
|
||||
- (void)updateConstraints {
|
||||
[self updateConstraintConstantsForWindow:self.window];
|
||||
[super updateConstraints];
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
NSTimeInterval const ORKTwoFingerTappingMinimumDuration = 5.0;
|
||||
|
||||
if ( self.stepDuration < ORKTwoFingerTappingMinimumDuration) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"duration can not be shorter than %@ seconds.", @(ORKTwoFingerTappingMinimumDuration)] userInfo:nil];
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"duration cannot be shorter than %@ seconds.", @(ORKTwoFingerTappingMinimumDuration)] userInfo:nil];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -136,7 +136,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
NSTimeInterval mediaTime = CACurrentMediaTime();
|
||||
NSTimeInterval mediaTime = touch.timestamp;
|
||||
|
||||
if (_tappingStart == 0) {
|
||||
_tappingStart = mediaTime;
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
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 "ORKCustomStepView_Internal.h"
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ORKTimedWalkContentView : ORKActiveStepCustomView
|
||||
|
||||
@property (nonatomic, strong, nullable) UIImage *image;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
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 "ORKTimedWalkContentView.h"
|
||||
#import "ORKSkin.h"
|
||||
#import "ORKProgressView.h"
|
||||
#import "ORKActiveStepQuantityView.h"
|
||||
#import "ORKTintedImageView.h"
|
||||
|
||||
|
||||
@interface ORKTimedWalkContentView ()
|
||||
|
||||
@property (nonatomic, strong) ORKProgressView *progressView;
|
||||
@property (nonatomic, strong) ORKTintedImageView *imageView;
|
||||
@property (nonatomic, strong) NSLayoutConstraint *imageRatioConstraint;
|
||||
@property (nonatomic, copy) NSArray *constraints;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKTimedWalkContentView
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_progressView = [ORKProgressView new];
|
||||
_progressView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[self addSubview:_progressView];
|
||||
|
||||
_imageView = [ORKTintedImageView new];
|
||||
_imageView.contentMode = UIViewContentModeScaleAspectFit;
|
||||
_imageView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
_imageView.shouldApplyTint = YES;
|
||||
_imageView.isAccessibilityElement = NO;
|
||||
[self addSubview:_imageView];
|
||||
|
||||
self.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
|
||||
[self setNeedsUpdateConstraints];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setImage:(UIImage *)image {
|
||||
_image = image;
|
||||
self.imageView.image = image;
|
||||
|
||||
self.imageRatioConstraint.active = NO;
|
||||
|
||||
CGSize size = image.size;
|
||||
if (size.width > 0 && size.height > 0) {
|
||||
self.imageRatioConstraint = [NSLayoutConstraint constraintWithItem:_imageView
|
||||
attribute:NSLayoutAttributeHeight
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_imageView
|
||||
attribute:NSLayoutAttributeWidth
|
||||
multiplier:size.height/size.width
|
||||
constant:0];
|
||||
self.imageRatioConstraint.active = YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateConstraints {
|
||||
[NSLayoutConstraint deactivateConstraints:self.constraints];
|
||||
|
||||
NSMutableArray *constraints = [NSMutableArray array];
|
||||
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(_progressView, _imageView);
|
||||
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_progressView]|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil views:views]];
|
||||
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[_progressView]-(>=10)-[_imageView]-|"
|
||||
options:NSLayoutFormatAlignAllCenterX
|
||||
metrics:nil views:views]];
|
||||
|
||||
[NSLayoutConstraint activateConstraints:constraints];
|
||||
[super updateConstraints];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
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 ORKTimedWalkStep : ORKActiveStep
|
||||
|
||||
@property (nonatomic, assign) double distanceInMeters;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
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 "ORKTimedWalkStep.h"
|
||||
#import "ORKTimedWalkStepViewController.h"
|
||||
|
||||
|
||||
@implementation ORKTimedWalkStep
|
||||
|
||||
+ (Class)stepViewControllerClass {
|
||||
return [ORKTimedWalkStepViewController class];
|
||||
}
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
||||
self = [super initWithIdentifier:identifier];
|
||||
if (self) {
|
||||
self.shouldStartTimerAutomatically = YES;
|
||||
self.shouldShowDefaultTimer = NO;
|
||||
self.shouldPlaySoundOnStart = YES;
|
||||
self.shouldPlaySoundOnFinish = YES;
|
||||
self.shouldVibrateOnStart = YES;
|
||||
self.shouldVibrateOnFinish = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)validateParameters {
|
||||
[super validateParameters];
|
||||
|
||||
double const ORKTimedWalkMinimumDistanceInMeters = 1.0;
|
||||
double const ORKTimedWalkMaximumDistanceInMeters = 10000.0;
|
||||
|
||||
NSTimeInterval const ORKTimedWalkMinimumDuration = 1.0;
|
||||
|
||||
if (self.distanceInMeters < ORKTimedWalkMinimumDistanceInMeters ||
|
||||
self.distanceInMeters > ORKTimedWalkMaximumDistanceInMeters) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"timed walk distance must be greater than or equal to %@ meters and less than or equal to %@ meters.", @(ORKTimedWalkMinimumDistanceInMeters), @(ORKTimedWalkMaximumDistanceInMeters)] userInfo:nil];
|
||||
}
|
||||
|
||||
if (self.stepDuration < ORKTimedWalkMinimumDuration) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"duration cannot be shorter than %@ seconds.", @(ORKTimedWalkMinimumDuration)] userInfo:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)allowsBackNavigation {
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
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 ORKTimedWalkStepViewController : ORKActiveStepViewController
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
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 "ORKTimedWalkStepViewController.h"
|
||||
#import "ORKTimedWalkContentView.h"
|
||||
#import "ORKTimedWalkStep.h"
|
||||
#import "ORKActiveStepViewController_Internal.h"
|
||||
#import "ORKNavigationContainerView_Internal.h"
|
||||
#import "ORKStepViewController_Internal.h"
|
||||
#import "ORKActiveStepView.h"
|
||||
|
||||
|
||||
@interface ORKTimedWalkStepViewController ()
|
||||
|
||||
@property (nonatomic, strong) NSMutableArray *samples;
|
||||
@property (nonatomic, strong) ORKTimedWalkContentView *timedWalkContentView;
|
||||
@property (nonatomic, assign) NSTimeInterval trialDuration;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKTimedWalkStepViewController
|
||||
|
||||
- (instancetype)initWithStep:(ORKStep *)step {
|
||||
self = [super initWithStep:step];
|
||||
|
||||
if (self) {
|
||||
self.suspendIfInactive = YES;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (ORKTimedWalkStep *)timedWalkStep {
|
||||
return (ORKTimedWalkStep *)self.step;
|
||||
}
|
||||
|
||||
- (void)initializeInternalButtonItems {
|
||||
[super initializeInternalButtonItems];
|
||||
|
||||
self.internalDoneButtonItem = nil;
|
||||
self.continueButtonTitle = ORKLocalizedString(@"BUTTON_DONE", nil);
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
self.timedWalkContentView = [ORKTimedWalkContentView new];
|
||||
self.timedWalkContentView.image = [self timedWalkStep].image;
|
||||
self.activeStepView.activeCustomView = self.timedWalkContentView;
|
||||
self.activeStepView.stepViewFillsAvailableSpace = YES;
|
||||
self.activeStepView.continueSkipContainer.continueEnabled = YES;
|
||||
|
||||
self.timerUpdateInterval = 0.1f;
|
||||
}
|
||||
|
||||
- (void)finish {
|
||||
[super finish];
|
||||
|
||||
[self goForward];
|
||||
}
|
||||
|
||||
- (void)countDownTimerFired:(ORKActiveStepTimer *)timer finished:(BOOL)finished {
|
||||
self.trialDuration = timer.runtime;
|
||||
[super countDownTimerFired:timer finished:finished];
|
||||
}
|
||||
|
||||
- (ORKStepResult *)result {
|
||||
ORKStepResult *sResult = [super result];
|
||||
|
||||
NSMutableArray *results = [NSMutableArray arrayWithArray:sResult.results];
|
||||
|
||||
ORKTimedWalkResult *timedWalkResult = [[ORKTimedWalkResult alloc] initWithIdentifier:self.step.identifier];
|
||||
timedWalkResult.distanceInMeters = [self timedWalkStep].distanceInMeters;
|
||||
timedWalkResult.timeLimit = [self timedWalkStep].stepDuration;
|
||||
timedWalkResult.duration = self.trialDuration;
|
||||
|
||||
[results addObject:timedWalkResult];
|
||||
|
||||
sResult.results = [results copy];
|
||||
|
||||
return sResult;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -38,20 +38,19 @@
|
||||
@property (nonatomic, strong) ORKUnitLabel *captionLabel;
|
||||
@property (nonatomic, strong) UIProgressView *progressView;
|
||||
|
||||
- (void)setupConstraints;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKToneAudiometryContentView {
|
||||
ORKScreenType _screenType;
|
||||
NSLayoutConstraint *_topToProgressViewConstraint;
|
||||
NSLayoutConstraint *_topToCaptionLabelConstraint;
|
||||
NSLayoutConstraint *_tapButtonToBottomConstraint;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
|
||||
_screenType = ORKGetScreenTypeForWindow(self.window);
|
||||
_captionLabel = [ORKUnitLabel new];
|
||||
_captionLabel.textAlignment = NSTextAlignmentCenter;
|
||||
_captionLabel.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
@@ -73,13 +72,18 @@
|
||||
|
||||
_captionLabel.text = nil;
|
||||
|
||||
[self setupConstraints];
|
||||
[self setNeedsUpdateConstraints];
|
||||
[self setUpConstraints];
|
||||
[self updateConstraintConstantsForWindow:self.window];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)willMoveToWindow:(UIWindow *)newWindow {
|
||||
[super willMoveToWindow:newWindow];
|
||||
[self updateConstraintConstantsForWindow:newWindow];
|
||||
}
|
||||
|
||||
- (void)tintColorDidChange {
|
||||
[super tintColorDidChange];
|
||||
self.progressView.progressTintColor = [self tintColor];
|
||||
@@ -101,73 +105,105 @@
|
||||
self.tapButton.enabled = NO;
|
||||
}
|
||||
|
||||
- (void)setupConstraints {
|
||||
ORKScreenType screenType = _screenType;
|
||||
const CGFloat HeaderBaselineToCaptionTop = ORKGetMetricForScreenType(ORKScreenMetricCaptionBaselineToTappingLabelTop, screenType);
|
||||
const CGFloat AssumedHeaderBaselineToStepViewTop = ORKGetMetricForScreenType(ORKScreenMetricLearnMoreBaselineToStepViewTop, screenType);
|
||||
CGFloat margin = ORKStandardHorizMarginForView(self);
|
||||
self.layoutMargins = (UIEdgeInsets) { .left=margin*2, .right=margin*2 };
|
||||
- (void)updateConstraintConstantsForWindow:(UIWindow *)window {
|
||||
const CGFloat HeaderBaselineToCaptionTop = ORKGetMetricForWindow(ORKScreenMetricCaptionBaselineToTappingLabelTop, window);
|
||||
const CGFloat AssumedHeaderBaselineToStepViewTop = ORKGetMetricForWindow(ORKScreenMetricLearnMoreBaselineToStepViewTop, window);
|
||||
static const CGFloat TapButtonBottomToBottom = 36.0;
|
||||
|
||||
_topToProgressViewConstraint.constant = (HeaderBaselineToCaptionTop / 3) - AssumedHeaderBaselineToStepViewTop;
|
||||
_topToCaptionLabelConstraint.constant = HeaderBaselineToCaptionTop - AssumedHeaderBaselineToStepViewTop;
|
||||
_tapButtonToBottomConstraint.constant = TapButtonBottomToBottom;
|
||||
}
|
||||
|
||||
static const CGFloat TapButtonBottomToBottom = 36;
|
||||
- (void)updateLayoutMargins {
|
||||
CGFloat margin = ORKStandardHorizontalMarginForView(self);
|
||||
self.layoutMargins = (UIEdgeInsets){.left = margin * 2, .right = margin * 2};
|
||||
}
|
||||
|
||||
- (void)setFrame:(CGRect)frame {
|
||||
[super setFrame:frame];
|
||||
[self updateLayoutMargins];
|
||||
}
|
||||
|
||||
- (void)setBounds:(CGRect)bounds {
|
||||
[super setBounds:bounds];
|
||||
[self updateLayoutMargins];
|
||||
}
|
||||
|
||||
- (void)setUpConstraints {
|
||||
NSMutableArray *constraints = [NSMutableArray array];
|
||||
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(_progressView, _captionLabel, _tapButton);
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_progressView
|
||||
attribute:NSLayoutAttributeTop
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeTop
|
||||
multiplier:1 constant:(HeaderBaselineToCaptionTop/3) - AssumedHeaderBaselineToStepViewTop]];
|
||||
_topToProgressViewConstraint = [NSLayoutConstraint constraintWithItem:_progressView
|
||||
attribute:NSLayoutAttributeTop
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeTop
|
||||
multiplier:1.0
|
||||
constant:0.0]; // constant will be set in updateConstraintConstantsForWindow:
|
||||
[constraints addObject:_topToProgressViewConstraint];
|
||||
|
||||
_topToCaptionLabelConstraint = [NSLayoutConstraint constraintWithItem:_captionLabel
|
||||
attribute:NSLayoutAttributeTop
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeTop
|
||||
multiplier:1.0
|
||||
constant:0.0]; // constant will be set in updateConstraintConstantsForWindow:
|
||||
[constraints addObject:_topToCaptionLabelConstraint];
|
||||
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_captionLabel
|
||||
attribute:NSLayoutAttributeTop
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeTop
|
||||
multiplier:1 constant:(HeaderBaselineToCaptionTop - AssumedHeaderBaselineToStepViewTop)]];
|
||||
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:self
|
||||
attribute:NSLayoutAttributeBottom
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_tapButton
|
||||
attribute:NSLayoutAttributeBottom
|
||||
multiplier:1 constant:TapButtonBottomToBottom]];
|
||||
_tapButtonToBottomConstraint = [NSLayoutConstraint constraintWithItem:self
|
||||
attribute:NSLayoutAttributeBottom
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_tapButton
|
||||
attribute:NSLayoutAttributeBottom
|
||||
multiplier:1.0
|
||||
constant:0.0]; // constant will be set in updateConstraintConstantsForWindow:
|
||||
[constraints addObject:_tapButtonToBottomConstraint];
|
||||
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_captionLabel]-(>=10)-[_tapButton]"
|
||||
options:NSLayoutFormatAlignAllCenterX
|
||||
metrics:nil views:views]];
|
||||
metrics:nil
|
||||
views:views]];
|
||||
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_progressView]-|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil views:views]];
|
||||
NSLayoutConstraint *wideProgress = [NSLayoutConstraint constraintWithItem:_progressView
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1
|
||||
constant:2000];
|
||||
wideProgress.priority = UILayoutPriorityRequired-1;
|
||||
[constraints addObject:wideProgress];
|
||||
metrics:nil
|
||||
views:views]];
|
||||
NSLayoutConstraint *progressWidthConstraint = [NSLayoutConstraint constraintWithItem:_progressView
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1.0
|
||||
constant:ORKScreenMetricMaxDimension];
|
||||
progressWidthConstraint.priority = UILayoutPriorityRequired - 1;
|
||||
[constraints addObject:progressWidthConstraint];
|
||||
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_captionLabel]-|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil views:views]];
|
||||
metrics:nil
|
||||
views:views]];
|
||||
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_tapButton
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
multiplier:1 constant:0]];
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
|
||||
[self addConstraints:constraints];
|
||||
|
||||
[NSLayoutConstraint activateConstraints:constraints];
|
||||
}
|
||||
|
||||
- (void)updateConstraints {
|
||||
[self updateConstraintConstantsForWindow:self.window];
|
||||
[super updateConstraints];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
NSTimeInterval const ORKToneAudiometryTaskToneMinimumDuration = 5.0;
|
||||
|
||||
if (self.toneDuration < ORKToneAudiometryTaskToneMinimumDuration) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"tone duration can not be shorter than %@ seconds.", @(ORKToneAudiometryTaskToneMinimumDuration)] userInfo:nil];
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"tone duration cannot be shorter than %@ seconds.", @(ORKToneAudiometryTaskToneMinimumDuration)] userInfo:nil];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@
|
||||
|
||||
NSMutableArray *results = [NSMutableArray arrayWithArray:sResult.results];
|
||||
|
||||
ORKToneAudiometryResult *toneResult = [[ORKToneAudiometryResult alloc] initWithIdentifier:(NSString *__nonnull)self.step.identifier];
|
||||
ORKToneAudiometryResult *toneResult = [[ORKToneAudiometryResult alloc] initWithIdentifier:self.step.identifier];
|
||||
toneResult.startDate = sResult.startDate;
|
||||
toneResult.endDate = now;
|
||||
toneResult.samples = [self.samples copy];
|
||||
@@ -159,9 +159,9 @@
|
||||
ORKToneAudiometrySample *sample = [ORKToneAudiometrySample new];
|
||||
NSUInteger frequencyIndex = (self.currentTestIndex / 2);
|
||||
NSNumber *frequency = self.testingFrequencies[frequencyIndex];
|
||||
sample.frequency = frequency;
|
||||
sample.frequency = [frequency doubleValue];
|
||||
sample.channel = ((self.currentTestIndex % 2) == 0) ? ORKAudioChannelLeft : ORKAudioChannelRight;
|
||||
sample.amplitude = @(self.audioGenerator.volumeAmplitude);
|
||||
sample.amplitude = self.audioGenerator.volumeAmplitude;
|
||||
|
||||
[self.samples addObject:sample];
|
||||
|
||||
@@ -180,8 +180,7 @@
|
||||
self.currentTestIndex ++;
|
||||
if (self.currentTestIndex == (self.testingFrequencies.count * 2)) {
|
||||
[self finish];
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
[self startCurrentTest];
|
||||
}
|
||||
}
|
||||
@@ -202,7 +201,7 @@
|
||||
|
||||
CGFloat progress = 0.001 + (CGFloat)testIndex / (self.testingFrequencies.count * 2);
|
||||
[self.toneAudiometryContentView setProgress:progress
|
||||
caption:(channel == ORKAudioChannelLeft) ? [NSString stringWithFormat:ORKLocalizedString(@"TONE_LABEL_%@_LEFT", nil), frequency] : [NSString stringWithFormat:ORKLocalizedString(@"TONE_LABEL_%@_RIGHT", nil), frequency]
|
||||
caption:(channel == ORKAudioChannelLeft) ? [NSString stringWithFormat:ORKLocalizedString(@"TONE_LABEL_%@_LEFT", nil), ORKLocalizedStringFromNumber(frequency)] : [NSString stringWithFormat:ORKLocalizedString(@"TONE_LABEL_%@_RIGHT", nil), ORKLocalizedStringFromNumber(frequency)]
|
||||
animated:YES];
|
||||
|
||||
[self.audioGenerator playSoundAtFrequency:frequency.doubleValue
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
|
||||
@interface ORKTouchRecordingView : UIView
|
||||
|
||||
@property (nonatomic, weak) id<ORKTouchRecordingDelegate> deleagte;
|
||||
@property (nonatomic, weak) id<ORKTouchRecordingDelegate> delegate;
|
||||
|
||||
@end
|
||||
|
||||
@@ -113,10 +113,10 @@
|
||||
}
|
||||
|
||||
- (void)start {
|
||||
if (! _logger) {
|
||||
if (!_logger) {
|
||||
NSError *err = nil;
|
||||
_logger = [self makeJSONDataLoggerWithError:&err];
|
||||
if (! _logger) {
|
||||
if (!_logger) {
|
||||
[self finishRecordingWithError:err];
|
||||
return;
|
||||
}
|
||||
@@ -203,11 +203,6 @@
|
||||
@end
|
||||
|
||||
|
||||
@interface ORKTouchRecorderConfiguration ()
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKTouchRecorderConfiguration
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
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 ORKTowerOfHanoiStep : ORKActiveStep
|
||||
|
||||
/**
|
||||
The number of disks in the puzzle.
|
||||
It is not recommended that you use a large number of disks. As this provides a poor user experience.
|
||||
The default value of this property is 3.
|
||||
*/
|
||||
@property (nonatomic, assign) NSUInteger numberOfDisks;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
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 "ORKTowerOfHanoiStep.h"
|
||||
#import "ORKTowerOfHanoiStepViewController.h"
|
||||
#import "ORKHelpers.h"
|
||||
|
||||
|
||||
static const NSUInteger MaximumNumberOfDisks = 8;
|
||||
|
||||
@implementation ORKTowerOfHanoiStep
|
||||
|
||||
+ (Class)stepViewControllerClass {
|
||||
return [ORKTowerOfHanoiViewController class];
|
||||
}
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
||||
self = [super initWithIdentifier:identifier];
|
||||
if (self) {
|
||||
[self commonInit];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
[self commonInit];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)commonInit {
|
||||
self.optional = YES;
|
||||
self.numberOfDisks = 3;
|
||||
}
|
||||
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [super initWithCoder:aDecoder];
|
||||
if (self) {
|
||||
ORK_DECODE_INTEGER(aDecoder, numberOfDisks);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
[super encodeWithCoder:aCoder];
|
||||
ORK_ENCODE_INTEGER(aCoder, numberOfDisks);
|
||||
}
|
||||
|
||||
- (instancetype)copyWithZone:(NSZone *)zone {
|
||||
ORKTowerOfHanoiStep *step = [super copyWithZone:zone];
|
||||
step.numberOfDisks = self.numberOfDisks;
|
||||
return step;
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object {
|
||||
BOOL isParentSame = [super isEqual:object];
|
||||
|
||||
__typeof(self) castObject = object;
|
||||
return (isParentSame &&
|
||||
(self.numberOfDisks == castObject.numberOfDisks));
|
||||
}
|
||||
|
||||
- (void)validateParameters {
|
||||
[super validateParameters];
|
||||
if (self.numberOfDisks > MaximumNumberOfDisks) {
|
||||
ORK_Log_Warning(@"Having a large number of disks provides a poor user experience, consider reducing the number below %@.", @(MaximumNumberOfDisks));
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)allowsBackNavigation {
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)startsFinished {
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)shouldContinueOnFinish {
|
||||
return YES;
|
||||
}
|
||||
|
||||
@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 ORKTowerOfHanoiViewController : ORKActiveStepViewController
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,357 @@
|
||||
/*
|
||||
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 "ORKTowerOfHanoiStepViewController.h"
|
||||
#import "ORKActiveStepViewController_Internal.h"
|
||||
#import "ORKTowerOfHanoiTowerView.h"
|
||||
#import "ORKActiveStepView.h"
|
||||
#import "ORKTowerOfHanoiTower.h"
|
||||
#import "ORKTowerOfHanoiStep.h"
|
||||
#import "ORKSkin.h"
|
||||
|
||||
|
||||
static const NSUInteger NumberOfTowers = 3;
|
||||
|
||||
@interface ORKTowerOfHanoiViewController () <ORKTowerOfHanoiTowerViewDataSource, ORKTowerOfHanoiTowerViewDelegate>
|
||||
|
||||
@property (nonatomic, strong) NSDateComponentsFormatter *dateComponentsFormatter;
|
||||
@property (nonatomic, strong) NSMutableArray *moves;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKTowerOfHanoiViewController {
|
||||
ORKActiveStepCustomView *_towerOfHanoiCustomView;
|
||||
NSNumber *_selectedIndex;
|
||||
NSArray *_variableConstraints;
|
||||
NSMutableArray *_towers;
|
||||
NSArray *_towerViews;
|
||||
NSTimer *_timer;
|
||||
NSInteger _secondsElapsed;
|
||||
NSDate *_firstMoveDate;
|
||||
}
|
||||
|
||||
#pragma mark - UIViewController
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
_towerOfHanoiCustomView = [ORKActiveStepCustomView new];
|
||||
_towerOfHanoiCustomView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
self.activeStepView.activeCustomView = _towerOfHanoiCustomView;
|
||||
self.activeStepView.minimumStepHeaderHeight = ORKGetMetricForWindow(ORKScreenMetricMinimumStepHeaderHeightForTowerOfHanoiPuzzle, self.view.window);
|
||||
|
||||
[self setUpTowers];
|
||||
[self setUpTowerViews];
|
||||
[self reloadData];
|
||||
NSString *title = ORKLocalizedString(@"TOWER_OF_HANOI_TASK_ACTIVE_STEP_INTRO_TEXT",nil);
|
||||
NSString *text = ORKLocalizedString(@"TOWER_OF_HANOI_TASK_INTRO_TEXT",nil);
|
||||
NSString *skip = ORKLocalizedString(@"TOWER_OF_HANOI_TASK_ACTIVE_STEP_SKIP_BUTTON_TITLE", nil);
|
||||
[self.activeStepView updateTitle:title text:text];
|
||||
[self setSkipButtonTitle:skip];
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
[super viewWillDisappear:animated];
|
||||
[_timer invalidate];
|
||||
}
|
||||
|
||||
- (void)updateViewConstraints {
|
||||
[super updateViewConstraints];
|
||||
|
||||
[NSLayoutConstraint deactivateConstraints:_variableConstraints];
|
||||
_variableConstraints = nil;
|
||||
|
||||
if (!_variableConstraints) {
|
||||
_variableConstraints = [NSMutableArray new];
|
||||
}
|
||||
|
||||
BOOL needCompactLayout =
|
||||
(self.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact) &&
|
||||
(self.traitCollection.verticalSizeClass != UIUserInterfaceSizeClassCompact);
|
||||
_variableConstraints = needCompactLayout ? [self compactConstraints] : [self regularConstraints];
|
||||
[NSLayoutConstraint activateConstraints:_variableConstraints];
|
||||
}
|
||||
|
||||
#pragma mark - ORKStepViewController
|
||||
|
||||
- (void)skipForward {
|
||||
[self finish];
|
||||
}
|
||||
|
||||
#pragma mark - ORKActiveTaskViewController
|
||||
|
||||
- (ORKResult *)result {
|
||||
ORKStepResult *stepResult = [super result];
|
||||
ORKTowerOfHanoiResult *result = [[ORKTowerOfHanoiResult alloc] initWithIdentifier:self.step.identifier];
|
||||
result.moves = self.moves;
|
||||
result.puzzleWasSolved = [self puzzleIsSolved];
|
||||
if (_firstMoveDate != nil) {
|
||||
result.startDate = _firstMoveDate;
|
||||
}
|
||||
stepResult.results = @[result];
|
||||
return stepResult;
|
||||
}
|
||||
|
||||
#pragma mark - ORKTowerOfHanoiTowerViewDataSource
|
||||
|
||||
- (NSUInteger)numberOfDisksInTowerOfHanoiView:(ORKTowerOfHanoiTowerView *)towerView {
|
||||
NSInteger towerIndex = [_towerViews indexOfObject:towerView];
|
||||
ORKTowerOfHanoiTower *tower = _towers[towerIndex];
|
||||
return tower.disks.count;
|
||||
}
|
||||
|
||||
- (NSNumber *)towerOfHanoiView:(ORKTowerOfHanoiTowerView *)towerView diskAtIndex:(NSUInteger)index {
|
||||
NSInteger towerIndex = [_towerViews indexOfObject:towerView];
|
||||
ORKTowerOfHanoiTower *tower = _towers[towerIndex];
|
||||
return (index >= tower.disks.count) ? nil :tower.disks[index];
|
||||
}
|
||||
|
||||
#pragma mark - ORKTowerOfHanoiTowerViewDelegate
|
||||
|
||||
- (void)towerOfHanoiTowerViewWasSelected:(ORKTowerOfHanoiTowerView *)towerView {
|
||||
NSInteger newSelectedIndex = [_towerViews indexOfObject:towerView];
|
||||
if (_selectedIndex == nil) {
|
||||
_selectedIndex = @(newSelectedIndex);
|
||||
}
|
||||
else if ([_selectedIndex isEqual:@(newSelectedIndex)]) {
|
||||
_selectedIndex = nil;
|
||||
} else {
|
||||
[self transferDiskFromTowerAtIndex:_selectedIndex.integerValue toTowerAtIndex:newSelectedIndex];
|
||||
}
|
||||
[self reloadData];
|
||||
[self evaluatePuzzle];
|
||||
}
|
||||
|
||||
- (ORKTowerOfHanoiTowerView *)towerOfHanoiHighlightedTowerView {
|
||||
return (_selectedIndex != nil ? _towerViews[_selectedIndex.integerValue] : nil);
|
||||
}
|
||||
|
||||
#pragma mark - ORKTowerOfHanoiViewController
|
||||
|
||||
- (NSMutableArray *)moves {
|
||||
if (_moves == nil) {
|
||||
_moves = [NSMutableArray array];
|
||||
}
|
||||
return _moves;
|
||||
}
|
||||
|
||||
- (NSDateComponentsFormatter *)dateComponentsFormatter {
|
||||
if (_dateComponentsFormatter == nil) {
|
||||
_dateComponentsFormatter = [NSDateComponentsFormatter new];
|
||||
_dateComponentsFormatter.unitsStyle = NSDateComponentsFormatterUnitsStylePositional;
|
||||
_dateComponentsFormatter.zeroFormattingBehavior = NSDateComponentsFormatterZeroFormattingBehaviorPad;
|
||||
_dateComponentsFormatter.allowedUnits = NSCalendarUnitMinute | NSCalendarUnitSecond;
|
||||
}
|
||||
return _dateComponentsFormatter;
|
||||
}
|
||||
|
||||
- (NSUInteger)numberOfDisks {
|
||||
return ((ORKTowerOfHanoiStep *)self.step).numberOfDisks;
|
||||
}
|
||||
|
||||
- (void)updateTitleText {
|
||||
NSString *moves = ORKLocalizedStringFromNumber(@(self.moves.count));
|
||||
NSString *time = [self.dateComponentsFormatter stringFromTimeInterval:_secondsElapsed];
|
||||
NSString *title = ORKLocalizedString(@"TOWER_OF_HANOI_TASK_ACTIVE_STEP_INTRO_TEXT",nil);
|
||||
NSString *text = [NSString stringWithFormat:ORKLocalizedString(@"TOWER_OF_HANOI_TASK_ACTIVE_STEP_PROGRESS_TEXT", nil), moves, time];
|
||||
[self.activeStepView updateTitle:title text:text];
|
||||
}
|
||||
|
||||
- (void)reloadData {
|
||||
for (ORKTowerOfHanoiTowerView *towerView in _towerViews) {
|
||||
towerView.highlighted = _selectedIndex != nil && [_towerViews indexOfObject:towerView] == _selectedIndex.integerValue;
|
||||
[towerView reloadData];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)puzzleIsSolved {
|
||||
return ((ORKTowerOfHanoiTower *)_towers.lastObject).disks.count == [self numberOfDisks];
|
||||
}
|
||||
|
||||
- (void)evaluatePuzzle {
|
||||
if ([self puzzleIsSolved]) {
|
||||
[self finish];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setUpTowers {
|
||||
NSMutableArray *diskStack = [NSMutableArray array];
|
||||
for (NSInteger disk = [self numberOfDisks] ; disk > 0 ; disk--) {
|
||||
[diskStack addObject: @(disk)];
|
||||
}
|
||||
_towers = [@[[[ORKTowerOfHanoiTower alloc] initWithDisks:diskStack], [ORKTowerOfHanoiTower emptyTower], [ORKTowerOfHanoiTower emptyTower]] mutableCopy];
|
||||
}
|
||||
|
||||
- (void)setUpTowerViews {
|
||||
NSMutableArray *towerViews = [NSMutableArray array];
|
||||
for (NSInteger index = 0 ; index < 3 ; index++) {
|
||||
ORKTowerOfHanoiTowerView *towerView = [[ORKTowerOfHanoiTowerView alloc] initWithFrame:CGRectZero maximumNumberOfDisks:[self numberOfDisks]];
|
||||
towerView.delegate = self;
|
||||
towerView.dataSource = self;
|
||||
towerView.targeted = (index == NumberOfTowers - 1);
|
||||
[towerViews addObject:towerView];
|
||||
towerView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[_towerOfHanoiCustomView addSubview:towerView];
|
||||
}
|
||||
_towerViews = towerViews;
|
||||
}
|
||||
|
||||
- (void)transferDiskFromTowerAtIndex:(NSInteger)donorTowerIndex toTowerAtIndex:(NSInteger)recipientTowerIndex {
|
||||
ORKTowerOfHanoiTower *donorTower = _towers[donorTowerIndex];
|
||||
ORKTowerOfHanoiTower *recipientTower = _towers[recipientTowerIndex];
|
||||
if ([recipientTower recieveDiskFrom:donorTower]) {
|
||||
[self makeMoveFromTowerAtIndex:donorTowerIndex toTowerAtIndex:recipientTowerIndex];
|
||||
} else {
|
||||
NSNumber *donorSize = [self towerOfHanoiView:_towerViews[donorTowerIndex] diskAtIndex:0];
|
||||
NSNumber *recipientSize = [self towerOfHanoiView:_towerViews[recipientTowerIndex] diskAtIndex:0];
|
||||
|
||||
if (donorSize && recipientSize) {
|
||||
// Only announce if the both donor and recipient are valid
|
||||
NSString *invalidMoveAnnouncement = [NSString stringWithFormat:ORKLocalizedString(@"AX_TOWER_OF_HANOI_INVALID_MOVE_FORMAT", nil), donorSize.stringValue, recipientSize.stringValue];
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
|
||||
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, invalidMoveAnnouncement);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)makeMoveFromTowerAtIndex:(NSUInteger)donorTowerIndex toTowerAtIndex:(NSUInteger)recipientTowerIndex {
|
||||
ORKTowerOfHanoiMove *move = [[ORKTowerOfHanoiMove alloc] init];
|
||||
move.donorTowerIndex = donorTowerIndex;
|
||||
move.recipientTowerIndex = recipientTowerIndex;
|
||||
move.timestamp = (self.moves.count == 0) ? 0 : fabs([_firstMoveDate timeIntervalSinceNow]);
|
||||
[_moves addObject:move];
|
||||
[self didMakeMove];
|
||||
}
|
||||
|
||||
- (void)didMakeMove {
|
||||
_selectedIndex = nil;
|
||||
if (self.moves.count == 1) {
|
||||
_firstMoveDate = [NSDate date];
|
||||
_timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerTicked) userInfo:nil repeats:YES];
|
||||
}
|
||||
[self updateTitleText];
|
||||
}
|
||||
|
||||
- (void)timerTicked {
|
||||
_secondsElapsed++;
|
||||
[self updateTitleText];
|
||||
}
|
||||
|
||||
- (NSArray *)compactConstraints {
|
||||
CGFloat compactWidth = ([[UIScreen mainScreen]bounds].size.height - (3 * 8)) / 3;
|
||||
NSDictionary *views = @{ @"A" : _towerViews[0], @"B" : _towerViews[1], @"C" : _towerViews[2]};
|
||||
NSMutableArray *newConstraints = [NSMutableArray new];
|
||||
|
||||
[newConstraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:@"V:|-[A]-[B]-[C]-|"]
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil
|
||||
views:views]];
|
||||
[newConstraints addObject:[NSLayoutConstraint constraintWithItem:_towerViews[0]
|
||||
attribute:NSLayoutAttributeHeight
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_towerViews[1]
|
||||
attribute:NSLayoutAttributeHeight
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
[newConstraints addObject:[NSLayoutConstraint constraintWithItem:_towerViews[2]
|
||||
attribute:NSLayoutAttributeHeight
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_towerViews[1]
|
||||
attribute:NSLayoutAttributeHeight
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
|
||||
for (int index = 0 ; index < _towerViews.count ; index++) {
|
||||
[newConstraints addObject:[NSLayoutConstraint constraintWithItem:_towerViews[index]
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1.0
|
||||
constant:compactWidth]];
|
||||
[newConstraints addObject:[NSLayoutConstraint constraintWithItem:_towerViews[index]
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_towerOfHanoiCustomView
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
}
|
||||
|
||||
return newConstraints;
|
||||
}
|
||||
|
||||
- (NSArray *)regularConstraints {
|
||||
NSDictionary *views = @{ @"A" : _towerViews[0], @"B" : _towerViews[1], @"C" : _towerViews[2]};
|
||||
NSMutableArray *newConstraints = [NSMutableArray new];
|
||||
|
||||
[newConstraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[A]-|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil
|
||||
views:views]];
|
||||
[newConstraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[B]-|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil
|
||||
views:views]];
|
||||
[newConstraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[C]-|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil
|
||||
views:views]];
|
||||
[newConstraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[A]-[B]-[C]-|"
|
||||
options:(NSLayoutFormatOptions)0
|
||||
metrics:nil
|
||||
views:views]];
|
||||
|
||||
[newConstraints addObject:[NSLayoutConstraint constraintWithItem:_towerViews[0]
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_towerViews[1]
|
||||
attribute:NSLayoutAttributeWidth
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
[newConstraints addObject:[NSLayoutConstraint constraintWithItem:_towerViews[2]
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_towerViews[1]
|
||||
attribute:NSLayoutAttributeWidth
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
|
||||
return newConstraints;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
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 <Foundation/Foundation.h>
|
||||
|
||||
@interface ORKTowerOfHanoiTower : NSObject
|
||||
|
||||
@property(nonatomic, copy, readonly) NSArray *disks;
|
||||
|
||||
+ (instancetype)emptyTower;
|
||||
|
||||
- (instancetype)initWithDisks:(NSArray *)disks;
|
||||
|
||||
- (BOOL)recieveDiskFrom:(ORKTowerOfHanoiTower *)donorTower;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
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 "ORKTowerOfHanoiTower.h"
|
||||
|
||||
@interface ORKTowerOfHanoiTower ()
|
||||
|
||||
@property(nonatomic, copy, readwrite) NSArray *disks;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ORKTowerOfHanoiTower
|
||||
|
||||
+ (instancetype)emptyTower {
|
||||
return [[ORKTowerOfHanoiTower alloc]initWithDisks:@[]];
|
||||
}
|
||||
|
||||
- (instancetype)initWithDisks:(NSArray *)disks {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_disks = disks;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)canRecieveDisk:(NSNumber *)disk {
|
||||
return _disks.count == 0 || ((NSNumber *)_disks.lastObject).integerValue > disk.integerValue;
|
||||
}
|
||||
|
||||
- (BOOL)recieveDiskFrom:(ORKTowerOfHanoiTower*)donorTower {
|
||||
if (donorTower.disks.count == 0 || ![self canRecieveDisk:donorTower.disks.lastObject]) {
|
||||
return NO;
|
||||
}
|
||||
NSMutableArray *recipientDisks = [self.disks mutableCopy];
|
||||
NSMutableArray *donorDisks = [donorTower.disks mutableCopy];
|
||||
[recipientDisks addObject:donorTower.disks.lastObject];
|
||||
[donorDisks removeLastObject];
|
||||
self.disks = recipientDisks;
|
||||
donorTower.disks = donorDisks;
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
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 <UIKit/UIKit.h>
|
||||
|
||||
@class ORKTowerOfHanoiTowerView;
|
||||
|
||||
@protocol ORKTowerOfHanoiTowerViewDataSource <NSObject>
|
||||
|
||||
@required
|
||||
- (NSUInteger)numberOfDisksInTowerOfHanoiView:(ORKTowerOfHanoiTowerView *)towerView;
|
||||
- (NSNumber *)towerOfHanoiView:(ORKTowerOfHanoiTowerView *)towerView diskAtIndex:(NSUInteger)index;
|
||||
|
||||
@end
|
||||
|
||||
@protocol ORKTowerOfHanoiTowerViewDelegate <NSObject>
|
||||
|
||||
@required
|
||||
- (void)towerOfHanoiTowerViewWasSelected:(ORKTowerOfHanoiTowerView *)towerView;
|
||||
- (ORKTowerOfHanoiTowerView *)towerOfHanoiHighlightedTowerView;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface ORKTowerOfHanoiTowerView : UIView
|
||||
|
||||
@property (nonatomic, getter=isHighLighted) BOOL highlighted;
|
||||
@property (nonatomic, getter=isTargeted) BOOL targeted;
|
||||
@property (nonatomic, weak) id <ORKTowerOfHanoiTowerViewDataSource> dataSource;
|
||||
@property (nonatomic, weak) id <ORKTowerOfHanoiTowerViewDelegate> delegate;
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame maximumNumberOfDisks:(NSUInteger)maximumNumberOfDisks;
|
||||
|
||||
- (void)reloadData;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,273 @@
|
||||
/*
|
||||
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 "ORKTowerOfHanoiTowerView.h"
|
||||
#import "ORKActiveStepView.h"
|
||||
#import "ORKSkin.h"
|
||||
#import "ORKAccessibility.h"
|
||||
#import "ORKHelpers.h"
|
||||
|
||||
|
||||
static const CGFloat DiskHeight = 10;
|
||||
static const CGFloat DiskSpacing = 8;
|
||||
static const CGFloat BaseSpacing = 10;
|
||||
|
||||
@implementation ORKTowerOfHanoiTowerView {
|
||||
NSInteger _maximumNumberOfDisks;
|
||||
UIView *_base;
|
||||
NSMutableArray *_diskViews;
|
||||
NSMutableArray *_diskSizes;
|
||||
NSMutableArray *_variableConstraints;
|
||||
}
|
||||
|
||||
#pragma mark - Init
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame maximumNumberOfDisks:(NSUInteger)maximumNumberOfDisks {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
_maximumNumberOfDisks = maximumNumberOfDisks;
|
||||
_base = [[UIView alloc] initWithFrame:CGRectZero];
|
||||
_base.backgroundColor = [UIColor ork_midGrayTintColor];
|
||||
_base.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
_base.layer.cornerRadius = 2.5;
|
||||
_base.layer.masksToBounds = YES;
|
||||
_diskViews = [NSMutableArray new];
|
||||
_diskSizes = [NSMutableArray new];
|
||||
[self addSubview:_base];
|
||||
[self addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(userDidTapTower)]];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - UIView
|
||||
|
||||
- (void)updateConstraints {
|
||||
[NSLayoutConstraint deactivateConstraints:_variableConstraints];
|
||||
[_variableConstraints removeAllObjects];
|
||||
|
||||
if (!_variableConstraints) {
|
||||
_variableConstraints = [NSMutableArray new];
|
||||
}
|
||||
|
||||
CGFloat height = (DiskHeight * _maximumNumberOfDisks) + (DiskSpacing * _maximumNumberOfDisks);
|
||||
[_variableConstraints addObject:[NSLayoutConstraint constraintWithItem:self
|
||||
attribute:NSLayoutAttributeHeight
|
||||
relatedBy:NSLayoutRelationGreaterThanOrEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1.0
|
||||
constant:height + BaseSpacing]];
|
||||
|
||||
[_variableConstraints addObject:[NSLayoutConstraint constraintWithItem:_base
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeWidth
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
|
||||
[_variableConstraints addObject:[NSLayoutConstraint constraintWithItem:_base
|
||||
attribute:NSLayoutAttributeHeight
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1.0
|
||||
constant:2.0]];
|
||||
|
||||
[_variableConstraints addObject:[NSLayoutConstraint constraintWithItem:_base
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
|
||||
[_variableConstraints addObject:[NSLayoutConstraint constraintWithItem:_base
|
||||
attribute:NSLayoutAttributeBottom
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeCenterY
|
||||
multiplier:1.0
|
||||
constant:(height * 0.5) + BaseSpacing]];
|
||||
|
||||
UIView *topDisk;
|
||||
for (NSInteger index = 0 ; index < _diskSizes.count ; index++) {
|
||||
UIView *disk = _diskViews[index];
|
||||
CGFloat divide = 1.0 / _maximumNumberOfDisks;
|
||||
CGFloat multiply = ((NSNumber *)_diskSizes[index]).floatValue * divide;
|
||||
|
||||
[_variableConstraints addObject:[NSLayoutConstraint constraintWithItem:disk
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
|
||||
[_variableConstraints addObject:[NSLayoutConstraint constraintWithItem:disk
|
||||
attribute:NSLayoutAttributeWidth
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:_base
|
||||
attribute:NSLayoutAttributeWidth
|
||||
multiplier:multiply
|
||||
constant:0.0]];
|
||||
|
||||
[_variableConstraints addObject:[NSLayoutConstraint constraintWithItem:disk
|
||||
attribute:NSLayoutAttributeHeight
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:nil
|
||||
attribute:NSLayoutAttributeNotAnAttribute
|
||||
multiplier:1.0
|
||||
constant:DiskHeight]];
|
||||
|
||||
if (index == 0) {
|
||||
[_variableConstraints addObject:[NSLayoutConstraint constraintWithItem:disk
|
||||
attribute:NSLayoutAttributeBottom
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeCenterY
|
||||
multiplier:1.0
|
||||
constant:height * 0.5]];
|
||||
} else {
|
||||
[_variableConstraints addObject:[NSLayoutConstraint constraintWithItem:disk
|
||||
attribute:NSLayoutAttributeBottom
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:topDisk
|
||||
attribute:NSLayoutAttributeTop
|
||||
multiplier:1.0
|
||||
constant:-DiskSpacing]];
|
||||
}
|
||||
topDisk = disk;
|
||||
}
|
||||
[NSLayoutConstraint activateConstraints:_variableConstraints];
|
||||
[super updateConstraints];
|
||||
}
|
||||
|
||||
- (void)tintColorDidChange {
|
||||
[self reloadData];
|
||||
}
|
||||
|
||||
#pragma mark - Public
|
||||
|
||||
- (void)reloadData {
|
||||
[self updateDisks];
|
||||
[self highlightIfNeeded];
|
||||
[self indicateTargetIfNeeded];
|
||||
[self setNeedsUpdateConstraints];
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
- (void)userDidTapTower {
|
||||
[self.delegate towerOfHanoiTowerViewWasSelected:self];
|
||||
}
|
||||
|
||||
- (void)updateDisks {
|
||||
[_diskViews makeObjectsPerformSelector:@selector(removeFromSuperview)];
|
||||
ORKRemoveConstraintsForRemovedViews(_variableConstraints, _diskViews);
|
||||
|
||||
[_diskViews removeAllObjects];
|
||||
[_diskSizes removeAllObjects];
|
||||
|
||||
NSInteger numberOfDisks = [self.dataSource numberOfDisksInTowerOfHanoiView:self];
|
||||
for (NSInteger index = 0 ; index < numberOfDisks ; index++) {
|
||||
NSNumber *diskSize = [self.dataSource towerOfHanoiView:self diskAtIndex:index];
|
||||
[_diskSizes addObject:diskSize];
|
||||
UIView *diskView = [[UIView alloc] initWithFrame:CGRectZero];
|
||||
diskView.backgroundColor = [self tintColor];
|
||||
diskView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
diskView.layer.cornerRadius = DiskHeight * 0.5;
|
||||
diskView.clipsToBounds = YES;
|
||||
[self addSubview:diskView];
|
||||
[_diskViews addObject:diskView];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)highlightIfNeeded {
|
||||
if (self.isHighLighted) {
|
||||
((UIView *)_diskViews.lastObject).alpha = 0.2;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)indicateTargetIfNeeded {
|
||||
if (self.isTargeted) {
|
||||
_base.backgroundColor = [self tintColor];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Accessibility
|
||||
|
||||
- (BOOL)isAccessibilityElement {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSString *)accessibilityLabel {
|
||||
NSString *targetDisk = (self.isTargeted ? ORKLocalizedString(@"AX_TOWER_OF_HANOI_TARGET_DISK", nil) : nil);
|
||||
return ORKAccessibilityStringForVariables(ORKLocalizedString(@"AX_TOWER_OF_HANOI_TOWER", nil), targetDisk);
|
||||
}
|
||||
|
||||
- (NSString *)accessibilityHint {
|
||||
if (!self.isHighLighted && [self.delegate towerOfHanoiHighlightedTowerView] != nil) {
|
||||
return ORKLocalizedString(@"AX_TOWER_OF_HANOI_PLACE_DISK", nil);
|
||||
}
|
||||
|
||||
BOOL hasDisks = ([self.dataSource numberOfDisksInTowerOfHanoiView:self] > 0);
|
||||
return (self.isHighLighted ? nil : (hasDisks ? ORKLocalizedString(@"AX_TOWER_OF_HANOI_SELECT_DISK", nil) : nil));
|
||||
}
|
||||
|
||||
- (UIAccessibilityTraits)accessibilityTraits {
|
||||
UIAccessibilityTraits traits = [super accessibilityTraits];
|
||||
if (self.isHighLighted) {
|
||||
traits |= UIAccessibilityTraitSelected;
|
||||
}
|
||||
|
||||
// Don't echo if when a disk is placed.
|
||||
if (!self.isHighLighted && UIAccessibilityFocusedElement(UIAccessibilityNotificationVoiceOverIdentifier) == self) {
|
||||
traits |= UIAccessibilityTraitStartsMediaSession;
|
||||
}
|
||||
|
||||
return traits;
|
||||
}
|
||||
|
||||
- (NSString *)accessibilityValue {
|
||||
|
||||
NSString *disksString = @"";
|
||||
|
||||
for (NSNumber *diskSize in _diskSizes) {
|
||||
disksString = ORKAccessibilityStringForVariables(disksString, diskSize.stringValue, @", ");
|
||||
}
|
||||
|
||||
NSString *value = (_diskSizes.count > 0 ? [NSString stringWithFormat:ORKLocalizedString(@"AX_TOWER_OF_HANOI_TOWER_CONTAINS", nil), disksString] : ORKLocalizedString(@"AX_TOWER_OF_HANOI_TOWER_EMPTY", nil));
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -72,7 +72,7 @@
|
||||
|
||||
AVSpeechUtterance *utterance = [[AVSpeechUtterance alloc] initWithString:text];
|
||||
float speechRate = AVSpeechUtteranceDefaultSpeechRate;
|
||||
if (! [[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){.majorVersion = 9, .minorVersion = 0, .patchVersion = 0}]) {
|
||||
if (![[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){.majorVersion = 9, .minorVersion = 0, .patchVersion = 0}]) {
|
||||
speechRate = AVSpeechUtteranceDefaultSpeechRate / 2.5;
|
||||
}
|
||||
utterance.rate = speechRate;
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
|
||||
if ( self.numberOfStepsPerLeg < ORKShortWalkTaskMinimumNumberOfStepsPerLeg) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException
|
||||
reason:[NSString stringWithFormat:@"numberOfStepsPerLeg can not be less than %@.", @(ORKShortWalkTaskMinimumNumberOfStepsPerLeg)]
|
||||
reason:[NSString stringWithFormat:@"numberOfStepsPerLeg cannot be less than %@.", @(ORKShortWalkTaskMinimumNumberOfStepsPerLeg)]
|
||||
userInfo:nil];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,173 +41,25 @@
|
||||
#import "ORKWalkingTaskStep.h"
|
||||
#import "ORKPedometerRecorder.h"
|
||||
#import "ORKActiveStepView.h"
|
||||
|
||||
|
||||
static const CGFloat kProgressCircleDiameter = 10;
|
||||
static const CGFloat kProgressCircleSpacing = 4;
|
||||
|
||||
@interface ORKWalkingProgressCircleView : UIView
|
||||
|
||||
@property (nonatomic, assign) BOOL completed;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKWalkingProgressCircleView
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self setCompleted:NO];
|
||||
self.backgroundColor = [self tintColor];
|
||||
self.layer.cornerRadius = kProgressCircleDiameter/2;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)tintColorDidChange {
|
||||
[super tintColorDidChange];
|
||||
self.backgroundColor = [self tintColor];
|
||||
}
|
||||
|
||||
- (CGSize)intrinsicContentSize {
|
||||
return (CGSize){kProgressCircleDiameter,kProgressCircleDiameter};
|
||||
}
|
||||
|
||||
- (CGSize)sizeThatFits:(CGSize)size {
|
||||
return (CGSize){kProgressCircleDiameter,kProgressCircleDiameter};
|
||||
}
|
||||
|
||||
- (void)setCompleted:(BOOL)completed {
|
||||
_completed = completed;
|
||||
self.alpha = (completed ? 1.0 : 0.6);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface ORKWalkingProgressView : UIView
|
||||
|
||||
@property (nonatomic, assign) NSInteger count;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKWalkingProgressView {
|
||||
NSArray *_circles;
|
||||
NSInteger _index;
|
||||
NSTimer *_timer;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
self.count = 3;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[_timer invalidate];
|
||||
_timer = nil;
|
||||
}
|
||||
|
||||
- (void)setCount:(NSInteger)count {
|
||||
_count = count;
|
||||
if (count != [_circles count]) {
|
||||
for (UIView *v in _circles) {
|
||||
[v removeFromSuperview];
|
||||
}
|
||||
NSMutableArray *newCircles = [NSMutableArray array];
|
||||
for (NSInteger idx = 0; idx < count; idx ++) {
|
||||
ORKWalkingProgressCircleView *circle = [ORKWalkingProgressCircleView new];
|
||||
[newCircles addObject:circle];
|
||||
[self addSubview:circle];
|
||||
}
|
||||
|
||||
_circles = newCircles;
|
||||
[self invalidateIntrinsicContentSize];
|
||||
[self setNeedsLayout];
|
||||
self.index = _index;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setIndex:(NSInteger)index {
|
||||
_index = index;
|
||||
[_circles enumerateObjectsUsingBlock:^(ORKWalkingProgressCircleView *circle, NSUInteger idx, BOOL *stop) {
|
||||
circle.completed = (idx < _index);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)didMoveToWindow {
|
||||
if (self.window) {
|
||||
[self startAnimating];
|
||||
} else {
|
||||
[self stopAnimating];
|
||||
}
|
||||
}
|
||||
- (void)stopAnimating {
|
||||
[_timer invalidate];
|
||||
_timer = nil;
|
||||
}
|
||||
|
||||
- (void)incrementIndex {
|
||||
self.index = (_index + 1) % (_count + 1);
|
||||
}
|
||||
|
||||
- (void)startAnimating {
|
||||
[self stopAnimating];
|
||||
self.index = 0;
|
||||
_timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(incrementIndex) userInfo:nil repeats:YES];
|
||||
}
|
||||
|
||||
- (CGSize)sizeThatFits:(CGSize)size {
|
||||
size.height = kProgressCircleDiameter;
|
||||
size.width = (_count * kProgressCircleDiameter) + MAX(_count-1,0) * kProgressCircleSpacing;
|
||||
return size;
|
||||
}
|
||||
|
||||
- (CGSize)intrinsicContentSize {
|
||||
return [self sizeThatFits:CGSizeZero];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
CGSize sz = (CGSize){kProgressCircleDiameter,kProgressCircleDiameter};
|
||||
CGFloat xStep = kProgressCircleDiameter + kProgressCircleSpacing;
|
||||
CGFloat x0 = 0;
|
||||
for (UIView *v in _circles) {
|
||||
v.frame = (CGRect){{x0,0},sz};
|
||||
x0 += xStep;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
#import "ORKProgressView.h"
|
||||
|
||||
|
||||
@interface ORKWalkingContentView : ORKActiveStepCustomView {
|
||||
ORKScreenType _screenType;
|
||||
NSLayoutConstraint *_topConstraint;
|
||||
}
|
||||
|
||||
@property (nonatomic, strong, readonly) ORKWalkingProgressView *progressView;
|
||||
@property (nonatomic, strong, readonly) ORKProgressView *progressView;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKWalkingContentView
|
||||
|
||||
- (void)willMoveToWindow:(UIWindow *)newWindow {
|
||||
[super willMoveToWindow:newWindow];
|
||||
_screenType = ORKGetScreenTypeForWindow(newWindow);
|
||||
[self updateConstraintConstants];
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
_progressView = [ORKWalkingProgressView new];
|
||||
_progressView = [ORKProgressView new];
|
||||
_progressView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
_screenType = ORKScreenTypeiPhone4;
|
||||
|
||||
#if LAYOUT_DEBUG
|
||||
self.backgroundColor = [[UIColor cyanColor] colorWithAlphaComponent:0.2];
|
||||
@@ -215,29 +67,53 @@ static const CGFloat kProgressCircleSpacing = 4;
|
||||
#endif
|
||||
|
||||
[self addSubview:_progressView];
|
||||
[self setNeedsUpdateConstraints];
|
||||
|
||||
[self setUpConstraints];
|
||||
[self updateConstraintConstantsForWindow:self.window];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)updateConstraintConstants {
|
||||
|
||||
ORKScreenType screenType = _screenType;
|
||||
- (void)willMoveToWindow:(UIWindow *)newWindow {
|
||||
[super willMoveToWindow:newWindow];
|
||||
[self updateConstraintConstantsForWindow:newWindow];
|
||||
}
|
||||
|
||||
- (void)updateConstraintConstantsForWindow:(UIWindow *)window {
|
||||
const CGFloat CaptionBaselineToProgressTop = 100;
|
||||
const CGFloat CaptionBaselineToStepViewTop = ORKGetMetricForScreenType(ORKScreenMetricLearnMoreBaselineToStepViewTop, screenType);
|
||||
[_topConstraint setConstant:(CaptionBaselineToProgressTop - CaptionBaselineToStepViewTop)];
|
||||
const CGFloat CaptionBaselineToStepViewTop = ORKGetMetricForWindow(ORKScreenMetricLearnMoreBaselineToStepViewTop, window);
|
||||
_topConstraint.constant = CaptionBaselineToProgressTop - CaptionBaselineToStepViewTop;
|
||||
}
|
||||
|
||||
- (void)setUpConstraints {
|
||||
NSMutableArray *constraints = [NSMutableArray new];
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(_progressView);
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_progressView]-(>=0)-|"
|
||||
options:NSLayoutFormatAlignAllCenterX
|
||||
metrics:nil
|
||||
views:views]];
|
||||
_topConstraint = [NSLayoutConstraint constraintWithItem:_progressView
|
||||
attribute:NSLayoutAttributeTop
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeTop
|
||||
multiplier:1.0
|
||||
constant:0.0]; // constant will be set in updateConstraintConstantsForWindow:
|
||||
[constraints addObject:_topConstraint];
|
||||
|
||||
[constraints addObject:[NSLayoutConstraint constraintWithItem:_progressView
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:self
|
||||
attribute:NSLayoutAttributeCenterX
|
||||
multiplier:1.0
|
||||
constant:0.0]];
|
||||
|
||||
[NSLayoutConstraint activateConstraints:constraints];
|
||||
}
|
||||
|
||||
- (void)updateConstraints {
|
||||
[self removeConstraints:[self constraints]];
|
||||
NSDictionary *views = NSDictionaryOfVariableBindings(_progressView);
|
||||
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_progressView]-(>=0)-|" options:NSLayoutFormatAlignAllCenterX metrics:nil views:views]];
|
||||
_topConstraint = [NSLayoutConstraint constraintWithItem:_progressView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTop multiplier:1 constant:0];
|
||||
[self updateConstraintConstants];
|
||||
[self addConstraint:_topConstraint];
|
||||
[self addConstraint:[NSLayoutConstraint constraintWithItem:_progressView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]];
|
||||
|
||||
[self updateConstraintConstantsForWindow:self.window];
|
||||
[super updateConstraints];
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user