Compare commits

..

24 Commits

Author SHA1 Message Date
akshay-yadav 29950b62e4 marker 2022-06-15 12:53:26 -07:00
akshay-yadav 65de4b333c README update 2022-06-15 11:44:35 -07:00
akshay-yadav 63fcf4918f Merge branch 'stable' into 'main' for housekeeping 2022-06-15 11:40:04 -07:00
akshay-yadav c9880b0139 merge stable into main
resolve merge conflicts
Bugfixes
Cleanup
merge main back to stable
2022-06-15 11:38:42 -07:00
akshay-yadav 13eabb7720 merging stable back to main
Resolving Merge conflicts
Bugfixes
Cleanup
2022-06-15 11:37:16 -07:00
Pariece McKinney 743b773ea3 Point RK to IOS 13 (#1499) 2022-04-25 09:40:44 -07:00
Pariece McKinney 249eee5dfb IOS15 Fixes (#1487) 2022-02-17 11:29:47 -08:00
aplummer-apple 90c68d0d19 Update project library search paths to compile on apple silicon (#1479)
I believe this line was in here to resolve issues with an early beta of xcode 11 and is unnecessary now
2021-11-29 10:20:27 -08:00
Pariece McKinney d10a427911 Merge pull request #1471 from Pariecemckinney-apple/pmckinney/taskVCDeprecationWarningFix
Fix ORKTaskViewController deprecation warning
2021-10-25 14:50:24 -07:00
Pariecemckinney-apple 05755a3213 initial commit 2021-10-25 11:38:50 -07:00
Corey e18a633de1 Set ORKTaskViewController nav background color (#1469) 2021-10-04 13:58:03 -07:00
Pariece McKinney 0e68cdf744 Merge pull request #1448 from stevemoser/patch-6
Fix broken Apple Forums link
2021-06-28 15:58:31 -07:00
Pariece McKinney 7f119a8d0d Merge pull request #1338 from Hengyu/hengyu
Use implied answer format for cells
2021-06-28 15:54:27 -07:00
hengyu fde1e7e957 Use implied answer format for cells 2021-06-27 14:58:31 +08:00
Erik Hornberger 0ad96d505c Merge pull request #1462 from erik-apple/nullable-return-type
Silence warning about nullable return types
2021-06-21 15:25:36 -07:00
Erik Hornberger d4ff76fc25 Silence warning about nullable return types 2021-06-21 15:08:01 -07:00
Erik Hornberger 85c1395361 Merge pull request #1461 from erik-apple/update-deprecated-method-in-keychain
Update deprecated method call
2021-06-21 14:47:45 -07:00
Erik Hornberger 1443e57c57 Replace deprecated method call 2021-06-21 14:22:10 -07:00
Erik Hornberger 3b75f6213c Updates to the predefined range of motion task (#1459) 2021-05-25 17:05:56 -07:00
gavirawson-apple e9d5de64a5 Clip step image (#1457) 2021-05-19 18:11:15 -07:00
Erik Hornberger 19c61383f6 Permission Type Updates (#1454)
* Ensure that the slider's colors match the view's tint

* New visual style for request permission step

* Add a new notifications permission type

* Add a new motion activity permission type

* Update ORKRequestPermissionButton.m

* Forward declare button

* Update ResearchKit/Common/ORKPermissionType.m

Co-authored-by: joeylabarck-apple <81833193+joeylabarck-apple@users.noreply.github.com>

* Update ResearchKit/Common/ORKPermissionType.h

Co-authored-by: joeylabarck-apple <81833193+joeylabarck-apple@users.noreply.github.com>

* Update ResearchKit/Common/ORKMotionActivityPermissionType.h

Co-authored-by: joeylabarck-apple <81833193+joeylabarck-apple@users.noreply.github.com>

* Update ResearchKit/Common/ORKPermissionType.h

Co-authored-by: joeylabarck-apple <81833193+joeylabarck-apple@users.noreply.github.com>

* Update ResearchKit/Common/ORKPermissionType.h

Co-authored-by: joeylabarck-apple <81833193+joeylabarck-apple@users.noreply.github.com>

* Add missing import

* Add missing import

Co-authored-by: joeylabarck-apple <81833193+joeylabarck-apple@users.noreply.github.com>
2021-04-16 17:02:45 -07:00
Steve Moser cb6cc97fa4 Fix broken Apple Forums link 2021-03-01 08:26:36 -05:00
srinathtm-apple 0651bf0c2a Use wheel date picker style (#1447)
* Use wheel date picker style
2021-02-11 19:16:32 -08:00
akshay-yadav ae9b9e57cb updates 2019-10-24 14:19:09 -07:00
106 changed files with 3053 additions and 605 deletions
+2 -2
View File
@@ -1,5 +1,5 @@
language: objective-c
osx_image: xcode11
osx_image: xcode12
xcode_project: ResearchKit.xcodeproj
xcode_scheme: ResearchKit
xcode_destination: platform=iOS Simulator,OS=13.0,name=iPhone 11 Pro Max
xcode_destination: platform=iOS Simulator,OS=14.0,name=iPhone 11 Pro Max
+2 -2
View File
@@ -16,7 +16,7 @@ for medical research or for other research projects.
Getting More Information
========================
* Join the [*ResearchKit* Forum](https://forums.developer.apple.com/community/researchkit) for discussing uses of the *ResearchKit framework and* related projects.
* Join the [*ResearchKit* Forum](https://developer.apple.com/forums/tags/researchkit) for discussing uses of the *ResearchKit framework and* related projects.
Use Cases
===========
@@ -82,7 +82,7 @@ The latest stable version of *ResearchKit framework* can be cloned with
git clone -b stable https://github.com/ResearchKit/ResearchKit.git
```
Or, for the latest changes, use the `master` branch:
Or, for the latest changes, use the `main` branch:
```
git clone https://github.com/ResearchKit/ResearchKit.git
+132 -42
View File
@@ -22,11 +22,27 @@
/* End PBXAggregateTarget section */
/* Begin PBXBuildFile section */
0B4C04582357B43700719958 /* ORKNormalizedReactionTimeStimulusView.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B4C04572357B43700719958 /* ORKNormalizedReactionTimeStimulusView.m */; };
0B4C045C2357B47700719958 /* ORKNormalizedReactionTimeStimulusView.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B4C045B2357B47700719958 /* ORKNormalizedReactionTimeStimulusView.h */; };
0B4C045E2357B48D00719958 /* ORKNormalizedReactionTimeContentView.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B4C045D2357B48D00719958 /* ORKNormalizedReactionTimeContentView.m */; };
0B4C04602357B49C00719958 /* ORKNormalizedReactionTimeContentView.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B4C045F2357B49C00719958 /* ORKNormalizedReactionTimeContentView.h */; };
0B4C04622357B4B100719958 /* ORKNormalizedReactionTimeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B4C04612357B4B100719958 /* ORKNormalizedReactionTimeViewController.m */; };
0B4C04642357B4BF00719958 /* ORKNormalizedReactionTimeViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B4C04632357B4BF00719958 /* ORKNormalizedReactionTimeViewController.h */; };
0B4C04662357B4CD00719958 /* ORKNormalizedReactionTimeStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B4C04652357B4CD00719958 /* ORKNormalizedReactionTimeStep.m */; };
0B4C04682357B4DA00719958 /* ORKNormalizedReactionTimeStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B4C04672357B4DA00719958 /* ORKNormalizedReactionTimeStep.h */; settings = {ATTRIBUTES = (Public, ); }; };
0B4C046A2357B4ED00719958 /* ORKNormalizedReactionTimeResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B4C04692357B4ED00719958 /* ORKNormalizedReactionTimeResult.m */; };
0B4C046C2357B4FF00719958 /* ORKNormalizedReactionTimeResult.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B4C046B2357B4FF00719958 /* ORKNormalizedReactionTimeResult.h */; settings = {ATTRIBUTES = (Public, ); }; };
00B1F7852241503900D022FE /* Speech.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00B1F7842241503900D022FE /* Speech.framework */; };
00C2668A2302244300337E0B /* ORKCustomStepViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 00C266882302244300337E0B /* ORKCustomStepViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
00C2668B2302244300337E0B /* ORKCustomStepViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 00C266892302244300337E0B /* ORKCustomStepViewController.m */; };
00C2668E23022CD400337E0B /* ORKCustomStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 00C2668C23022CD400337E0B /* ORKCustomStep.h */; settings = {ATTRIBUTES = (Public, ); }; };
00C2668F23022CD400337E0B /* ORKCustomStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 00C2668D23022CD400337E0B /* ORKCustomStep.m */; };
037D9B892629FCD300B83F9D /* ORKRequestPermissionButton.h in Headers */ = {isa = PBXBuildFile; fileRef = 037D9B872629FCD300B83F9D /* ORKRequestPermissionButton.h */; };
037D9B8A2629FCD300B83F9D /* ORKRequestPermissionButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 037D9B882629FCD300B83F9D /* ORKRequestPermissionButton.m */; };
037D9B972629FF7E00B83F9D /* ORKNotificationPermissionType.h in Headers */ = {isa = PBXBuildFile; fileRef = 037D9B952629FF7E00B83F9D /* ORKNotificationPermissionType.h */; settings = {ATTRIBUTES = (Public, ); }; };
037D9B982629FF7E00B83F9D /* ORKNotificationPermissionType.m in Sources */ = {isa = PBXBuildFile; fileRef = 037D9B962629FF7E00B83F9D /* ORKNotificationPermissionType.m */; };
037D9BA7262A031300B83F9D /* ORKMotionActivityPermissionType.h in Headers */ = {isa = PBXBuildFile; fileRef = 037D9BA5262A031300B83F9D /* ORKMotionActivityPermissionType.h */; settings = {ATTRIBUTES = (Public, ); }; };
037D9BA8262A031300B83F9D /* ORKMotionActivityPermissionType.m in Sources */ = {isa = PBXBuildFile; fileRef = 037D9BA6262A031300B83F9D /* ORKMotionActivityPermissionType.m */; };
106FF29E1B663FCE004EACF2 /* ORKHolePegTestPlaceStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 106FF29C1B663FCE004EACF2 /* ORKHolePegTestPlaceStep.h */; settings = {ATTRIBUTES = (Private, ); }; };
106FF29F1B663FCE004EACF2 /* ORKHolePegTestPlaceStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 106FF29D1B663FCE004EACF2 /* ORKHolePegTestPlaceStep.m */; };
106FF2A21B665B86004EACF2 /* ORKHolePegTestPlaceStepViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 106FF2A01B665B86004EACF2 /* ORKHolePegTestPlaceStepViewController.h */; settings = {ATTRIBUTES = (Private, ); }; };
@@ -188,6 +204,7 @@
51751464245A5172009E8FFC /* ORKFrontFacingCameraStepViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 51751462245A5172009E8FFC /* ORKFrontFacingCameraStepViewController.m */; };
51751467245A53D7009E8FFC /* ORKFrontFacingCameraStepContentView.h in Headers */ = {isa = PBXBuildFile; fileRef = 51751465245A53D7009E8FFC /* ORKFrontFacingCameraStepContentView.h */; };
51751468245A53D7009E8FFC /* ORKFrontFacingCameraStepContentView.m in Sources */ = {isa = PBXBuildFile; fileRef = 51751466245A53D7009E8FFC /* ORKFrontFacingCameraStepContentView.m */; };
517B96E127ED310E00C29A00 /* ORKTextButtonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 517B96E027ED310E00C29A00 /* ORKTextButtonTests.swift */; };
5185A799227C960500A570DE /* ORKLandoltCStepContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5185A798227C960500A570DE /* ORKLandoltCStepContentView.swift */; };
5190141924759E6800E3A418 /* ORKFrontFacingCameraStepResult.h in Headers */ = {isa = PBXBuildFile; fileRef = 5190141724759E6800E3A418 /* ORKFrontFacingCameraStepResult.h */; settings = {ATTRIBUTES = (Public, ); }; };
5190141A24759E6800E3A418 /* ORKFrontFacingCameraStepResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 5190141824759E6800E3A418 /* ORKFrontFacingCameraStepResult.m */; };
@@ -955,11 +972,27 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
0B4C04572357B43700719958 /* ORKNormalizedReactionTimeStimulusView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ORKNormalizedReactionTimeStimulusView.m; sourceTree = "<group>"; };
0B4C045B2357B47700719958 /* ORKNormalizedReactionTimeStimulusView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ORKNormalizedReactionTimeStimulusView.h; sourceTree = "<group>"; };
0B4C045D2357B48D00719958 /* ORKNormalizedReactionTimeContentView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ORKNormalizedReactionTimeContentView.m; sourceTree = "<group>"; };
0B4C045F2357B49C00719958 /* ORKNormalizedReactionTimeContentView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ORKNormalizedReactionTimeContentView.h; sourceTree = "<group>"; };
0B4C04612357B4B100719958 /* ORKNormalizedReactionTimeViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ORKNormalizedReactionTimeViewController.m; sourceTree = "<group>"; };
0B4C04632357B4BF00719958 /* ORKNormalizedReactionTimeViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ORKNormalizedReactionTimeViewController.h; sourceTree = "<group>"; };
0B4C04652357B4CD00719958 /* ORKNormalizedReactionTimeStep.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ORKNormalizedReactionTimeStep.m; sourceTree = "<group>"; };
0B4C04672357B4DA00719958 /* ORKNormalizedReactionTimeStep.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ORKNormalizedReactionTimeStep.h; sourceTree = "<group>"; };
0B4C04692357B4ED00719958 /* ORKNormalizedReactionTimeResult.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ORKNormalizedReactionTimeResult.m; sourceTree = "<group>"; };
0B4C046B2357B4FF00719958 /* ORKNormalizedReactionTimeResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ORKNormalizedReactionTimeResult.h; sourceTree = "<group>"; };
00B1F7842241503900D022FE /* Speech.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Speech.framework; path = System/Library/Frameworks/Speech.framework; sourceTree = SDKROOT; };
00C266882302244300337E0B /* ORKCustomStepViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ORKCustomStepViewController.h; sourceTree = "<group>"; };
00C266892302244300337E0B /* ORKCustomStepViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ORKCustomStepViewController.m; sourceTree = "<group>"; };
00C2668C23022CD400337E0B /* ORKCustomStep.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ORKCustomStep.h; sourceTree = "<group>"; };
00C2668D23022CD400337E0B /* ORKCustomStep.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ORKCustomStep.m; sourceTree = "<group>"; };
037D9B872629FCD300B83F9D /* ORKRequestPermissionButton.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ORKRequestPermissionButton.h; sourceTree = "<group>"; };
037D9B882629FCD300B83F9D /* ORKRequestPermissionButton.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ORKRequestPermissionButton.m; sourceTree = "<group>"; };
037D9B952629FF7E00B83F9D /* ORKNotificationPermissionType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ORKNotificationPermissionType.h; sourceTree = "<group>"; };
037D9B962629FF7E00B83F9D /* ORKNotificationPermissionType.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ORKNotificationPermissionType.m; sourceTree = "<group>"; };
037D9BA5262A031300B83F9D /* ORKMotionActivityPermissionType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ORKMotionActivityPermissionType.h; sourceTree = "<group>"; };
037D9BA6262A031300B83F9D /* ORKMotionActivityPermissionType.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ORKMotionActivityPermissionType.m; sourceTree = "<group>"; };
106FF29C1B663FCE004EACF2 /* ORKHolePegTestPlaceStep.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ORKHolePegTestPlaceStep.h; sourceTree = "<group>"; };
106FF29D1B663FCE004EACF2 /* ORKHolePegTestPlaceStep.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ORKHolePegTestPlaceStep.m; sourceTree = "<group>"; };
106FF2A01B665B86004EACF2 /* ORKHolePegTestPlaceStepViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ORKHolePegTestPlaceStepViewController.h; sourceTree = "<group>"; };
@@ -1124,6 +1157,7 @@
51751462245A5172009E8FFC /* ORKFrontFacingCameraStepViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ORKFrontFacingCameraStepViewController.m; sourceTree = "<group>"; };
51751465245A53D7009E8FFC /* ORKFrontFacingCameraStepContentView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ORKFrontFacingCameraStepContentView.h; sourceTree = "<group>"; };
51751466245A53D7009E8FFC /* ORKFrontFacingCameraStepContentView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ORKFrontFacingCameraStepContentView.m; sourceTree = "<group>"; };
517B96E027ED310E00C29A00 /* ORKTextButtonTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ORKTextButtonTests.swift; sourceTree = "<group>"; };
5185A798227C960500A570DE /* ORKLandoltCStepContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ORKLandoltCStepContentView.swift; sourceTree = "<group>"; };
5190141724759E6800E3A418 /* ORKFrontFacingCameraStepResult.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ORKFrontFacingCameraStepResult.h; sourceTree = "<group>"; };
5190141824759E6800E3A418 /* ORKFrontFacingCameraStepResult.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ORKFrontFacingCameraStepResult.m; sourceTree = "<group>"; };
@@ -1939,6 +1973,40 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
0B4C04552357B39200719958 /* Normalized */ = {
isa = PBXGroup;
children = (
0B4C046B2357B4FF00719958 /* ORKNormalizedReactionTimeResult.h */,
0B4C04692357B4ED00719958 /* ORKNormalizedReactionTimeResult.m */,
0B4C04672357B4DA00719958 /* ORKNormalizedReactionTimeStep.h */,
0B4C04652357B4CD00719958 /* ORKNormalizedReactionTimeStep.m */,
0B4C04632357B4BF00719958 /* ORKNormalizedReactionTimeViewController.h */,
0B4C04612357B4B100719958 /* ORKNormalizedReactionTimeViewController.m */,
0B4C045F2357B49C00719958 /* ORKNormalizedReactionTimeContentView.h */,
0B4C045D2357B48D00719958 /* ORKNormalizedReactionTimeContentView.m */,
0B4C045B2357B47700719958 /* ORKNormalizedReactionTimeStimulusView.h */,
0B4C04572357B43700719958 /* ORKNormalizedReactionTimeStimulusView.m */,
);
name = Normalized;
sourceTree = "<group>";
};
0B4C04562357B3A700719958 /* Shake */ = {
isa = PBXGroup;
children = (
FF919A3D1E81AFEF005C2A1E /* ORKReactionTimeResult.h */,
FF919A3E1E81AFEF005C2A1E /* ORKReactionTimeResult.m */,
25ECC0931AFBD68300F3D63B /* ORKReactionTimeStep.h */,
25ECC0941AFBD68300F3D63B /* ORKReactionTimeStep.m */,
25ECC0991AFBD8B300F3D63B /* ORKReactionTimeViewController.h */,
25ECC09A1AFBD8B300F3D63B /* ORKReactionTimeViewController.m */,
25ECC09D1AFBD92D00F3D63B /* ORKReactionTimeContentView.h */,
25ECC09E1AFBD92D00F3D63B /* ORKReactionTimeContentView.m */,
25ECC0A11AFBDD2700F3D63B /* ORKReactionTimeStimulusView.h */,
25ECC0A21AFBDD2700F3D63B /* ORKReactionTimeStimulusView.m */,
);
name = Shake;
sourceTree = "<group>";
};
00C266872302242A00337E0B /* Custom Step */ = {
isa = PBXGroup;
children = (
@@ -2078,6 +2146,7 @@
children = (
14D3F09B225BCA8100A3962D /* ORKBorderedButtonTests.swift */,
148E58C1227B753F00EEF915 /* ORKContinueButtonTests.swift */,
517B96E027ED310E00C29A00 /* ORKTextButtonTests.swift */,
);
name = ORKButtonTests;
sourceTree = "<group>";
@@ -2236,16 +2305,8 @@
25ECC0921AFBD64800F3D63B /* Reaction Time */ = {
isa = PBXGroup;
children = (
FF919A3D1E81AFEF005C2A1E /* ORKReactionTimeResult.h */,
FF919A3E1E81AFEF005C2A1E /* ORKReactionTimeResult.m */,
25ECC0931AFBD68300F3D63B /* ORKReactionTimeStep.h */,
25ECC0941AFBD68300F3D63B /* ORKReactionTimeStep.m */,
25ECC0991AFBD8B300F3D63B /* ORKReactionTimeViewController.h */,
25ECC09A1AFBD8B300F3D63B /* ORKReactionTimeViewController.m */,
25ECC09D1AFBD92D00F3D63B /* ORKReactionTimeContentView.h */,
25ECC09E1AFBD92D00F3D63B /* ORKReactionTimeContentView.m */,
25ECC0A11AFBDD2700F3D63B /* ORKReactionTimeStimulusView.h */,
25ECC0A21AFBDD2700F3D63B /* ORKReactionTimeStimulusView.m */,
0B4C04562357B3A700719958 /* Shake */,
0B4C04552357B39200719958 /* Normalized */,
);
name = "Reaction Time";
sourceTree = "<group>";
@@ -2339,31 +2400,6 @@
name = "Custom Vision View";
sourceTree = "<group>";
};
511987C024632E33004FC2C7 /* Request Permissions Step */ = {
isa = PBXGroup;
children = (
51E03D60249196E0008F8406 /* Permission Types */,
511987C1246330CA004FC2C7 /* ORKRequestPermissionsStep.h */,
511987C2246330CA004FC2C7 /* ORKRequestPermissionsStep.m */,
511987C52463316E004FC2C7 /* ORKRequestPermissionsStepViewController.h */,
511987C62463316E004FC2C7 /* ORKRequestPermissionsStepViewController.m */,
51D823032472FB9B00BF4F72 /* ORKRequestPermissionsStepContainerView.h */,
51D823042472FB9B00BF4F72 /* ORKRequestPermissionsStepContainerView.m */,
51E03D6B2491B6DB008F8406 /* ORKRequestPermissionView.h */,
51E03D6C2491B6DB008F8406 /* ORKRequestPermissionView.m */,
);
name = "Request Permissions Step";
sourceTree = "<group>";
};
5157808A22984C280058FF6C /* Vision Tasks */ = {
isa = PBXGroup;
children = (
510743F1227C929000574EC4 /* Landolt C */,
BA95AA9320ACD05100E7FF8E /* AmslerGrid */,
);
name = "Vision Tasks";
sourceTree = "<group>";
};
51198798245FC31C004FC2C7 /* 3D Model Manager */ = {
isa = PBXGroup;
children = (
@@ -2387,6 +2423,33 @@
name = "USDZ Model Manager";
sourceTree = "<group>";
};
511987C024632E33004FC2C7 /* Request Permissions Step */ = {
isa = PBXGroup;
children = (
51E03D60249196E0008F8406 /* Permission Types */,
511987C1246330CA004FC2C7 /* ORKRequestPermissionsStep.h */,
511987C2246330CA004FC2C7 /* ORKRequestPermissionsStep.m */,
511987C52463316E004FC2C7 /* ORKRequestPermissionsStepViewController.h */,
511987C62463316E004FC2C7 /* ORKRequestPermissionsStepViewController.m */,
51D823032472FB9B00BF4F72 /* ORKRequestPermissionsStepContainerView.h */,
51D823042472FB9B00BF4F72 /* ORKRequestPermissionsStepContainerView.m */,
51E03D6B2491B6DB008F8406 /* ORKRequestPermissionView.h */,
51E03D6C2491B6DB008F8406 /* ORKRequestPermissionView.m */,
037D9B872629FCD300B83F9D /* ORKRequestPermissionButton.h */,
037D9B882629FCD300B83F9D /* ORKRequestPermissionButton.m */,
);
name = "Request Permissions Step";
sourceTree = "<group>";
};
5157808A22984C280058FF6C /* Vision Tasks */ = {
isa = PBXGroup;
children = (
510743F1227C929000574EC4 /* Landolt C */,
BA95AA9320ACD05100E7FF8E /* AmslerGrid */,
);
name = "Vision Tasks";
sourceTree = "<group>";
};
5175144B2459EBD3009E8FFC /* 3D Model */ = {
isa = PBXGroup;
children = (
@@ -2423,6 +2486,10 @@
children = (
51E03D6124919711008F8406 /* ORKPermissionType.h */,
51E03D6224919711008F8406 /* ORKPermissionType.m */,
037D9B952629FF7E00B83F9D /* ORKNotificationPermissionType.h */,
037D9B962629FF7E00B83F9D /* ORKNotificationPermissionType.m */,
037D9BA5262A031300B83F9D /* ORKMotionActivityPermissionType.h */,
037D9BA6262A031300B83F9D /* ORKMotionActivityPermissionType.m */,
51E03D652491A5E0008F8406 /* HealthKit Permission Type */,
);
name = "Permission Types";
@@ -3960,6 +4027,11 @@
files = (
D3C8072A21891EBE00F9A231 /* ORKEnvironmentSPLMeterResult.h in Headers */,
BF1D43851D4904C6007EE90B /* ORKVideoInstructionStep.h in Headers */,
0B4C045C2357B47700719958 /* ORKNormalizedReactionTimeStimulusView.h in Headers */,
0B4C04602357B49C00719958 /* ORKNormalizedReactionTimeContentView.h in Headers */,
0B4C04642357B4BF00719958 /* ORKNormalizedReactionTimeViewController.h in Headers */,
0B4C04682357B4DA00719958 /* ORKNormalizedReactionTimeStep.h in Headers */,
0B4C046C2357B4FF00719958 /* ORKNormalizedReactionTimeResult.h in Headers */,
BF5161501BE9C53D00174DDD /* ORKWaitStep.h in Headers */,
244EFAD21BCEFD83001850D9 /* ORKAnswerFormat_Private.h in Headers */,
BCA5C0351AEC05F20092AC8D /* ORKStepNavigationRule.h in Headers */,
@@ -4034,6 +4106,7 @@
10FF9AD31B79F5EA00ECB5B4 /* ORKHolePegTestRemovePegView.h in Headers */,
FF919A5F1E81CF07005C2A1E /* ORKVideoInstructionStepResult.h in Headers */,
866DA5201D63D04700C9AF3F /* ORKCollector.h in Headers */,
037D9BA7262A031300B83F9D /* ORKMotionActivityPermissionType.h in Headers */,
BF9155AB1BDE8DA9007FA459 /* ORKWaitStepViewController.h in Headers */,
65EF3BFA21B7A1D4007BE0D6 /* ORKTouchAbilitySwipeStep.h in Headers */,
BF9155A91BDE8DA9007FA459 /* ORKWaitStepView.h in Headers */,
@@ -4109,6 +4182,7 @@
BA473FE8224DB38900A362E3 /* ORKBodyItem.h in Headers */,
2429D5721BBB5397003A512F /* ORKRegistrationStep.h in Headers */,
65EEE15A21B12CFD007858E8 /* ORKTouchAbilityLongPressStepViewController.h in Headers */,
037D9B892629FCD300B83F9D /* ORKRequestPermissionButton.h in Headers */,
B183A4A21A8535D100C76870 /* ResearchKit_Private.h in Headers */,
242C9E111BBE06DE0088B7F4 /* ORKVerificationStepView.h in Headers */,
86C40CE81A8D7C5C00081FAC /* ORKAnswerFormat_Internal.h in Headers */,
@@ -4264,6 +4338,7 @@
9550E6731D58DBCF00C691B8 /* ORKTouchAnywhereStep.h in Headers */,
6506EB25218057FC002DF5DB /* ORKTouchAbilityTapContentView.h in Headers */,
86C40CF61A8D7C5C00081FAC /* ORKBorderedButton.h in Headers */,
037D9B972629FF7E00B83F9D /* ORKNotificationPermissionType.h in Headers */,
71769E2D208824D100A19914 /* ORKdBHLToneAudiometryOnboardingStep.h in Headers */,
65D152362192D08C00E45120 /* ORKTouchAbilityTrial.h in Headers */,
BA8C066B22ECF7C200ACDE6B /* ORKSecondaryTaskStep.h in Headers */,
@@ -4648,6 +4723,7 @@
FA7A9D371B09365F005A2BEA /* ORKConsentSectionFormatterTests.m in Sources */,
714151D0225C4A23002CA33B /* ORKPasscodeViewControllerTests.swift in Sources */,
14E79040226A5F72009D8083 /* ORKStepViewControllerHelpers.swift in Sources */,
517B96E127ED310E00C29A00 /* ORKTextButtonTests.swift in Sources */,
86D348021AC161B0006DB02B /* ORKRecorderTests.m in Sources */,
1490DCFC224D4867003FEEDA /* ORKEnvironmentSPLMeterResultTests.swift in Sources */,
7141EA2222EFBC0C00650145 /* ORKLoggingTests.m in Sources */,
@@ -4673,6 +4749,7 @@
2433C9E41B9A506F0052D375 /* ORKKeychainWrapper.m in Sources */,
FF919A2B1E81A94B005C2A1E /* ORKRangeOfMotionResult.m in Sources */,
86C40CA61A8D7C5C00081FAC /* ORKLocationRecorder.m in Sources */,
0B4C045E2357B48D00719958 /* ORKNormalizedReactionTimeContentView.m in Sources */,
86C40CB61A8D7C5C00081FAC /* ORKTouchRecorder.m in Sources */,
510743FC227C942E00574EC4 /* ORKLandoltCStep.swift in Sources */,
BC2908BC1FBD628F0030AB89 /* ORKTypes.m in Sources */,
@@ -4703,6 +4780,7 @@
BCB6E6531B7D533B000D5B34 /* ORKPieChartLegendCell.m in Sources */,
866DA52A1D63D04700C9AF3F /* ORKOperation.m in Sources */,
86C40D8C1A8D7C5C00081FAC /* ORKSkin.m in Sources */,
0B4C046A2357B4ED00719958 /* ORKNormalizedReactionTimeResult.m in Sources */,
86C40D221A8D7C5C00081FAC /* ORKFormStep.m in Sources */,
7118AC7820BF6A7800D7A6BB /* ORKSpeechInNoiseStep.m in Sources */,
BA2FE3EF20B109C600AAC231 /* ORKAmslerGridResult.m in Sources */,
@@ -4719,6 +4797,7 @@
106FF2AB1B690FD7004EACF2 /* ORKHolePegTestPlacePegView.m in Sources */,
BF91559D1BDE8D7D007FA459 /* ORKReviewStep.m in Sources */,
86C40C381A8D7C5C00081FAC /* ORKSpatialSpanGame.m in Sources */,
0B4C04662357B4CD00719958 /* ORKNormalizedReactionTimeStep.m in Sources */,
65EF3C0321B7BAA8007BE0D6 /* ORKTouchAbilitySwipeResult.m in Sources */,
86C40C481A8D7C5C00081FAC /* ORKSpatialSpanMemoryStepViewController.m in Sources */,
86C40CDA1A8D7C5C00081FAC /* ORKTextFieldView.m in Sources */,
@@ -4756,6 +4835,7 @@
86C40CF01A8D7C5C00081FAC /* ORKAnswerTextView.m in Sources */,
86C40D3E1A8D7C5C00081FAC /* ORKImageChoiceLabel.m in Sources */,
1B4B95B81F5F012E006B629F /* ORKWeightPicker.m in Sources */,
0B4C04582357B43700719958 /* ORKNormalizedReactionTimeStimulusView.m in Sources */,
250F94051B4C5A6600FA23EB /* ORKTowerOfHanoiStep.m in Sources */,
86C40E0A1A8D7C5C00081FAC /* ORKConsentReviewStep.m in Sources */,
BCD192E81B81243900FCC08A /* ORKPieChartLegendView.m in Sources */,
@@ -4859,7 +4939,9 @@
65E3636C2177271000AEE2F6 /* ORKTouchAbilityTapStepViewController.m in Sources */,
86C40C701A8D7C5C00081FAC /* CMMotionActivity+ORKJSONDictionary.m in Sources */,
147503B01AEE8071004B17F3 /* ORKAudioGenerator.m in Sources */,
037D9BA8262A031300B83F9D /* ORKMotionActivityPermissionType.m in Sources */,
86C40D7E1A8D7C5C00081FAC /* ORKScaleValueLabel.m in Sources */,
037D9B8A2629FCD300B83F9D /* ORKRequestPermissionButton.m in Sources */,
8056857A1C90C19500BF437A /* UIImage+ResearchKit.m in Sources */,
BA8C5022226FFB04001896D0 /* ORKLearnMoreItem.m in Sources */,
BC94EF321E962F7400143081 /* ORKDeprecated.m in Sources */,
@@ -4908,6 +4990,7 @@
86C40D6C1A8D7C5C00081FAC /* ORKResult.m in Sources */,
781D54151DF886AB00223305 /* ORKTrailmakingStepViewController.m in Sources */,
BA87E26E20A79FA100B375A9 /* ORKRingView.m in Sources */,
037D9B982629FF7E00B83F9D /* ORKNotificationPermissionType.m in Sources */,
86C40CD21A8D7C5C00081FAC /* ORKInstructionStepView.m in Sources */,
10FF9AD81B7A045E00ECB5B4 /* ORKSeparatorView.m in Sources */,
86C40D181A8D7C5C00081FAC /* ORKErrors.m in Sources */,
@@ -5015,6 +5098,7 @@
86C40DE01A8D7C5C00081FAC /* ORKVerticalContainerView.m in Sources */,
71769E3E20884DB800A19914 /* ORKdBHLToneAudiometryStepViewController.m in Sources */,
BA5B9208204F5D9A007C2F9D /* ORKSpeechRecognitionResult.m in Sources */,
0B4C04622357B4B100719958 /* ORKNormalizedReactionTimeViewController.m in Sources */,
BCD192EC1B81245500FCC08A /* ORKPieChartTitleTextView.m in Sources */,
2489F7B41D65214D008DEF20 /* ORKVideoCaptureStepViewController.m in Sources */,
861D2AF71B843968008C4CD0 /* ORKCompletionStepViewController.m in Sources */,
@@ -5189,8 +5273,7 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_LABEL = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
LIBRARY_SEARCH_PATHS = "$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)";
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
@@ -5259,7 +5342,7 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_LABEL = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
ONLY_ACTIVE_ARCH = NO;
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
@@ -5296,7 +5379,7 @@
GCC_WARN_SHADOW = NO;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
INFOPLIST_FILE = ResearchKitTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MTL_ENABLE_DEBUG_INFO = YES;
PRODUCT_BUNDLE_IDENTIFIER = "org.researchkit.$(PRODUCT_NAME:rfc1034identifier)";
@@ -5322,7 +5405,7 @@
GCC_WARN_SHADOW = NO;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
INFOPLIST_FILE = ResearchKitTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MTL_ENABLE_DEBUG_INFO = NO;
PRODUCT_BUNDLE_IDENTIFIER = "org.researchkit.$(PRODUCT_NAME:rfc1034identifier)";
@@ -5352,6 +5435,10 @@
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREPROCESSOR_DEFINITIONS = (
GLES_SILENCE_DEPRECATION,
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
@@ -5362,8 +5449,9 @@
GCC_WARN_UNUSED_LABEL = YES;
INFOPLIST_FILE = ResearchKit/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MARKETING_VERSION = 2.1.0;
MODULEMAP_FILE = ResearchKit/ResearchKit.modulemap;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
@@ -5398,6 +5486,7 @@
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREPROCESSOR_DEFINITIONS = GLES_SILENCE_DEPRECATION;
GCC_SYMBOLS_PRIVATE_EXTERN = YES;
GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
@@ -5408,8 +5497,9 @@
GCC_WARN_UNUSED_LABEL = YES;
INFOPLIST_FILE = ResearchKit/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MARKETING_VERSION = 2.1.0;
MODULEMAP_FILE = ResearchKit/ResearchKit.modulemap;
MTL_ENABLE_DEBUG_INFO = NO;
PRODUCT_BUNDLE_IDENTIFIER = "org.researchkit.${PRODUCT_NAME:rfc1034identifier}";
+9
View File
@@ -206,6 +206,15 @@ The default value of this property is `NO`.
*/
@property (nonatomic, copy, nullable) NSArray<ORKRecorderConfiguration *> *recorderConfigurations;
/**
A Boolean value that determines if a step is a practice step or not.
When the value of this property is `YES`, the ResearchKit framework sets the allowsBackNavigation property to 'YES'
The default value of this property is `NO`.
*/
@property (nonatomic, assign) BOOL isPractice;
@end
NS_ASSUME_NONNULL_END
+8 -1
View File
@@ -122,6 +122,7 @@
ORK_DECODE_OBJ_CLASS(aDecoder, spokenInstruction, NSString);
ORK_DECODE_OBJ_CLASS(aDecoder, finishedSpokenInstruction, NSString);
ORK_DECODE_OBJ_ARRAY(aDecoder, recorderConfigurations, ORKRecorderConfiguration);
ORK_DECODE_BOOL(aDecoder, isPractice);
}
return self;
}
@@ -142,6 +143,7 @@
ORK_ENCODE_OBJ(aCoder, spokenInstruction);
ORK_ENCODE_OBJ(aCoder, finishedSpokenInstruction);
ORK_ENCODE_OBJ(aCoder, recorderConfigurations);
ORK_ENCODE_BOOL(aCoder, isPractice);
}
- (BOOL)isEqual:(id)object {
@@ -162,7 +164,8 @@
(self.shouldVibrateOnStart == castObject.shouldVibrateOnStart) &&
(self.shouldVibrateOnFinish == castObject.shouldVibrateOnFinish) &&
(self.shouldContinueOnFinish == castObject.shouldContinueOnFinish) &&
(self.shouldUseNextAsSkipButton == castObject.shouldUseNextAsSkipButton));
(self.shouldUseNextAsSkipButton == castObject.shouldUseNextAsSkipButton) &&
(self.isPractice == castObject.isPractice));
}
- (NSSet<HKObjectType *> *)requestedHealthKitTypesForReading {
@@ -184,4 +187,8 @@
return mask;
}
- (BOOL)allowsBackNavigation {
return self.isPractice;
}
@end
@@ -109,7 +109,6 @@
_submitVideoButton.titleLabel.font = [UIFont systemFontOfSize:20.0];
[_submitVideoButton setTitleColor:UIColor.whiteColor forState:UIControlStateNormal];
[_submitVideoButton setBackgroundColor:[UIColor systemBlueColor]];
[_submitVideoButton setTitleEdgeInsets:UIEdgeInsetsMake(5.0, 8.0, 5.0, 8.0)];
[_submitVideoButton setTitle:ORKLocalizedString(@"FRONT_FACING_CAMERA_SUBMIT_VIDEO", nil) forState:UIControlStateNormal];
[self.contentView addSubview:_submitVideoButton];
}
@@ -194,9 +193,18 @@ typedef NS_CLOSED_ENUM(NSInteger, ORKStartStopButtonState) {
- (void)setupSubviews {
_startStopButton = [UIButton new];
if (@available(iOS 15.0, *)) {
UIButtonConfiguration *buttonConfiguration = [UIButtonConfiguration plainButtonConfiguration];
[buttonConfiguration setContentInsets:NSDirectionalEdgeInsetsMake(0, 6, 0, 6)];
[_startStopButton setConfiguration:buttonConfiguration];
} else {
_startStopButton.contentEdgeInsets = (UIEdgeInsets){.left = 6, .right = 6};
}
_startStopButton.layer.cornerRadius = 14.0;
_startStopButton.clipsToBounds = YES;
_startStopButton.contentEdgeInsets = (UIEdgeInsets){.left = 6, .right = 6};
UIFontDescriptor *descriptorOne = [UIFontDescriptor preferredFontDescriptorWithTextStyle:UIFontTextStyleHeadline];
_startStopButton.titleLabel.font = [UIFont boldSystemFontOfSize:[[descriptorOne objectForKey: UIFontDescriptorSizeAttribute] doubleValue] + 1.0];
[self.contentView addSubview:_startStopButton];
+19 -2
View File
@@ -88,7 +88,16 @@
}
self.locationManager = [self createLocationManager];
if ([CLLocationManager authorizationStatus] <= kCLAuthorizationStatusDenied) {
CLAuthorizationStatus status = kCLAuthorizationStatusNotDetermined;
if (@available(iOS 14.0, *)) {
status = self.locationManager.authorizationStatus;
} else {
status = [CLLocationManager authorizationStatus];
}
if (status == kCLAuthorizationStatusRestricted || status == kCLAuthorizationStatusNotDetermined) {
[self.locationManager requestWhenInUseAuthorization];
}
self.locationManager.pausesLocationUpdatesAutomatically = NO;
@@ -156,7 +165,15 @@
}
- (BOOL)isRecording {
return [CLLocationManager locationServicesEnabled] && (self.locationManager != nil) && ([CLLocationManager authorizationStatus] > kCLAuthorizationStatusDenied);
CLAuthorizationStatus status = kCLAuthorizationStatusNotDetermined;
if (@available(iOS 14.0, *)) {
status = self.locationManager.authorizationStatus;
} else {
status = [CLLocationManager authorizationStatus];
}
return [CLLocationManager locationServicesEnabled] && (self.locationManager != nil) && (status > kCLAuthorizationStatusDenied);
}
- (void)reset {
@@ -0,0 +1,58 @@
/*
Copyright (c) 2019, Apple Inc. All rights reserved.
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;
#import "ORKCustomStepView_Internal.h"
#import "ORKNormalizedReactionTimeStimulusView.h"
#import "ORKRoundTappingButton.h"
NS_ASSUME_NONNULL_BEGIN
@interface ORKNormalizedReactionTimeContentView : ORKActiveStepCustomView
@property (nonatomic) ORKRoundTappingButton *button;
- (void)setStimulusHidden:(BOOL)hidden;
- (void)startSuccessAnimationWithDuration:(NSTimeInterval)duration completion:(nullable void (^)(void))completion;
- (void)startFailureAnimationWithDuration:(NSTimeInterval)duration completion:(nullable void (^)(void))completion;
- (void)resetAfterDelay:(NSTimeInterval)delay completion:(nullable void (^)(void))completion;
- (UIView *)getBackgroundView;
- (ORKNormalizedReactionTimeStimulusView *)getStimulusView;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,213 @@
/*
Copyright (c) 2019, Apple Inc. All rights reserved.
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 "ORKNormalizedReactionTimeContentView.h"
#import "ORKNavigationContainerView.h"
#import "ORKNormalizedReactionTimeStimulusView.h"
#import "ORKSkin.h"
#import "ORKHelpers_Internal.h"
CGFloat NormalizeButtonSize = 100.0;
CGFloat BackgroundViewSpaceMultiplier = 2.0;
@implementation ORKNormalizedReactionTimeContentView {
ORKNormalizedReactionTimeStimulusView *_stimulusView;
UIView *_backgroundView;
}
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.translatesAutoresizingMaskIntoConstraints = NO;
[self resizeConstraints];
[self addStimulusView];
[self addBackgroundView];
[self addButton];
}
return self;
}
- (void)startSuccessAnimationWithDuration:(NSTimeInterval)duration completion:(void (^)(void))completion {
[_stimulusView startSuccessAnimationWithDuration:duration completion:completion];
}
- (void)startFailureAnimationWithDuration:(NSTimeInterval)duration completion:(void (^)(void))completion {
[_stimulusView startFailureAnimationWithDuration:duration completion:completion];
}
- (void)resetAfterDelay:(NSTimeInterval)delay completion:(nullable void (^)(void))completion {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
_stimulusView.hidden = YES;
if (completion) {
completion();
}
});
}
-(void)resizeConstraints {
ORKScreenType screenType = ORKGetVerticalScreenTypeForWindow([[[UIApplication sharedApplication] delegate] window]);
if (screenType == ORKScreenTypeiPhone5 ) {
NormalizeButtonSize = 70.0;
BackgroundViewSpaceMultiplier = 1.75;
}
}
-(void)addButton {
_button = [ORKRoundTappingButton new];
_button.translatesAutoresizingMaskIntoConstraints = NO;
[_button setTitle: ORKLocalizedString(@"REACTION_TIME_TASK_NORM_BUTTON_TITLE", nil) forState:UIControlStateNormal];
[_button setDiameter:NormalizeButtonSize];
[self addSubview:_button];
[NSLayoutConstraint activateConstraints: @[
[NSLayoutConstraint constraintWithItem:_button
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0.0],
[NSLayoutConstraint constraintWithItem:_button
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:_backgroundView
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:1.0],
]];
}
- (void)addStimulusView {
if (!_stimulusView) {
_stimulusView = [ORKNormalizedReactionTimeStimulusView new];
_stimulusView.translatesAutoresizingMaskIntoConstraints = NO;
_stimulusView.backgroundColor = self.tintColor;
[self addSubview:_stimulusView];
[self setUpStimulusViewConstraints];
}
}
- (void)addBackgroundView {
if (!_backgroundView) {
_backgroundView = [UIView new];
}
_backgroundView.translatesAutoresizingMaskIntoConstraints = NO;
_backgroundView.layer.borderWidth = 3.0;
_backgroundView.backgroundColor = [[UIColor lightGrayColor] colorWithAlphaComponent:0.3];
_backgroundView.layer.borderColor = [UIColor lightGrayColor].CGColor;
[self insertSubview:_backgroundView belowSubview:_stimulusView];
[self setupBackgroundViewConstraints];
}
- (UIView *)getBackgroundView {
return _backgroundView;
}
- (ORKNormalizedReactionTimeStimulusView *)getStimulusView {
return _stimulusView;
}
- (void)setStimulusHidden:(BOOL)hidden {
_stimulusView.hidden = hidden;
}
- (void)setUpStimulusViewConstraints {
NSMutableArray *constraints = [NSMutableArray array];
[constraints addObject:[NSLayoutConstraint constraintWithItem:_stimulusView
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0.0]];
[constraints addObject:[NSLayoutConstraint constraintWithItem:_stimulusView
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeCenterY
multiplier:1.0
constant:0.0]];
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_stimulusView]-(>=0)-|"
options:NSLayoutFormatAlignAllCenterX
metrics:nil
views:NSDictionaryOfVariableBindings(_stimulusView)]];
[NSLayoutConstraint activateConstraints:constraints];
}
- (void)setupBackgroundViewConstraints {
NSMutableArray *constraints = [NSMutableArray array];
[constraints addObjectsFromArray:@[
[NSLayoutConstraint constraintWithItem:_backgroundView
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:_stimulusView
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0.0],
[NSLayoutConstraint constraintWithItem:_backgroundView
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:_stimulusView
attribute:NSLayoutAttributeCenterY
multiplier:1.0
constant:0.0],
[NSLayoutConstraint constraintWithItem:_backgroundView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:_stimulusView
attribute:NSLayoutAttributeWidth
multiplier:BackgroundViewSpaceMultiplier
constant:0.0],
[NSLayoutConstraint constraintWithItem:_backgroundView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:_stimulusView
attribute:NSLayoutAttributeHeight
multiplier:BackgroundViewSpaceMultiplier
constant:0.0],
]];
[NSLayoutConstraint activateConstraints:constraints];
}
@end
@@ -0,0 +1,65 @@
/*
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 <ResearchKit/ORKResult.h>
NS_ASSUME_NONNULL_BEGIN
@class ORKFileResult;
/**
The `ORKReactionTimeResult` class represents the result of a single successful attempt within an ORKReactionTimeStep.
The `timestamp` property is equal to the value of systemUptime (in NSProcessInfo) when the stimulus occurred.
Each entry of motion data in this file contains a time interval which may be directly compared to timestamp in order to determine the elapsed time since the stimulus.
The fileResult property references the motion data recorded from the beginning of the attempt until the threshold acceleration was reached.
Using the time taken to reach the threshold acceleration as the reaction time of a participant will yield a rather crude measurement. Rather, you should devise your own method using the data recorded to obtain an accurate approximation of the true reaction time.
A reaction time result is typically generated by the framework as the task proceeds. When the task
completes, it may be appropriate to serialize the sample for transmission to a server
or to immediately perform analysis on it.
*/
ORK_CLASS_AVAILABLE
@interface ORKNormalizedReactionTimeResult: ORKResult
@property (nonatomic, copy) NSDate * timerStartDate;
@property (nonatomic, copy) NSDate * timerEndDate;
@property (nonatomic, copy, nullable) NSDate * stimulusStartDate;
@property (nonatomic, copy, nullable) NSDate * reactionDate;
@property (nonatomic) NSNumber *currentInterval;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,94 @@
/*
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 "ORKNormalizedReactionTimeResult.h"
#import "ORKResult_Private.h"
#import "ORKHelpers_Internal.h"
@implementation ORKNormalizedReactionTimeResult
- (void)encodeWithCoder:(NSCoder *)aCoder {
[super encodeWithCoder:aCoder];
ORK_ENCODE_OBJ(aCoder, timerStartDate);
ORK_ENCODE_OBJ(aCoder, timerEndDate);
ORK_ENCODE_OBJ(aCoder, stimulusStartDate);
ORK_ENCODE_OBJ(aCoder, reactionDate);
ORK_ENCODE_OBJ(aCoder, currentInterval);
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
ORK_DECODE_OBJ_CLASS(aDecoder, timerStartDate, NSDate);
ORK_DECODE_OBJ_CLASS(aDecoder, timerEndDate, NSDate);
ORK_DECODE_OBJ_CLASS(aDecoder, stimulusStartDate, NSDate);
ORK_DECODE_OBJ_CLASS(aDecoder, reactionDate, NSDate);
ORK_DECODE_OBJ_CLASS(aDecoder, currentInterval, NSNumber);
}
return self;
}
+ (BOOL)supportsSecureCoding {
return YES;
}
- (BOOL)isEqual:(id)object {
BOOL isParentSame = [super isEqual:object];
__typeof(self) castObject = object;
return (isParentSame &&
ORKEqualObjects(self.timerStartDate, castObject.timerStartDate) &&
ORKEqualObjects(self.timerEndDate, castObject.timerEndDate) &&
ORKEqualObjects(self.stimulusStartDate, castObject.stimulusStartDate) &&
ORKEqualObjects(self.reactionDate, castObject.reactionDate) &&
ORKEqualObjects(self.currentInterval, castObject.currentInterval)) ;
}
- (NSUInteger)hash {
return super.hash ^ _timerStartDate.hash ^ _timerEndDate.hash ^ _stimulusStartDate.hash ^ _reactionDate.hash;
}
- (instancetype)copyWithZone:(NSZone *)zone {
ORKNormalizedReactionTimeResult *result = [super copyWithZone:zone];
result.timerStartDate = [self.timerStartDate copy];
result.timerEndDate = [self.timerEndDate copy];
result.stimulusStartDate = [self.stimulusStartDate copy];
result.reactionDate = [self.reactionDate copy];
result.currentInterval = [self.currentInterval copy];
return result;
}
@end
@@ -0,0 +1,65 @@
/*
Copyright (c) 2019, Apple Inc. All rights reserved.
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;
@import AudioToolbox;
#import <ResearchKit/ORKDefines.h>
#import <ResearchKit/ORKActiveStep.h>
NS_ASSUME_NONNULL_BEGIN
ORK_CLASS_AVAILABLE
@interface ORKNormalizedReactionTimeStep : ORKActiveStep
@property (nonatomic, assign) NSTimeInterval maximumStimulusInterval;
@property (nonatomic, assign) NSTimeInterval minimumStimulusInterval;
@property (nonatomic, assign) NSTimeInterval timeout;
@property (nonatomic, assign) NSInteger numberOfAttempts;
@property (nonatomic, assign) double thresholdAcceleration;
@property (nonatomic, assign) SystemSoundID successSound;
@property (nonatomic, assign) SystemSoundID timeoutSound;
@property (nonatomic, assign) SystemSoundID failureSound;
@property (nonatomic) NSNumber *currentInterval;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,153 @@
/*
Copyright (c) 2019, Apple Inc. All rights reserved.
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 "ORKNormalizedReactionTimeStep.h"
#import "ORKNormalizedReactionTimeViewController.h"
#import "ORKHelpers_Internal.h"
@implementation ORKNormalizedReactionTimeStep
+ (Class)stepViewControllerClass {
return [ORKNormalizedReactionTimeViewController class];
}
- (instancetype)initWithIdentifier:(NSString *)identifier {
self = [super initWithIdentifier:identifier];
self.shouldContinueOnFinish = YES;
return self;
}
- (instancetype)copyWithZone:(NSZone *)zone {
ORKNormalizedReactionTimeStep *step = [super copyWithZone:zone];
step.maximumStimulusInterval = self.maximumStimulusInterval;
step.minimumStimulusInterval = self.minimumStimulusInterval;
step.thresholdAcceleration = self.thresholdAcceleration;
step.timeout = self.timeout;
step.numberOfAttempts = self.numberOfAttempts;
step.successSound = self.successSound;
step.timeoutSound = self.timeoutSound;
step.failureSound = self.failureSound;
self.currentInterval = self.currentInterval;
return step;
}
- (void)validateParameters {
[super validateParameters];
if (self.minimumStimulusInterval <= 0) {
@throw [NSException exceptionWithName:NSInvalidArgumentException
reason:@"minimumStimulusInterval must be greater than zero"
userInfo:nil];
}
if (self.maximumStimulusInterval < self.minimumStimulusInterval) {
@throw [NSException exceptionWithName:NSInvalidArgumentException
reason:@"maximumStimulusInterval cannot be less than minimumStimulusInterval"
userInfo:nil];
}
if (self.thresholdAcceleration <= 0) {
@throw [NSException exceptionWithName:NSInvalidArgumentException
reason:@"thresholdAcceleration must be greater than zero"
userInfo:nil];
}
if (self.timeout <= 0) {
@throw [NSException exceptionWithName:NSInvalidArgumentException
reason:@"timeout must be greater than zero"
userInfo:nil];
}
if (self.numberOfAttempts <= 0) {
@throw [NSException exceptionWithName:NSInvalidArgumentException
reason:@"numberOfAttempts must be greater than zero"
userInfo:nil];
}
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
ORK_DECODE_DOUBLE(aDecoder, maximumStimulusInterval);
ORK_DECODE_DOUBLE(aDecoder, minimumStimulusInterval);
ORK_DECODE_DOUBLE(aDecoder, thresholdAcceleration);
ORK_DECODE_DOUBLE(aDecoder, timeout);
ORK_DECODE_UINT32(aDecoder, successSound);
ORK_DECODE_UINT32(aDecoder, timeoutSound);
ORK_DECODE_UINT32(aDecoder, failureSound);
ORK_DECODE_INTEGER(aDecoder, numberOfAttempts);
ORK_DECODE_OBJ_CLASS(aDecoder, currentInterval, NSNumber);
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
[super encodeWithCoder:aCoder];
ORK_ENCODE_DOUBLE(aCoder, maximumStimulusInterval);
ORK_ENCODE_DOUBLE(aCoder, minimumStimulusInterval);
ORK_ENCODE_DOUBLE(aCoder, thresholdAcceleration);
ORK_ENCODE_DOUBLE(aCoder, timeout);
ORK_ENCODE_UINT32(aCoder, successSound);
ORK_ENCODE_UINT32(aCoder, timeoutSound);
ORK_ENCODE_UINT32(aCoder, failureSound);
ORK_ENCODE_INTEGER(aCoder, numberOfAttempts);
ORK_ENCODE_OBJ(aCoder, currentInterval);
}
+ (BOOL)supportsSecureCoding {
return YES;
}
- (BOOL)isEqual:(id)object {
BOOL isParentSame = [super isEqual:object];
__typeof(self) castObject = object;
return (isParentSame &&
(self.maximumStimulusInterval == castObject.maximumStimulusInterval) &&
(self.minimumStimulusInterval == castObject.minimumStimulusInterval) &&
(self.thresholdAcceleration == castObject.thresholdAcceleration) &&
(self.timeout == castObject.timeout) &&
(self.successSound == castObject.successSound) &&
(self.timeoutSound == castObject.timeoutSound) &&
(self.failureSound == castObject.failureSound) &&
(self.numberOfAttempts == castObject.numberOfAttempts) &&
(self.currentInterval == castObject.currentInterval)
);}
- (BOOL)allowsBackNavigation {
return NO;
}
- (BOOL)startsFinished {
return NO;
}
@end
@@ -0,0 +1,49 @@
/*
Copyright (c) 2019, Apple Inc. All rights reserved.
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;
#import "ORKCustomStepView_Internal.h"
NS_ASSUME_NONNULL_BEGIN
@interface ORKNormalizedReactionTimeStimulusView : UIView
- (void)reset;
- (void)startSuccessAnimationWithDuration:(NSTimeInterval)duration completion:(nullable void(^)(void))completion;
- (void)startFailureAnimationWithDuration:(NSTimeInterval)duration completion:(nullable void(^)(void))completion;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,166 @@
/*
Copyright (c) 2019, Apple Inc. All rights reserved.
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 "ORKNormalizedReactionTimeStimulusView.h"
@implementation ORKNormalizedReactionTimeStimulusView {
CAShapeLayer *_tickLayer;
CAShapeLayer *_crossLayer;
}
static const CGFloat RoundReactionTimeViewDiameter = 122;
- (instancetype)init {
self = [super init];
if (self) {
self.layer.cornerRadius = RoundReactionTimeViewDiameter * 0.5;
}
return self;
}
- (CGSize)intrinsicContentSize {
return CGSizeMake(RoundReactionTimeViewDiameter, RoundReactionTimeViewDiameter);
}
- (void)reset {
[_tickLayer removeFromSuperlayer];
[_crossLayer removeFromSuperlayer];
_tickLayer = nil;
_crossLayer = nil;
self.layer.backgroundColor = self.tintColor.CGColor;
}
- (void)startSuccessAnimationWithDuration:(NSTimeInterval)duration completion:(void(^)(void))completion {
if (self.hidden) {
if (completion) {
completion();
}
return;
}
[self addTickLayer];
[CATransaction begin];
[CATransaction setCompletionBlock:completion];
CAMediaTimingFunction *timing = [[CAMediaTimingFunction alloc] initWithControlPoints:0.180739998817444 :0 :0.577960014343262 :0.918200016021729];
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
[animation setTimingFunction:timing];
animation.removedOnCompletion = NO;
[animation setFillMode:kCAFillModeForwards];
animation.fromValue = @(0);
animation.toValue = @(1);
animation.duration = duration;
[_tickLayer addAnimation:animation forKey:@"strokeEnd"];
[CATransaction commit];
}
- (void)startFailureAnimationWithDuration:(NSTimeInterval)duration completion:(void(^)(void))completion {
self.hidden = NO;
self.layer.backgroundColor = [UIColor clearColor].CGColor;
[self addCrossLayer];
[CATransaction begin];
[CATransaction setCompletionBlock:completion];
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
[animation setFillMode:kCAFillModeForwards];
animation.fromValue = @([(CAShapeLayer *)[_crossLayer presentationLayer] strokeEnd]);
animation.toValue = @(1);
animation.duration = duration;
_crossLayer.strokeEnd = 1;
[_crossLayer addAnimation:animation forKey:@"strokeEnd"];
[CATransaction commit];
}
- (void)setHidden:(BOOL)hidden {
[self reset];
[super setHidden:hidden];
}
- (void)addCrossLayer {
_crossLayer = [self lineDrawingLayer];
_crossLayer.strokeColor = [UIColor redColor].CGColor;
_crossLayer.path = [self crossPath];
[self.layer addSublayer:_crossLayer];
}
- (void)addTickLayer {
_tickLayer = [self lineDrawingLayer];
_tickLayer.strokeColor = [UIColor whiteColor].CGColor;
_tickLayer.path = [self tickPath];
[self.layer addSublayer:_tickLayer];
}
- (CGPathRef)concealPath:(CGFloat)radius {
return [[UIBezierPath bezierPathWithArcCenter:CGPointMake(radius, radius)
radius:radius / 2
startAngle:M_PI + M_PI_2
endAngle:-M_PI_2
clockwise:NO] CGPath];
}
- (CGPathRef)tickPath {
UIBezierPath *path = [self linePath];
[path moveToPoint:(CGPoint){37,65}];
[path addLineToPoint:(CGPoint){50,78}];
[path addLineToPoint:(CGPoint){87,42}];
return path.CGPath;
}
- (CGPathRef)crossPath {
UIBezierPath *path = [self linePath];
[path moveToPoint:(CGPoint){45,78}];
[path addLineToPoint:(CGPoint){82,42}];
[path moveToPoint:(CGPoint){45,42}];
[path addLineToPoint:(CGPoint){82,78}];
return path.CGPath;
}
- (UIBezierPath *)linePath {
UIBezierPath *path = [UIBezierPath new];
path.lineCapStyle = kCGLineCapRound;
path.lineWidth = 5;
return path;
}
- (CAShapeLayer *)lineDrawingLayer {
CAShapeLayer *shapeLayer = [CAShapeLayer new];
shapeLayer.strokeEnd = 0;
shapeLayer.lineWidth = 5;
shapeLayer.lineCap = kCALineCapRound;
shapeLayer.lineJoin = kCALineJoinRound;
shapeLayer.frame = self.layer.bounds;
shapeLayer.backgroundColor = [UIColor clearColor].CGColor;
shapeLayer.fillColor = nil;
return shapeLayer;
}
@end
@@ -0,0 +1,45 @@
/*
Copyright (c) 2019, Apple Inc. All rights reserved.
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;
#import "ORKDefines.h"
#import "ORKActiveStepViewController.h"
NS_ASSUME_NONNULL_BEGIN
ORK_CLASS_AVAILABLE
@interface ORKNormalizedReactionTimeViewController : ORKActiveStepViewController
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,271 @@
/*
Copyright (c) 2019, Apple Inc. All rights reserved.
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 "ORKNormalizedReactionTimeViewController.h"
#import "ORKBorderedButton.h"
#import "ORKActiveStepView.h"
#import "ORKNormalizedReactionTimeContentView.h"
#import "ORKActiveStepViewController_Internal.h"
#import "ORKStepViewController_Internal.h"
#import "ORKVerticalContainerView_Internal.h"
#import "ORKCollectionResult_Private.h"
#import "ORKNormalizedReactionTimeResult.h"
#import "ORKNormalizedReactionTimeStep.h"
#import "ORKResult.h"
#import "ORKHelpers_Internal.h"
#import <AudioToolbox/AudioServices.h>
@implementation ORKNormalizedReactionTimeViewController {
ORKNormalizedReactionTimeContentView *_reactionTimeContentView;
NSMutableArray *_results;
NSTimer *_stimulusTimer;
NSTimer *_timeoutTimer;
NSTimeInterval _stimulusTimestamp;
BOOL _validResult;
BOOL _timedOut;
BOOL _shouldIndicateFailure;
UIView *_backgroundView;
ORKNormalizedReactionTimeStimulusView *_stimulusView;
NSDate *_timerStartDate;
NSDate *_stimulusStartDate;
NSDate *_reactionDate;
}
static const NSTimeInterval OutcomeAnimationDuration = 0.3;
#pragma mark - UIViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[self configureTitle];
_results = [NSMutableArray new];
_reactionTimeContentView = [ORKNormalizedReactionTimeContentView new];
[_reactionTimeContentView.button addTarget:self action:@selector(startStimulusTimer) forControlEvents:UIControlEventTouchDown];
[_reactionTimeContentView.button addTarget:self action:@selector(startReactionTimer) forControlEvents:UIControlEventTouchUpInside];
self.activeStepView.activeCustomView = _reactionTimeContentView;
_backgroundView = [_reactionTimeContentView getBackgroundView];
_stimulusView = [_reactionTimeContentView getStimulusView];
[_backgroundView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapDetected)]];
[_stimulusView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapDetected)]];
[_reactionTimeContentView setStimulusHidden:YES];
}
-(void)startReactionTimer {
if (_stimulusView.hidden) {
_validResult = NO;
_timedOut = YES;
[self addReactionTimeResult];
#if TARGET_IPHONE_SIMULATOR
// Device motion recorder won't work, so manually trigger didfinish
[self attemptDidFinish];
#endif
} else {
_timerStartDate = [NSDate date];
}
}
- (void)tapDetected {
if ([_stimulusTimer isValid] || [_timeoutTimer isValid]) {
_reactionDate = [NSDate date];
[self addReactionTimeResult];
}
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self start];
_shouldIndicateFailure = YES;
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
_shouldIndicateFailure = NO;
}
#pragma mark - ORKActiveStepViewController
- (void)start {
[super start];
}
- (ORKStepResult *)result {
ORKStepResult *stepResult = [super result];
stepResult.results = [self.addedResults arrayByAddingObjectsFromArray:_results] ? : _results;
return stepResult;
}
- (void)applicationWillResignActive:(NSNotification *)notification {
[super applicationWillResignActive:notification];
_validResult = NO;
[_stimulusTimer invalidate];
[_timeoutTimer invalidate];
}
- (void)applicationDidBecomeActive:(NSNotification *)notification {
[super applicationDidBecomeActive:notification];
[self resetAfterDelay:0];
}
#pragma mark - ORKRecorderDelegate
- (void)addReactionTimeResult {
ORKNormalizedReactionTimeResult *reactionTimeResult = [[ORKNormalizedReactionTimeResult alloc] initWithIdentifier:self.step.identifier];
reactionTimeResult.timerStartDate = _timerStartDate;
reactionTimeResult.timerEndDate = [NSDate date];
reactionTimeResult.reactionDate = _reactionDate;
reactionTimeResult.stimulusStartDate = _stimulusStartDate;
reactionTimeResult.currentInterval = [self reactionTimeStep].currentInterval;
[_results addObject:reactionTimeResult];
_timerStartDate = nil;
_reactionDate = nil;
_stimulusStartDate = nil;
[self attemptDidFinish];
}
#pragma mark - ORKReactionTimeStepViewController
- (ORKNormalizedReactionTimeStep *)reactionTimeStep {
return (ORKNormalizedReactionTimeStep *)self.step;
}
- (void)configureTitle {
NSString *format = ORKLocalizedString(@"REACTION_TIME_TASK_ATTEMPTS_FORMAT", nil);
NSString *text = [[NSString stringWithFormat: @"%@\n",ORKLocalizedString(@"REACTION_TIME_NORMALIZED_TASK_ACTIVE_STEP_TITLE", nil)] stringByAppendingString: [NSString stringWithFormat:format, ORKLocalizedStringFromNumber(@(_results.count + 1)), ORKLocalizedStringFromNumber(@([self reactionTimeStep].numberOfAttempts))]];
[self.activeStepView updateTitle:nil text:text];
}
- (void)attemptDidFinish {
void (^completion)(void) = ^{
if (_results.count == [self reactionTimeStep].numberOfAttempts) {
[self finish];
} else {
[self resetAfterDelay:2];
}
};
if (_validResult) {
[self indicateSuccess:completion];
} else {
[self indicateFailure:completion];
}
_validResult = NO;
_timedOut = NO;
[_stimulusTimer invalidate];
[_timeoutTimer invalidate];
}
- (void)indicateSuccess:(void(^)(void))completion {
[_reactionTimeContentView startSuccessAnimationWithDuration:OutcomeAnimationDuration completion:completion];
AudioServicesPlaySystemSound([self reactionTimeStep].successSound);
}
- (void)indicateFailure:(void(^)(void))completion {
if (!_shouldIndicateFailure) {
return;
}
[_reactionTimeContentView startFailureAnimationWithDuration:OutcomeAnimationDuration completion:completion];
SystemSoundID sound = _timedOut ? [self reactionTimeStep].timeoutSound : [self reactionTimeStep].failureSound;
AudioServicesPlayAlertSound(sound);
}
- (void)resetAfterDelay:(NSTimeInterval)delay {
ORKWeakTypeOf(self) weakSelf = self;
[_reactionTimeContentView resetAfterDelay:delay completion:^{
[weakSelf configureTitle];
[weakSelf start];
}];
}
- (void)startStimulusTimer {
_stimulusTimer = [NSTimer scheduledTimerWithTimeInterval:[self stimulusInterval] target:self selector:@selector(stimulusTimerDidFire) userInfo:nil repeats:NO];
}
- (void)stimulusTimerDidFire {
_stimulusStartDate = [NSDate date];
_stimulusTimestamp = [NSProcessInfo processInfo].systemUptime;
[_reactionTimeContentView setStimulusHidden:NO];
_validResult = YES;
[self startTimeoutTimer];
}
- (void)startTimeoutTimer {
NSTimeInterval timeout = [self reactionTimeStep].timeout;
if (timeout > 0) {
_timeoutTimer = [NSTimer scheduledTimerWithTimeInterval:timeout target:self selector:@selector(timeoutTimerDidFire) userInfo:nil repeats:NO];
}
}
- (void)timeoutTimerDidFire {
_validResult = NO;
_timedOut = YES;
[self addReactionTimeResult];
#if TARGET_IPHONE_SIMULATOR
// Device motion recorder won't work, so manually trigger didfinish
[self attemptDidFinish];
#endif
}
- (NSTimeInterval)stimulusInterval {
ORKNormalizedReactionTimeStep *step = [self reactionTimeStep];
NSNumber* interval = [self getRandomInterval];
step.currentInterval = interval;
return [interval doubleValue];
}
- (NSNumber*) getRandomInterval {
NSArray* values = @[@2,@4,@6];
int randIndex = arc4random() % [values count];
return (NSNumber*)values[randIndex];
}
@end
@@ -189,7 +189,7 @@
*/
- (double)getDeviceAngleInDegreesFromAttitude:(CMAttitude *)attitude {
if (!_orientation) {
_orientation = [UIApplication sharedApplication].statusBarOrientation;
_orientation = self.view.window.windowScene.interfaceOrientation;
}
double angle;
if (UIInterfaceOrientationIsLandscape(_orientation)) {
@@ -260,7 +260,7 @@
- (void)setButtonItem:(ORKBorderedButton *)buttonItem {
_buttonItem = buttonItem;
if (buttonItem) {
buttonItem.contentEdgeInsets = (UIEdgeInsets){.top = 2, .bottom = 2, .left = 8, .right = 8};
[buttonItem updateContentInsets:NSDirectionalEdgeInsetsMake(2, 8, 2, 8)];
buttonItem.translatesAutoresizingMaskIntoConstraints = NO;
[_continueView addSubview:buttonItem];
[[NSLayoutConstraint constraintWithItem:_buttonItem
@@ -45,6 +45,11 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic) ORKBorderedButton * GButton;
@property (nonatomic) ORKBorderedButton * BButton;
@property (nonatomic) ORKBorderedButton * YButton;
@property (nonatomic) BOOL useTextForStimuli;
@property (nonatomic) BOOL useGridLayoutForButtons;
- (void)setUseGridLayoutForButtons:(bool)useGridLayoutForButtons;
- (void)setUseTextForStimuli:(bool)useTextForStimuli;
@end
NS_ASSUME_NONNULL_END
+164 -43
View File
@@ -36,9 +36,13 @@
#import "ORKBorderedButton.h"
static const CGFloat minimumButtonHeight = 60;
CGFloat minimumButtonHeight = 60;
UILayoutConstraintAxis alignment = UILayoutConstraintAxisHorizontal;
CGFloat labelHeight = 250.0;
CGFloat labelWidth = 250.0;
static const CGFloat buttonStackViewSpacing = 20.0;
@implementation ORKStroopContentView {
UILabel *_colorLabel;
UIStackView *_buttonStackView;
@@ -52,41 +56,126 @@ static const CGFloat buttonStackViewSpacing = 20.0;
_colorLabel.numberOfLines = 1;
_colorLabel.textAlignment = NSTextAlignmentCenter;
_colorLabel.translatesAutoresizingMaskIntoConstraints = NO;
[_colorLabel setFont:[UIFont systemFontOfSize:60]];
[_colorLabel setAdjustsFontSizeToFitWidth:YES];
ORKScreenType screenType = ORKGetVerticalScreenTypeForWindow([[[UIApplication sharedApplication] delegate] window]);
_RButton = [[ORKBorderedButton alloc] init];
_RButton.translatesAutoresizingMaskIntoConstraints = NO;
[_RButton setTitle:ORKLocalizedString(@"STROOP_COLOR_RED_INITIAL", nil) forState:UIControlStateNormal];
_GButton = [[ORKBorderedButton alloc] init];
_GButton.translatesAutoresizingMaskIntoConstraints = NO;
[_GButton setTitle:ORKLocalizedString(@"STROOP_COLOR_GREEN_INITIAL", nil) forState:UIControlStateNormal];
_BButton = [[ORKBorderedButton alloc] init];
_BButton.translatesAutoresizingMaskIntoConstraints = NO;
[_BButton setTitle:ORKLocalizedString(@"STROOP_COLOR_BLUE_INITIAL", nil) forState:UIControlStateNormal];
_YButton = [[ORKBorderedButton alloc] init];
_YButton.translatesAutoresizingMaskIntoConstraints = NO;
[_YButton setTitle:ORKLocalizedString(@"STROOP_COLOR_YELLOW_INITIAL", nil) forState:UIControlStateNormal];
if (!_buttonStackView) {
_buttonStackView = [[UIStackView alloc] initWithArrangedSubviews:@[_RButton, _GButton, _BButton, _YButton]];
if (screenType == ORKScreenTypeiPhone5 ) {
labelWidth = 200.0;
labelHeight = 200.0;
} else {
labelWidth = 250.0;
labelHeight = 250.0;
}
_buttonStackView.translatesAutoresizingMaskIntoConstraints = NO;
_buttonStackView.spacing = buttonStackViewSpacing;
_buttonStackView.axis = UILayoutConstraintAxisHorizontal;
[self setupDefaultButtons];
[self addSubview:_colorLabel];
[self addSubview:_buttonStackView];
[self setUpConstraints];
}
return self;
}
-(void)setupButtons {
_RButton = [[ORKBorderedButton alloc] init];
[_RButton setNormalTintColor:[UIColor blackColor]];
_RButton.translatesAutoresizingMaskIntoConstraints = NO;
[_RButton setTitle:ORKLocalizedString(@"STROOP_COLOR_RED_INITIAL", nil) forState:UIControlStateNormal];
_GButton = [[ORKBorderedButton alloc] init];
[_GButton setNormalTintColor:[UIColor blackColor]];
_GButton.translatesAutoresizingMaskIntoConstraints = NO;
[_GButton setTitle:ORKLocalizedString(@"STROOP_COLOR_GREEN_INITIAL", nil) forState:UIControlStateNormal];
_BButton = [[ORKBorderedButton alloc] init];
[_BButton setNormalTintColor:[UIColor blackColor]];
_BButton.translatesAutoresizingMaskIntoConstraints = NO;
[_BButton setTitle:ORKLocalizedString(@"STROOP_COLOR_BLUE_INITIAL", nil) forState:UIControlStateNormal];
_YButton = [[ORKBorderedButton alloc] init];
[_YButton setNormalTintColor:[UIColor blackColor]];
_YButton.translatesAutoresizingMaskIntoConstraints = NO;
[_YButton setTitle:ORKLocalizedString(@"STROOP_COLOR_YELLOW_INITIAL", nil) forState:UIControlStateNormal];
}
-(void)setupDefaultButtons {
[self setupButtons];
if (!_buttonStackView) {
_buttonStackView = [[UIStackView alloc] initWithArrangedSubviews:@[_RButton, _GButton, _BButton, _YButton]];
alignment = UILayoutConstraintAxisHorizontal;
}
minimumButtonHeight = 60;
_buttonStackView.translatesAutoresizingMaskIntoConstraints = NO;
_buttonStackView.spacing = buttonStackViewSpacing;
_buttonStackView.axis = alignment;
[self addSubview:_buttonStackView];
}
-(void)setupGridButtons {
if (_useGridLayoutForButtons) {
[self setupButtons];
[_buttonStackView removeFromSuperview];
UIStackView* stack1 = [[UIStackView alloc] initWithArrangedSubviews:@[_RButton, _GButton]];
UIStackView* stack2 = [[UIStackView alloc] initWithArrangedSubviews:@[_BButton, _YButton]];
stack1.translatesAutoresizingMaskIntoConstraints = NO;
stack1.spacing = buttonStackViewSpacing;
stack1.axis = UILayoutConstraintAxisHorizontal;
stack2.translatesAutoresizingMaskIntoConstraints = NO;
stack2.spacing = buttonStackViewSpacing;
stack2.axis = UILayoutConstraintAxisHorizontal;
_buttonStackView = [[UIStackView alloc] initWithArrangedSubviews:@[stack1,stack2]];
_buttonStackView.axis = UILayoutConstraintAxisVertical;
ORKScreenType screenType = ORKGetVerticalScreenTypeForWindow([[[UIApplication sharedApplication] delegate] window]);
if (screenType == ORKScreenTypeiPhone6) {
minimumButtonHeight = 150.0;
} else if (screenType == ORKScreenTypeiPhone5 ) {
minimumButtonHeight = 100.0;
} else {
minimumButtonHeight = 200;
}
alignment = UILayoutConstraintAxisVertical;
_buttonStackView.translatesAutoresizingMaskIntoConstraints = NO;
_buttonStackView.spacing = buttonStackViewSpacing;
_buttonStackView.axis = alignment;
[self addSubview:_buttonStackView];
[self setUpConstraints];
}
}
- (void)setUseGridLayoutForButtons:(bool)useGridLayoutForButtons{
_useGridLayoutForButtons = useGridLayoutForButtons;
[self setupGridButtons];
}
-(void)setUseTextForStimuli:(bool)useTextForStimuli{
_useTextForStimuli = useTextForStimuli;
if (!_useTextForStimuli) {
[_colorLabel setFont:[UIFont boldSystemFontOfSize:60]];
}
}
- (void)setColorLabelText:(NSString *)colorLabelText {
[_colorLabel setText:colorLabelText];
[self setNeedsDisplay];
@@ -94,6 +183,9 @@ static const CGFloat buttonStackViewSpacing = 20.0;
- (void)setColorLabelColor:(UIColor *)colorLabelColor {
[_colorLabel setTextColor:colorLabelColor];
if (!_useTextForStimuli) {
[_colorLabel setBackgroundColor:colorLabelColor];
}
[self setNeedsDisplay];
}
@@ -106,31 +198,60 @@ static const CGFloat buttonStackViewSpacing = 20.0;
}
- (void)setUpConstraints {
NSMutableArray *constraints = [[NSMutableArray alloc] init];
NSDictionary *views = NSDictionaryOfVariableBindings(_colorLabel, _buttonStackView);
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(==30)-[_colorLabel]-(>=10)-[_buttonStackView]-(==30)-|"
int bottomStackViewSpace = _useGridLayoutForButtons ? 90 : 30;
NSString * constraintString = [NSString stringWithFormat: @"V:|-(==30)-[_colorLabel]-(>=10)-[_buttonStackView]-(==%d)-|", bottomStackViewSpace];
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:constraintString
options:NSLayoutFormatAlignAllCenterX
metrics:nil
views:views]];
[constraints addObjectsFromArray:@[
[NSLayoutConstraint constraintWithItem:_buttonStackView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:minimumButtonHeight],
[NSLayoutConstraint constraintWithItem:_buttonStackView
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0.0]
]];
NSArray *baseLayouts = @[[NSLayoutConstraint constraintWithItem:_buttonStackView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:minimumButtonHeight],
[NSLayoutConstraint constraintWithItem:_buttonStackView
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0.0]];
[constraints addObjectsFromArray:baseLayouts];
if (!_useTextForStimuli) {
[constraints addObjectsFromArray: @[[NSLayoutConstraint constraintWithItem:_colorLabel
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:labelWidth],
[NSLayoutConstraint constraintWithItem:_colorLabel
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant: labelHeight],
[NSLayoutConstraint constraintWithItem:_buttonStackView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:minimumButtonHeight]]];
}
for (ORKBorderedButton *button in @[_RButton, _GButton, _BButton, _YButton]) {
[constraints addObject:[NSLayoutConstraint constraintWithItem:button
attribute:NSLayoutAttributeWidth
+24
View File
@@ -39,6 +39,30 @@ ORK_CLASS_AVAILABLE
@property (nonatomic, assign) NSInteger numberOfAttempts;
/**
A Boolean value indicating whether this task randomizes the visual and color of each stroop question.
This means that the color of the text displayed and the text may not match, which makes for a harder stroop test.
By default, this property is set to `YES`
*/
@property (nonatomic, assign) BOOL randomizeVisualAndColorAlignment;
/**
A Boolean value indicating whether this task should use text or boxes.
If set to `YES` then color words will be displayed for the user to guess.
If set to `NO` we will display a square box with the current color for the user to guess
By default, this property is set to `YES`
*/
@property (nonatomic, assign) BOOL useTextForStimuli;
/**
A Boolean value indicating whether this task will use a 2x2 grid of buttons
By default, this property is set to `NO`
*/
@property (nonatomic, assign) BOOL useGridLayoutForButtons;
@end
NS_ASSUME_NONNULL_END
+4 -5
View File
@@ -51,13 +51,16 @@
self.shouldShowDefaultTimer = NO;
self.shouldContinueOnFinish = YES;
self.stepDuration = NSIntegerMax;
self.randomizeVisualAndColorAlignment = YES;
self.useTextForStimuli = YES;
self.useGridLayoutForButtons = NO;
}
return self;
}
- (void)validateParameters {
[super validateParameters];
NSInteger minimumAttempts = 10;
NSInteger minimumAttempts = 3;
if (self.numberOfAttempts < minimumAttempts) {
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"number of attempts should be greater or equal to %ld.", (long)minimumAttempts] userInfo:nil];
}
@@ -67,10 +70,6 @@
return NO;
}
- (BOOL)allowsBackNavigation {
return NO;
}
- (instancetype)copyWithZone:(NSZone *)zone {
ORKStroopStep *step = [super copyWithZone:zone];
step.numberOfAttempts = self.numberOfAttempts;
@@ -40,9 +40,10 @@
#import "ORKStroopStep.h"
#import "ORKHelpers_Internal.h"
#import "ORKBorderedButton.h"
#import "ORKNavigationContainerView.h"
#import "ORKTaskViewController_Private.h"
#import "ORKNavigationContainerView_Internal.h"
@interface ORKStroopStepViewController ()
@property (nonatomic, strong) ORKStroopContentView *stroopContentView;
@@ -64,6 +65,8 @@
NSString *_yellowString;
NSTimer *_nextQuestionTimer;
NSTimer *_timeoutTimer;
NSMutableArray *_results;
NSTimeInterval _startTime;
NSTimeInterval _endTime;
@@ -93,7 +96,7 @@
_red = [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:1.0];
_green = [UIColor colorWithRed:0.0 green:1.0 blue:0.0 alpha:1.0];
_blue = [UIColor colorWithRed:0.0 green:0.0 blue:1.0 alpha:1.0];
_yellow = [UIColor colorWithRed:1.0 green:1.0 blue:0.0 alpha:1.0];
_yellow = [UIColor colorWithRed:245.0/225.0 green:221.0/225.0 blue:66.0/255.0 alpha:1.0];
self.colors = @{
_redString: _red,
@@ -111,6 +114,21 @@
self.questionNumber = 0;
_stroopContentView = [ORKStroopContentView new];
[_stroopContentView setUseTextForStimuli: [self stroopStep].useTextForStimuli];
[_stroopContentView setUseGridLayoutForButtons: [self stroopStep].useGridLayoutForButtons];
if ([self stroopStep].useGridLayoutForButtons) {
[_navigationFooterView setHidden:true];
_timeoutTimer = [NSTimer scheduledTimerWithTimeInterval:30
target:self
selector:@selector(timeOut)
userInfo:nil
repeats:NO];
}
self.activeStepView.activeCustomView = _stroopContentView;
[self.stroopContentView.RButton addTarget:self
@@ -148,9 +166,45 @@
selector:@selector(startNextQuestionOrFinish)
userInfo:nil
repeats:NO];
if ([self stroopStep].useGridLayoutForButtons) {
[_timeoutTimer invalidate];
_timeoutTimer = [NSTimer scheduledTimerWithTimeInterval:30
target:self
selector:@selector(timeOut)
userInfo:nil
repeats:NO];
}
}
}
- (void)timeOut {
UIAlertController* controller = [UIAlertController alertControllerWithTitle: ORKLocalizedString(@"TIME_OUT_TILE", nil)
message: ORKLocalizedString(@"TIME_OUT_BODY", nil) preferredStyle:UIAlertControllerStyleAlert];
[controller addAction:[UIAlertAction actionWithTitle: ORKLocalizedString(@"TIME_OUT_RESTART_ACTION", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[[self taskViewController] flipToFirstPage];
}]];
[controller addAction:[UIAlertAction actionWithTitle: ORKLocalizedString(@"TIME_OUT_END_ACTION", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
ORKStrongTypeOf(self.taskViewController.delegate) strongDelegate = self.taskViewController.delegate;
if ([strongDelegate respondsToSelector:@selector(taskViewController:didFinishWithReason:error:)]) {
[strongDelegate taskViewController:self.taskViewController didFinishWithReason:ORKTaskViewControllerFinishReasonDiscarded error:nil];
}
}]];
[self presentViewController:controller animated:true completion:nil];
}
- (void)startNextQuestionTimer {
_nextQuestionTimer = [NSTimer scheduledTimerWithTimeInterval:0.3
target:self
selector:@selector(startNextQuestionOrFinish)
userInfo:nil
repeats:NO];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self start];
@@ -198,27 +252,36 @@
}
- (void)startQuestion {
int pattern = arc4random() % 2;
if (pattern == 0) {
if ([self stroopStep].randomizeVisualAndColorAlignment) {
int pattern = arc4random() % 2;
if (pattern == 0) {
int index = arc4random() % [self.colors.allKeys count];
NSString *text = [self.colors.allKeys objectAtIndex:index];
self.stroopContentView.colorLabelText = text;
UIColor *color = [self.colors valueForKey:text];
self.stroopContentView.colorLabelColor = color;
}
else {
int index = arc4random() % [self.differentColorLabels.allKeys count];
NSString *text = [self.differentColorLabels.allKeys objectAtIndex:index];
self.stroopContentView.colorLabelText = text;
NSArray *colorArray = [self.differentColorLabels valueForKey:text];
int randomColor = arc4random() % colorArray.count;
UIColor *color = [colorArray objectAtIndex:randomColor];
self.stroopContentView.colorLabelColor = color;
}
} else {
int index = arc4random() % [self.colors.allKeys count];
NSString *text = [self.colors.allKeys objectAtIndex:index];
self.stroopContentView.colorLabelText = text;
UIColor *color = [self.colors valueForKey:text];
self.stroopContentView.colorLabelColor = color;
}
else {
int index = arc4random() % [self.differentColorLabels.allKeys count];
NSString *text = [self.differentColorLabels.allKeys objectAtIndex:index];
self.stroopContentView.colorLabelText = text;
NSArray *colorArray = [self.differentColorLabels valueForKey:text];
int randomColor = arc4random() % colorArray.count;
UIColor *color = [colorArray objectAtIndex:randomColor];
self.stroopContentView.colorLabelColor = color;
}
[self setButtonsEnabled];
_startTime = [NSProcessInfo processInfo].systemUptime;
}
- (void)setButtonsDisabled {
[self.stroopContentView.RButton setEnabled: NO];
[self.stroopContentView.GButton setEnabled: NO];
@@ -140,7 +140,7 @@
[self setContinueEnabled:!self.enableContinueAfterSelection];
_parentView = view;
_spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
_spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleMedium];
[_parentView addSubview:_spinner];
[self setSpinnerConstraints];
[_spinner startAnimating];
-1
View File
@@ -243,7 +243,6 @@ static const CGFloat PieToLegendPadding = 8.0;
- (void)setBounds:(CGRect)bounds {
[super setBounds:bounds];
_shouldInvalidateLegendViewIntrinsicContentSize = YES;
[self setNeedsLayout];
}
+1
View File
@@ -36,6 +36,7 @@
#import <ResearchKit/ORKPSATResult.h>
#import <ResearchKit/ORKRangeOfMotionResult.h>
#import <ResearchKit/ORKReactionTimeResult.h>
#import <ResearchKit/ORKNormalizedReactionTimeResult.h>
#import <ResearchKit/ORKSpatialSpanMemoryResult.h>
#import <ResearchKit/ORKSpeechRecognitionResult.h>
#import <ResearchKit/ORKStroopResult.h>
+23 -9
View File
@@ -53,12 +53,13 @@ static const CGFloat LabelCheckViewPadding = 10.0;
@property (nonatomic) ORKSelectionSubTitleLabel *detailLabel;
@property (nonatomic) ORKCheckmarkView *checkView;
@property (nonatomic) NSMutableArray<NSLayoutConstraint *> *containerConstraints;
@property (nonatomic, readonly) CGFloat leftRightMargin;
@property (nonatomic, readonly) CGFloat intraCellSpacing;
@end
@implementation ORKChoiceViewCell {
CGFloat _leftRightMargin;
CGFloat _topBottomMargin;
CAShapeLayer *_contentMaskLayer;
UIColor *_fillColor;
@@ -71,7 +72,6 @@ static const CGFloat LabelCheckViewPadding = 10.0;
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
self.clipsToBounds = YES;
_leftRightMargin = 0.0;
_topBottomMargin = 0.0;
[self setupContainerView];
[self setupCheckView];
@@ -84,6 +84,14 @@ static const CGFloat LabelCheckViewPadding = 10.0;
[self setMaskLayers];
}
- (CGFloat)leftRightMargin {
return self.useCardView ? ORKCardLeftRightMarginForWindow(self.window) : 0.0;
}
- (CGFloat)intraCellSpacing {
return 0;
}
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
[super traitCollectionDidChange:previousTraitCollection];
if (@available(iOS 13.0, *)) {
@@ -179,7 +187,7 @@ static const CGFloat LabelCheckViewPadding = 10.0;
if (!_containerView) {
_containerView = [UIView new];
}
[self addSubview:_containerView];
[self.contentView addSubview:_containerView];
}
- (void)addContainerViewToSelfConstraints {
@@ -187,24 +195,31 @@ static const CGFloat LabelCheckViewPadding = 10.0;
[NSLayoutConstraint constraintWithItem:_containerView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self
toItem:self.contentView
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0],
[NSLayoutConstraint constraintWithItem:_containerView
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:self
toItem:self.contentView
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:_leftRightMargin],
constant:self.leftRightMargin],
[NSLayoutConstraint constraintWithItem:_containerView
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:self
toItem:self.contentView
attribute:NSLayoutAttributeRight
multiplier:1.0
constant:-_leftRightMargin]
constant:-self.leftRightMargin],
[NSLayoutConstraint constraintWithItem:_containerView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.contentView
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:-self.intraCellSpacing],
]];
}
@@ -311,7 +326,6 @@ static const CGFloat LabelCheckViewPadding = 10.0;
- (void)setUseCardView:(bool)useCardView {
_useCardView = useCardView;
_leftRightMargin = ORKCardLeftRightMarginForWindow(self.window);
_topBottomMargin = CardTopBottomMargin;
[self setBackgroundColor:[UIColor clearColor]];
self.selectionStyle = UITableViewCellSelectionStyleNone;
+1 -1
View File
@@ -46,7 +46,7 @@ static const CGFloat ContinueButtonHeight = 50.0;
if (self) {
[self setTitle:title forState:UIControlStateNormal];
self.isDoneButton = isDoneButton;
self.contentEdgeInsets = (UIEdgeInsets){.left = 6, .right = 6};
[self updateContentInsets:NSDirectionalEdgeInsetsMake(0, 6, 0, 6)];
[self setUpConstraints];
}
@@ -43,7 +43,6 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, weak, nullable) id<ORKSignatureViewDelegate> signatureViewDelegate;
@property (nonatomic, weak, nullable) id<ORKCustomSignatureAccessoryViewProvider> customViewProvider;
@property (nonatomic, weak, nullable) id<ORKCustomSignatureFooterViewStatusDelegate> delegate;
@property (nonatomic) BOOL enabled;
- (BOOL)isComplete;
@@ -226,10 +226,6 @@ static const CGFloat ORKSignatureToClearPadding = 15.0;
[self setNeedsLayout];
}
- (void)setEnabled:(BOOL)enabled {
[_signatureView setEnabled:enabled];
}
- (void)setSignatureViewDelegate:(id<ORKSignatureViewDelegate>)signatureViewDelegate {
_signatureViewDelegate = signatureViewDelegate;
_signatureView.delegate = signatureViewDelegate;
@@ -197,4 +197,13 @@ ORK_CLASS_AVAILABLE
@end
ORK_CLASS_AVAILABLE
@interface ORKDataCollectionState : NSObject <NSSecureCoding>
@property(nonatomic, nullable, readwrite) NSString *archiveVersion;
@property(nonatomic, nonnull, readwrite) NSArray<ORKCollector *> *collectors;
@end
NS_ASSUME_NONNULL_END
+62 -10
View File
@@ -36,7 +36,9 @@
#import <HealthKit/HealthKit.h>
static NSString *const ORKDataCollectionPersistenceFileName = @".dataCollection.ork.data";
// The file names for persisting the state of our collectors
static NSString *const ORKDataCollectionPersistenceFileNamev1 = @".dataCollection.ork.data"; // pre-secureCoding
static NSString *const ORKDataCollectionPersistenceFileNamev2 = @".dataCollection.ork.archive"; // current
@implementation ORKDataCollectionManager {
dispatch_queue_t _queue;
@@ -133,27 +135,45 @@ static inline void dispatch_sync_if_not_on_queue(dispatch_queue_t queue, dispatc
- (NSArray<ORKCollector *> *)collectors {
if (_collectors == nil) {
_collectors = [NSKeyedUnarchiver unarchiveObjectWithFile:[self persistFilePath]];
if (_collectors == nil) {
@throw [NSException exceptionWithName:NSGenericException reason: [NSString stringWithFormat:@"Failed to read from path %@", [self persistFilePath]] userInfo:nil];
NSError *error;
NSData *data = [NSData dataWithContentsOfFile:[self persistFilePath]];
ORKDataCollectionState *state = [NSKeyedUnarchiver unarchivedObjectOfClass:ORKDataCollectionState.self fromData:data error:&error];
if (state == nil) {
NSDictionary *userInfo = @{NSUnderlyingErrorKey:error};
@throw [NSException exceptionWithName:NSGenericException reason: [NSString stringWithFormat:@"Failed to read from path %@", [self persistFilePath]] userInfo:userInfo];
} else {
_collectors = state.collectors ? : @[];
}
}
return _collectors;
}
- (NSString * _Nonnull)persistFilePath {
return [_managedDirectory stringByAppendingPathComponent:ORKDataCollectionPersistenceFileName];
return [_managedDirectory stringByAppendingPathComponent:ORKDataCollectionPersistenceFileNamev2];
}
- (void)persistCollectors {
NSArray *collectors = self.collectors;
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:collectors];
NSError *error;
[data writeToFile:[self persistFilePath] options:NSDataWritingAtomic|NSDataWritingFileProtectionComplete error:&error];
NSError *error = nil;
ORKDataCollectionState* state = [[ORKDataCollectionState alloc] init];
state.collectors = collectors;
state.archiveVersion = (NSString *)[[NSBundle bundleForClass:ORKDataCollectionManager.self] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:state requiringSecureCoding:YES error:&error];
if (error) {
@throw [NSException exceptionWithName:NSGenericException reason: [NSString stringWithFormat:@"Failed to write to path %@", [self persistFilePath]] userInfo:nil];
if (data == nil) {
NSDictionary *userInfo = @{NSUnderlyingErrorKey:error};
@throw [NSException exceptionWithName:NSGenericException reason:@"Failed to archive collectors" userInfo:userInfo];
}
error = nil;
BOOL success = [data writeToFile:[self persistFilePath] options:NSDataWritingAtomic|NSDataWritingFileProtectionComplete error:&error];
if (success != YES) {
NSDictionary *userInfo = @{NSUnderlyingErrorKey:error};
@throw [NSException exceptionWithName:NSGenericException reason: [NSString stringWithFormat:@"Failed to write to path %@", [self persistFilePath]] userInfo:userInfo];
}
}
@@ -340,3 +360,35 @@ static inline void dispatch_sync_if_not_on_queue(dispatch_queue_t queue, dispatc
}
@end
@implementation ORKDataCollectionState
/// Sentinel value for archiveVersion to indicate missing version info
+ (NSString *)absentVersionInfoSentinelValue {
return @"0.0.0";
}
+ (BOOL)supportsSecureCoding {
return YES;
}
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder {
if (aDecoder.allowsKeyedCoding != YES) {
return nil;
}
self = [super init];
ORK_DECODE_OBJ_ARRAY(aDecoder, collectors, ORKCollector);
ORK_DECODE_OBJ_CLASS(aDecoder, archiveVersion, NSString);
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
ORK_ENCODE_OBJ(aCoder, archiveVersion ? : [self.class absentVersionInfoSentinelValue]);
ORK_ENCODE_OBJ(aCoder, collectors);
}
@end
+3
View File
@@ -74,6 +74,9 @@
if (_pickerView == nil) {
_pickerView = [[UIDatePicker alloc] init];
[_pickerView addTarget:self action:@selector(valueDidChange:) forControlEvents:UIControlEventValueChanged];
if (@available(iOS 13.4, *)) {
_pickerView.preferredDatePickerStyle = UIDatePickerStyleWheels;
}
self.answerFormat = _answerFormat;
self.answer = _answer;
}
+55 -78
View File
@@ -28,20 +28,16 @@
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "ORKRequestPermissionButton.h"
#import "ORKHealthKitPermissionType.h"
#import "ORKHelpers_Internal.h"
#import "ORKRequestPermissionView.h"
#import <HealthKit/HealthKit.h>
typedef NS_CLOSED_ENUM(NSInteger, ORKRequestPermissionsButtonState) {
ORKRequestPermissionsButtonStateDefault = 0,
ORKRequestPermissionsButtonStateConnected,
ORKRequestPermissionsButtonStateNotSupported,
ORKRequestPermissionsButtonStateError,
} ORK_ENUM_AVAILABLE;
static NSString *const Symbol = @"heart.fill";
static uint32_t const IconTintColor = 0xFF5E5E;
@implementation ORKHealthKitPermissionType {
UIButton *_requestPermissionButton;
}
@implementation ORKHealthKitPermissionType
+ (instancetype)new {
ORKThrowMethodUnavailableException();
@@ -68,100 +64,81 @@ typedef NS_CLOSED_ENUM(NSInteger, ORKRequestPermissionsButtonState) {
UIImage *image;
if (@available(iOS 13.0, *)) {
image = [UIImage systemImageNamed:@"heart.fill"];
image = [UIImage systemImageNamed:Symbol];
}
self.cardView = [[ORKRequestPermissionView alloc] initWithIconImage:image
title:ORKLocalizedString(@"REQUEST_HEALTH_DATA_STEP_VIEW_TITLE", nil)
detailText:ORKLocalizedString(@"REQUEST_HEALTH_DATA_STEP_VIEW_DESCRIPTION", nil)];
_requestPermissionButton = self.cardView.requestPermissionButton;
[_requestPermissionButton addTarget:self action:@selector(requestPermissionButtonPressed) forControlEvents:UIControlEventTouchUpInside];
[self.cardView updateIconTintColor:[UIColor redColor]];
[self setState:ORKRequestPermissionsButtonStateDefault canContinue:NO];
[self.cardView.requestPermissionButton addTarget:self action:@selector(requestPermissionButtonPressed) forControlEvents:UIControlEventTouchUpInside];
[self.cardView updateIconTintColor:ORKRGB(IconTintColor)];
}
- (void)checkHealthKitAuthorizationStatus {
if (![HKHealthStore isHealthDataAvailable]) {
[self setRequestPermissionsButtonState:ORKRequestPermissionsButtonStateNotSupported];
[self setState:ORKRequestPermissionsButtonStateNotSupported canContinue:YES];
return;
}
if (@available(iOS 12.0, *)) {
[[HKHealthStore new] getRequestStatusForAuthorizationToShareTypes:_sampleTypesToWrite readTypes:_objectTypesToRead completion:^(HKAuthorizationRequestStatus requestStatus, NSError * _Nullable error) {
if (error) {
[self setRequestPermissionsButtonState:ORKRequestPermissionsButtonStateError];
return;
}
if (requestStatus == HKAuthorizationRequestStatusShouldRequest) {
[self setRequestPermissionsButtonState:ORKRequestPermissionsButtonStateDefault];
} else if (requestStatus == HKAuthorizationRequestStatusUnnecessary) {
[self setRequestPermissionsButtonState:ORKRequestPermissionsButtonStateConnected];
}
dispatch_async(dispatch_get_main_queue(), ^{
if (error) {
[self setState:ORKRequestPermissionsButtonStateDefault canContinue:NO];
return;
}
switch (requestStatus) {
case HKAuthorizationStatusSharingAuthorized:
[self setState:ORKRequestPermissionsButtonStateConnected canContinue:YES];
break;
case HKAuthorizationRequestStatusShouldRequest:
case HKAuthorizationRequestStatusUnknown:
[self setState:ORKRequestPermissionsButtonStateDefault canContinue:NO];
break;
}
});
}];
} else {
[self setRequestPermissionsButtonState:ORKRequestPermissionsButtonStateDefault];
[self setState:ORKRequestPermissionsButtonStateDefault canContinue:NO];
}
}
- (void)setRequestPermissionsButtonState:(ORKRequestPermissionsButtonState)state {
dispatch_async(dispatch_get_main_queue(), ^{
switch (state) {
case ORKRequestPermissionsButtonStateDefault:
[self updateRequestButtonWithText:ORKLocalizedString(@"REQUEST_HEALTH_DATA_STEP_BUTTON_STATE_DEFAULT", nil) backgroundColor:[UIColor systemBlueColor]];
[self setEnableContinue:NO];
break;
case ORKRequestPermissionsButtonStateConnected:
[self updateRequestButtonWithText:ORKLocalizedString(@"REQUEST_HEALTH_DATA_STEP_BUTTON_STATE_CONNECTED", nil) backgroundColor:[UIColor grayColor]];
[self setEnableContinue:YES];
break;
case ORKRequestPermissionsButtonStateNotSupported:
[self updateRequestButtonWithText:ORKLocalizedString(@"REQUEST_HEALTH_DATA_STEP_BUTTON_STATE_NOT_SUPPORTED", nil) backgroundColor:[UIColor redColor]];
[self setEnableContinue:YES];
break;
case ORKRequestPermissionsButtonStateError:
[self updateRequestButtonWithText:ORKLocalizedString(@"REQUEST_HEALTH_DATA_STEP_BUTTON_STATE_ERROR", nil) backgroundColor:[UIColor redColor]];
[self setEnableContinue:YES];
break;
default:
break;
}
});
}
- (void)updateRequestButtonWithText:(NSString *)text backgroundColor:(UIColor *)backgroundColor {
if (_requestPermissionButton) {
[_requestPermissionButton setTitle:text forState:UIControlStateNormal];
[_requestPermissionButton setBackgroundColor:backgroundColor];
}
}
- (void)requestPermissionButtonPressed {
[[HKHealthStore new] requestAuthorizationToShareTypes:_sampleTypesToWrite readTypes:_objectTypesToRead completion:^(BOOL success, NSError * _Nullable error) {
if (error) {
[self setRequestPermissionsButtonState:ORKRequestPermissionsButtonStateError];
return;
}
if (success) {
[self setRequestPermissionsButtonState:ORKRequestPermissionsButtonStateConnected];
} else {
[self setRequestPermissionsButtonState:ORKRequestPermissionsButtonStateError];
}
dispatch_async(dispatch_get_main_queue(), ^{
if (error) {
[self setState:ORKRequestPermissionsButtonStateError canContinue:YES];
return;
}
[self setState:ORKRequestPermissionsButtonStateConnected canContinue:YES];
});
}];
}
- (void)setEnableContinue:(BOOL)enableContinue {
if (self.cardView) {
[self.cardView setEnableContinueButton:enableContinue];
- (void)setState:(ORKRequestPermissionsButtonState)state canContinue:(BOOL)canContinue {
[self.cardView setEnableContinueButton:canContinue];
[self.cardView.requestPermissionButton setState:state];
}
- (BOOL)isEqual:(id)object {
if ([self class] != [object class]) {
return NO;
}
__typeof(self) castObject = object;
return
ORKEqualObjects(self.objectTypesToRead, castObject.objectTypesToRead) &&
ORKEqualObjects(self.sampleTypesToWrite, castObject.sampleTypesToWrite);
}
@end
+2 -2
View File
@@ -236,8 +236,8 @@ UIFont *ORKThinFontWithSize(CGFloat size);
UIFont *ORKLightFontWithSize(CGFloat size);
UIFont *ORKMediumFontWithSize(CGFloat size);
NSURL *ORKURLFromBookmarkData(NSData *data);
NSData *ORKBookmarkDataFromURL(NSURL *url);
NSURL * _Nullable ORKURLFromBookmarkData(NSData *data);
NSData * _Nullable ORKBookmarkDataFromURL(NSURL *url);
NSString *ORKPathRelativeToURL(NSURL *url, NSURL *baseURL);
NSURL *ORKURLForRelativePath(NSString *relativePath);
@@ -177,7 +177,8 @@
photoSettings = [AVCapturePhotoSettings photoSettingsWithRawPixelFormatType:rawPixelFormatType
processedFormat:@{AVVideoCodecKey: AVVideoCodecTypeJPEG}];
}
[photoSettings setAutoStillImageStabilizationEnabled:NO];
[photoSettings setPhotoQualityPrioritization:AVCapturePhotoQualityPrioritizationSpeed];
[photoSettings setFlashMode:(([_photoOutput.supportedFlashModes containsObject:[NSNumber numberWithInt:AVCaptureFlashModeOn]] ? AVCaptureFlashModeAuto : AVCaptureFlashModeOff))];
return photoSettings;
@@ -194,7 +195,7 @@
_imageDataExtension = @"jpeg";
}
[photoSettings setAutoStillImageStabilizationEnabled: [_photoOutput isStillImageStabilizationSupported]];
[photoSettings setPhotoQualityPrioritization:AVCapturePhotoQualityPrioritizationSpeed];
[photoSettings setFlashMode:(([_photoOutput.supportedFlashModes containsObject:[NSNumber numberWithInt:AVCaptureFlashModeOn]] ? AVCaptureFlashModeAuto : AVCaptureFlashModeOff))];
return photoSettings;
@@ -290,6 +291,14 @@
[super viewWillDisappear:animated];
}
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
if (_imageCaptureView) {
[_imageCaptureView orientationDidChange];
}
}
- (void)queue_SetupCaptureSession {
// Create the session
_captureSession = [[AVCaptureSession alloc] init];
+2
View File
@@ -56,6 +56,8 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, strong, nullable) UIImage *capturedImage;
@property (nonatomic, strong, nullable) NSError *error;
- (void)orientationDidChange;
@end
NS_ASSUME_NONNULL_END
+36 -19
View File
@@ -78,7 +78,6 @@
NSDictionary *dictionary = NSDictionaryOfVariableBindings(self, _previewView, _navigationFooterView, _headerView);
ORKEnableAutoLayoutForViews(dictionary.allValues);
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationDidChange) name:UIApplicationDidChangeStatusBarOrientationNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(queue_sessionRunning) name:AVCaptureSessionDidStartRunningNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sessionWasInterrupted:) name:AVCaptureSessionWasInterruptedNotification object:self.session];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sessionInterruptionEnded:) name:AVCaptureSessionInterruptionEndedNotification object:self.session];
@@ -101,24 +100,29 @@
- (void)orientationDidChange {
dispatch_async(dispatch_get_main_queue(), ^{
AVCaptureVideoOrientation orientation = AVCaptureVideoOrientationPortrait;
switch ([[UIApplication sharedApplication] statusBarOrientation]) {
case UIInterfaceOrientationLandscapeRight:
orientation = AVCaptureVideoOrientationLandscapeRight;
break;
case UIInterfaceOrientationLandscapeLeft:
orientation = AVCaptureVideoOrientationLandscapeLeft;
break;
case UIInterfaceOrientationPortraitUpsideDown:
orientation = AVCaptureVideoOrientationPortraitUpsideDown;
break;
case UIInterfaceOrientationPortrait:
orientation = AVCaptureVideoOrientationPortrait;
break;
case UIInterfaceOrientationUnknown:
// Do nothing in these cases, since we don't need to change display orientation.
return;
}
UIWindowScene *windowScene = self.window.windowScene;
if (windowScene) {
switch (windowScene.interfaceOrientation) {
case UIInterfaceOrientationLandscapeRight:
orientation = AVCaptureVideoOrientationLandscapeRight;
break;
case UIInterfaceOrientationLandscapeLeft:
orientation = AVCaptureVideoOrientationLandscapeLeft;
break;
case UIInterfaceOrientationPortraitUpsideDown:
orientation = AVCaptureVideoOrientationPortraitUpsideDown;
break;
case UIInterfaceOrientationPortrait:
orientation = AVCaptureVideoOrientationPortrait;
break;
case UIInterfaceOrientationUnknown:
// Do nothing in these cases, since we don't need to change display orientation.
return;
}
}
[_previewView setVideoOrientation:orientation];
[self.delegate videoOrientationDidChange:orientation];
[self setNeedsUpdateConstraints];
@@ -226,7 +230,13 @@
views:views]];
// Float the continue view over the previewView if in landscape to give more room for the preview
if (UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)) {
UIWindowScene *windowScene = self.window.windowScene;
if (windowScene == nil){
return;
}
if (UIInterfaceOrientationIsLandscape(windowScene.interfaceOrientation)) {
[_variableConstraints addObjectsFromArray:
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_previewView]|"
options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil
@@ -248,6 +258,13 @@
_navigationFooterView.backgroundColor = [_navigationFooterView.backgroundColor colorWithAlphaComponent:NavigationFooterViewOpaqueAlpha];
}
[_variableConstraints addObjectsFromArray:
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_previewView]-[_navigationFooterView]|"
options:NSLayoutFormatDirectionLeadingToTrailing
metrics:nil
views:views]];
_navigationFooterView.backgroundColor = [_navigationFooterView.backgroundColor colorWithAlphaComponent:NavigationFooterViewOpaqueAlpha];
[NSLayoutConstraint activateConstraints:_variableConstraints];
[super updateConstraints];
}
+1 -1
View File
@@ -66,7 +66,7 @@ ORK_CLASS_AVAILABLE
@return An object or `nil` if key is not valid.
*/
+ (nullable id<NSSecureCoding>)objectForKey:(nonnull NSString *)key error:(NSError * __autoreleasing _Nullable *)error;
+ (nullable id<NSSecureCoding>)objectOfClass:(Class)objectClass forKey:(NSString *)key error:(NSError * __autoreleasing _Nullable *)errorOut;
/**
Removes the object in the keychain for the provided key.
+11 -8
View File
@@ -50,7 +50,9 @@ static NSString *ORKKeychainWrapperDefaultService() {
+ (BOOL)setObject:(id<NSSecureCoding>)object
forKey:(NSString *)key
error:(NSError **)errorOut {
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:object];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:object
requiringSecureCoding:YES
error:errorOut];
return [self setData:data
forKey:key
service:ORKKeychainWrapperDefaultService()
@@ -58,13 +60,14 @@ static NSString *ORKKeychainWrapperDefaultService() {
error:errorOut];
}
+ (id<NSSecureCoding>)objectForKey:(NSString *)key
error:(NSError **)errorOut {
NSData *data = [self dataForKey:key
service:ORKKeychainWrapperDefaultService()
accessGroup:nil
error:errorOut];
return data ? [NSKeyedUnarchiver unarchiveObjectWithData:data] : nil;
+ (id<NSSecureCoding>)objectOfClass:(Class)objectClass forKey:(NSString *)key
error:(NSError **)errorOut {
NSData *data = [self dataForKey:key
service:ORKKeychainWrapperDefaultService()
accessGroup:nil
error:errorOut];
return data ? [NSKeyedUnarchiver unarchivedObjectOfClass:objectClass fromData:data error:errorOut] : nil;
}
+ (BOOL)removeObjectForKey:(NSString *)key
+9 -1
View File
@@ -60,7 +60,15 @@ ORK_CLASS_AVAILABLE
button.titleLabel.numberOfLines = 0;
button.titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
button.titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
[button setContentEdgeInsets:UIEdgeInsetsMake(CGFLOAT_MIN, CGFLOAT_MIN, CGFLOAT_MIN, CGFLOAT_MIN)];
if (@available(iOS 15.0, *)) {
UIButtonConfiguration *buttonConfig = [UIButtonConfiguration plainButtonConfiguration];
[buttonConfig setContentInsets:NSDirectionalEdgeInsetsMake(0, 0, 0, 0)];
[button setConfiguration:buttonConfig];
} else {
[button setContentEdgeInsets:UIEdgeInsetsMake(CGFLOAT_MIN, CGFLOAT_MIN, CGFLOAT_MIN, CGFLOAT_MIN)];
}
return button;
}
+11 -5
View File
@@ -278,14 +278,22 @@ static const NSString *FormattedAddressLines = @"FormattedAddressLines";
- (void)loadCurrentLocationIfNecessary {
if (_useCurrentLocation) {
CLAuthorizationStatus status = [CLLocationManager authorizationStatus];
CLAuthorizationStatus status = kCLAuthorizationStatusNotDetermined;
if (@available(iOS 14.0, *)) {
status = _locationManager.authorizationStatus;
} else {
status = [CLLocationManager authorizationStatus];
}
if (status == kCLAuthorizationStatusAuthorizedAlways || status == kCLAuthorizationStatusAuthorizedWhenInUse) {
_userLocationNeedsUpdate = YES;
_mapView.showsUserLocation = YES;
} else {
} else if (_locationManager == nil) {
_locationManager = [[CLLocationManager alloc] init];
_locationManager.delegate = self;
}
if (status == kCLAuthorizationStatusNotDetermined) {
[_locationManager requestWhenInUseAuthorization];
}
}
@@ -433,10 +441,8 @@ static const NSString *FormattedAddressLines = @"FormattedAddressLines";
# pragma mark CLLocationManagerDelegate
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
if (status == kCLAuthorizationStatusAuthorizedAlways || status == kCLAuthorizationStatusAuthorizedWhenInUse) {
- (void)locationManagerDidChangeAuthorization:(CLLocationManager *)manager {
[self loadCurrentLocationIfNecessary];
}
}
#pragma mark MKMapViewDelegate
@@ -0,0 +1,41 @@
/*
Copyright (c) 2021, 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 <ResearchKit/ORKDefines.h>
#import <ResearchKit/ORKPermissionType.h>
NS_ASSUME_NONNULL_BEGIN
ORK_CLASS_AVAILABLE
@interface ORKMotionActivityPermissionType : ORKPermissionType
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,131 @@
/*
Copyright (c) 2021, 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 "ORKMotionActivityPermissionType.h"
#import "ORKRequestPermissionView.h"
#import "ORKRequestPermissionButton.h"
#import "ORKHelpers_Internal.h"
@import CoreMotion;
static NSString *const Symbol = @"arrow.right.arrow.left.circle";
static const uint32_t IconLightTintColor = 0xEF72D8;
static const uint32_t IconDarkTintColor = 0xEF6FD8;
@interface ORKMotionActivityPermissionType()
@property (nonatomic) CMMotionActivityManager *activityManager;
@end
@implementation ORKMotionActivityPermissionType
+ (instancetype)new {
return [[ORKMotionActivityPermissionType alloc] init];
}
- (instancetype)init
{
self = [super init];
if (self) {
[self setupCardView];
}
return self;
}
- (CMMotionActivityManager *)activityManager {
if (!_activityManager) {
_activityManager = [[CMMotionActivityManager alloc] init];
}
return _activityManager;
}
- (void)setupCardView {
UIImage *image;
if (@available(iOS 13, *)) {
image = [UIImage systemImageNamed:Symbol];
}
self.cardView = [[ORKRequestPermissionView alloc]
initWithIconImage:image
title:ORKLocalizedString(@"REQUEST_MOTION_ACTIVITY_STEP_VIEW_TITLE", nil)
detailText:ORKLocalizedString(@"REQUEST_MOTION_ACTIVITY_STEP_VIEW_DESCRIPTION", nil)];
[self.cardView.requestPermissionButton addTarget:self action:@selector(requestPermissionButtonPressed) forControlEvents:UIControlEventTouchUpInside];
// Set the tint color for the icon
if (@available(iOS 13, *)) {
UIColor *dynamicTint = [[UIColor alloc] initWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {
return traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark ? ORKRGB(IconDarkTintColor) : ORKRGB(IconLightTintColor);
}];
[self.cardView updateIconTintColor:dynamicTint];
} else {
[self.cardView updateIconTintColor:ORKRGB(IconLightTintColor)];
}
[self checkMotionAuthStatus];
}
-(void)checkMotionAuthStatus {
switch (CMMotionActivityManager.authorizationStatus) {
case CMAuthorizationStatusNotDetermined:
[self setState:ORKRequestPermissionsButtonStateDefault canContinue:NO];
break;
case CMAuthorizationStatusDenied:
case CMAuthorizationStatusAuthorized:
case CMAuthorizationStatusRestricted:
[self setState:ORKRequestPermissionsButtonStateConnected canContinue:YES];
break;
}
}
// There is no explicit API for requesting device motion permission.
// It is requested automatically when you try read data for the first time.
// This method tries to read data in order to trigger the permission dialogue.
- (void)requestPermissionButtonPressed {
[self.activityManager startActivityUpdatesToQueue:[NSOperationQueue mainQueue]
withHandler:^(CMMotionActivity * _Nullable activity) {}];
[self.activityManager stopActivityUpdates];
[self setState:ORKRequestPermissionsButtonStateConnected canContinue:YES];
}
- (void)setState:(ORKRequestPermissionsButtonState)state canContinue:(BOOL)canContinue {
[self.cardView setEnableContinueButton:canContinue];
[self.cardView.requestPermissionButton setState:state];
}
- (BOOL)isEqual:(id)object {
if ([self class] != [object class]) {
return NO;
}
return YES;
}
@end
@@ -294,9 +294,9 @@ static const CGFloat activityIndicatorPadding = 24.0;
if (showActivityIndicator == YES) {
if (_activityIndicatorView == nil) {
_activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
_activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleMedium];
[_activityIndicatorView startAnimating];
[_continueButton addSubview:_activityIndicatorView];
CGPoint center = CGPointMake(_continueButton.titleLabel.frame.origin.x - activityIndicatorPadding, _continueButton.titleLabel.center.y);
[_activityIndicatorView setCenter:center];
@@ -0,0 +1,48 @@
/*
Copyright (c) 2021, 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 <ResearchKit/ORKDefines.h>
#import <ResearchKit/ORKPermissionType.h>
NS_ASSUME_NONNULL_BEGIN
ORK_CLASS_AVAILABLE
@interface ORKNotificationPermissionType : ORKPermissionType
+ (instancetype)new NS_UNAVAILABLE;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithAuthorizationOptions:(UNAuthorizationOptions)options;
@property (nonatomic, readonly) UNAuthorizationOptions options;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,141 @@
/*
Copyright (c) 2021, 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 <UserNotifications/UserNotifications.h>
#import "ORKNotificationPermissionType.h"
#import "ORKRequestPermissionButton.h"
#import "ORKRequestPermissionView.h"
#import "ORKHelpers_Internal.h"
static NSString *const Symbol = @"app.badge";
static const uint32_t IconLightTintColor = 0xFBD00B;
static const uint32_t IconDarkTintColor = 0xFFD005;
@interface ORKNotificationPermissionType ()
@property UNAuthorizationOptions options;
@end
@implementation ORKNotificationPermissionType
+ (instancetype)new {
ORKThrowMethodUnavailableException();
}
- (instancetype)init {
ORKThrowMethodUnavailableException();
}
- (instancetype)initWithAuthorizationOptions:(UNAuthorizationOptions)options {
NSAssert(options != 0, @"Authorization options must not be empty!");
self = [super init];
if (self) {
self.options = options;
[self setupCardView];
}
return self;
}
- (void)setupCardView {
UIImage *image;
if (@available(iOS 13.0, *)) {
image = [UIImage systemImageNamed:Symbol];
}
self.cardView = [[ORKRequestPermissionView alloc] initWithIconImage:image
title:ORKLocalizedString(@"REQUEST_NOTIFICATIONS_STEP_VIEW_TITLE", nil)
detailText:ORKLocalizedString(@"REQUEST_NOTIFICATIONS_STEP_VIEW_DESCRIPTION", nil)];
[self.cardView.requestPermissionButton addTarget:self action:@selector(requestPermissionButtonPressed) forControlEvents:UIControlEventTouchUpInside];
// Set the tint color for the icon
if (@available(iOS 13, *)) {
UIColor *dynamicTint = [[UIColor alloc] initWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {
return traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark ? ORKRGB(IconDarkTintColor) : ORKRGB(IconLightTintColor);
}];
[self.cardView updateIconTintColor:dynamicTint];
} else {
[self.cardView updateIconTintColor:ORKRGB(IconLightTintColor)];
}
[self setState:ORKRequestPermissionsButtonStateDefault canContinue:NO];
[[UNUserNotificationCenter currentNotificationCenter] getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
dispatch_async(dispatch_get_main_queue(), ^{
switch (settings.authorizationStatus) {
case UNAuthorizationStatusNotDetermined:
[self setState:ORKRequestPermissionsButtonStateDefault canContinue:NO];
break;
case UNAuthorizationStatusEphemeral:
case UNAuthorizationStatusAuthorized:
case UNAuthorizationStatusProvisional:
case UNAuthorizationStatusDenied:
[self setState:ORKRequestPermissionsButtonStateConnected canContinue:YES];
break;
}
});
}];
}
- (void)requestPermissionButtonPressed {
[[UNUserNotificationCenter currentNotificationCenter]
requestAuthorizationWithOptions: self.options
completionHandler:^(BOOL granted, NSError * _Nullable error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (error) {
[self setState:ORKRequestPermissionsButtonStateError canContinue:YES];
return;
}
[self setState:ORKRequestPermissionsButtonStateConnected canContinue:YES];
});
}];
}
- (void)setState:(ORKRequestPermissionsButtonState)state canContinue:(BOOL)canContinue {
[self.cardView setEnableContinueButton:canContinue];
[self.cardView.requestPermissionButton setState:state];
}
- (BOOL)isEqual:(id)object {
if ([self class] != [object class]) {
return NO;
}
__typeof(self) castObject = object;
return (self.options & castObject.options) == self.options;
}
@end
@@ -63,6 +63,7 @@
#import "ORKPSATStep.h"
#import "ORKQuestionStep.h"
#import "ORKReactionTimeStep.h"
#import "ORKNormalizedReactionTimeStep.h"
#import "ORKSpatialSpanMemoryStep.h"
#import "ORKSpeechRecognitionStep.h"
#import "ORKStep_Private.h"
@@ -1075,41 +1076,97 @@ NSString *const ORKKneeRangeOfMotionStepIdentifier = @"knee.range.of.motion";
}
if (!(options & ORKPredefinedTaskOptionExcludeInstructions)) {
ORKInstructionStep *instructionStep0 = [[ORKInstructionStep alloc] initWithIdentifier:ORKInstruction0StepIdentifier];
instructionStep0.title = ORKLocalizedString(@"RANGE_OF_MOTION_TITLE", nil);
instructionStep0.text = intendedUseDescription;
instructionStep0.detailText = ([limbType isEqualToString:ORKLocalizedString(@"LIMB_LEFT", nil)])? ORKLocalizedString(@"KNEE_RANGE_OF_MOTION_TEXT_INSTRUCTION_0_LEFT", nil) : ORKLocalizedString(@"KNEE_RANGE_OF_MOTION_TEXT_INSTRUCTION_0_RIGHT", nil);
instructionStep0.shouldTintImages = YES;
instructionStep0.imageContentMode = UIViewContentModeCenter;
ORKStepArrayAddStep(steps, instructionStep0);
ORKInstructionStep *instructionStep1 = [[ORKInstructionStep alloc] initWithIdentifier:ORKInstruction1StepIdentifier];
instructionStep1.title = ORKLocalizedString(@"RANGE_OF_MOTION_TITLE", nil);
instructionStep1.text = ([limbType isEqualToString:ORKLocalizedString(@"LIMB_LEFT", nil)])? ORKLocalizedString(@"KNEE_RANGE_OF_MOTION_TEXT_INSTRUCTION_1_LEFT", nil) : ORKLocalizedString(@"KNEE_RANGE_OF_MOTION_TEXT_INSTRUCTION_1_RIGHT", nil);
ORKStepArrayAddStep(steps, instructionStep1);
ORKInstructionStep *instructionStep2 = [[ORKInstructionStep alloc] initWithIdentifier:ORKInstruction2StepIdentifier];
instructionStep2.title = ORKLocalizedString(@"RANGE_OF_MOTION_TITLE", nil);
instructionStep2.text = ([limbType isEqualToString:ORKLocalizedString(@"LIMB_LEFT", nil)])? ORKLocalizedString(@"KNEE_RANGE_OF_MOTION_TITLE_LEFT", nil) : ORKLocalizedString(@"KNEE_RANGE_OF_MOTION_TITLE_RIGHT", nil);
instructionStep2.detailText = ([limbType isEqualToString:ORKLocalizedString(@"LIMB_LEFT", nil)])? ORKLocalizedString(@"KNEE_RANGE_OF_MOTION_TEXT_INSTRUCTION_2_LEFT", nil) : ORKLocalizedString(@"KNEE_RANGE_OF_MOTION_TEXT_INSTRUCTION_2_RIGHT", nil);
instructionStep2.image = kneeStartImage;
instructionStep2.imageContentMode = UIViewContentModeCenter;
instructionStep2.shouldTintImages = YES;
ORKStepArrayAddStep(steps, instructionStep2);
ORKInstructionStep *instructionStep3 = [[ORKInstructionStep alloc] initWithIdentifier:ORKInstruction3StepIdentifier];
instructionStep3.title = ORKLocalizedString(@"RANGE_OF_MOTION_TITLE", nil);
instructionStep3.text = ([limbType isEqualToString:ORKLocalizedString(@"LIMB_LEFT", nil)])? ORKLocalizedString(@"KNEE_RANGE_OF_MOTION_TEXT_INSTRUCTION_3_LEFT", nil) : ORKLocalizedString(@"KNEE_RANGE_OF_MOTION_TEXT_INSTRUCTION_3_RIGHT", nil);
instructionStep3.image = kneeMaximumImage;
instructionStep3.imageContentMode = UIViewContentModeCenter;
instructionStep3.shouldTintImages = YES;
ORKStepArrayAddStep(steps, instructionStep3);
if (@available(iOS 13.0, *)) {
// Use body items with SFSymbols in iOS 13+
ORKInstructionStep *instructionStep = [[ORKInstructionStep alloc] initWithIdentifier:ORKInstruction0StepIdentifier];
instructionStep.title = ORKLocalizedString(@"RANGE_OF_MOTION_TITLE", nil);
instructionStep.text = intendedUseDescription;
instructionStep.shouldTintImages = YES;
instructionStep.iconImage = kneeStartImage;
instructionStep.bodyItems = @[
[[ORKBodyItem alloc] initWithText:
([limbType isEqualToString:ORKLocalizedString(@"LIMB_LEFT", nil)])?
ORKLocalizedString(@"KNEE_RANGE_OF_MOTION_TEXT_INSTRUCTION_0_LEFT", nil) :
ORKLocalizedString(@"KNEE_RANGE_OF_MOTION_TEXT_INSTRUCTION_0_RIGHT", nil)
detailText:nil
image:[UIImage systemImageNamed:@"1.circle.fill"]
learnMoreItem:nil
bodyItemStyle:ORKBodyItemStyleImage],
[[ORKBodyItem alloc] initWithText:
([limbType isEqualToString:ORKLocalizedString(@"LIMB_LEFT", nil)])?
ORKLocalizedString(@"KNEE_RANGE_OF_MOTION_TEXT_INSTRUCTION_1_LEFT", nil) :
ORKLocalizedString(@"KNEE_RANGE_OF_MOTION_TEXT_INSTRUCTION_1_RIGHT", nil)
detailText:nil
image:[UIImage systemImageNamed:@"2.circle.fill"]
learnMoreItem:nil
bodyItemStyle:ORKBodyItemStyleImage],
[[ORKBodyItem alloc] initWithText:
([limbType isEqualToString:ORKLocalizedString(@"LIMB_LEFT", nil)])?
ORKLocalizedString(@"KNEE_RANGE_OF_MOTION_TEXT_INSTRUCTION_2_LEFT", nil) :
ORKLocalizedString(@"KNEE_RANGE_OF_MOTION_TEXT_INSTRUCTION_2_RIGHT", nil)
detailText:nil
image:[UIImage systemImageNamed:@"3.circle.fill"]
learnMoreItem:nil
bodyItemStyle:ORKBodyItemStyleImage],
[[ORKBodyItem alloc] initWithText:
([limbType isEqualToString:ORKLocalizedString(@"LIMB_LEFT", nil)])?
ORKLocalizedString(@"KNEE_RANGE_OF_MOTION_TEXT_INSTRUCTION_3_LEFT", nil) :
ORKLocalizedString(@"KNEE_RANGE_OF_MOTION_TEXT_INSTRUCTION_3_RIGHT", nil)
detailText:nil
image:[UIImage systemImageNamed:@"4.circle.fill"]
learnMoreItem:nil
bodyItemStyle:ORKBodyItemStyleImage],
];
ORKStepArrayAddStep(steps, instructionStep);
} else {
// Fallback for iOS 12 and earlier
ORKInstructionStep *instructionStep0 = [[ORKInstructionStep alloc] initWithIdentifier:ORKInstruction0StepIdentifier];
instructionStep0.title = ORKLocalizedString(@"RANGE_OF_MOTION_TITLE", nil);
instructionStep0.text = intendedUseDescription;
instructionStep0.detailText = ([limbType isEqualToString:ORKLocalizedString(@"LIMB_LEFT", nil)])? ORKLocalizedString(@"KNEE_RANGE_OF_MOTION_TEXT_INSTRUCTION_0_LEFT", nil) : ORKLocalizedString(@"KNEE_RANGE_OF_MOTION_TEXT_INSTRUCTION_0_RIGHT", nil);
instructionStep0.shouldTintImages = YES;
instructionStep0.imageContentMode = UIViewContentModeCenter;
ORKStepArrayAddStep(steps, instructionStep0);
ORKInstructionStep *instructionStep1 = [[ORKInstructionStep alloc] initWithIdentifier:ORKInstruction1StepIdentifier];
instructionStep1.title = ORKLocalizedString(@"RANGE_OF_MOTION_TITLE", nil);
instructionStep1.text = ([limbType isEqualToString:ORKLocalizedString(@"LIMB_LEFT", nil)])? ORKLocalizedString(@"KNEE_RANGE_OF_MOTION_TEXT_INSTRUCTION_1_LEFT", nil) : ORKLocalizedString(@"KNEE_RANGE_OF_MOTION_TEXT_INSTRUCTION_1_RIGHT", nil);
ORKStepArrayAddStep(steps, instructionStep1);
ORKInstructionStep *instructionStep2 = [[ORKInstructionStep alloc] initWithIdentifier:ORKInstruction2StepIdentifier];
instructionStep2.title = ORKLocalizedString(@"RANGE_OF_MOTION_TITLE", nil);
instructionStep2.text = ([limbType isEqualToString:ORKLocalizedString(@"LIMB_LEFT", nil)])? ORKLocalizedString(@"KNEE_RANGE_OF_MOTION_TITLE_LEFT", nil) : ORKLocalizedString(@"KNEE_RANGE_OF_MOTION_TITLE_RIGHT", nil);
instructionStep2.detailText = ([limbType isEqualToString:ORKLocalizedString(@"LIMB_LEFT", nil)])? ORKLocalizedString(@"KNEE_RANGE_OF_MOTION_TEXT_INSTRUCTION_2_LEFT", nil) : ORKLocalizedString(@"KNEE_RANGE_OF_MOTION_TEXT_INSTRUCTION_2_RIGHT", nil);
instructionStep2.image = kneeStartImage;
instructionStep2.imageContentMode = UIViewContentModeCenter;
instructionStep2.shouldTintImages = YES;
ORKStepArrayAddStep(steps, instructionStep2);
ORKInstructionStep *instructionStep3 = [[ORKInstructionStep alloc] initWithIdentifier:ORKInstruction3StepIdentifier];
instructionStep3.title = ORKLocalizedString(@"RANGE_OF_MOTION_TITLE", nil);
instructionStep3.text = ([limbType isEqualToString:ORKLocalizedString(@"LIMB_LEFT", nil)])? ORKLocalizedString(@"KNEE_RANGE_OF_MOTION_TEXT_INSTRUCTION_3_LEFT", nil) : ORKLocalizedString(@"KNEE_RANGE_OF_MOTION_TEXT_INSTRUCTION_3_RIGHT", nil);
instructionStep3.image = kneeMaximumImage;
instructionStep3.imageContentMode = UIViewContentModeCenter;
instructionStep3.shouldTintImages = YES;
ORKStepArrayAddStep(steps, instructionStep3);
}
}
NSString *instructionText = ([limbType isEqualToString:ORKLocalizedString(@"LIMB_LEFT", nil)])? ORKLocalizedString(@"KNEE_RANGE_OF_MOTION_TOUCH_ANYWHERE_STEP_INSTRUCTION_LEFT", nil) : ORKLocalizedString(@"KNEE_RANGE_OF_MOTION_TOUCH_ANYWHERE_STEP_INSTRUCTION_RIGHT", nil);
ORKTouchAnywhereStep *touchAnywhereStep = [[ORKTouchAnywhereStep alloc] initWithIdentifier:ORKTouchAnywhereStepIdentifier instructionText:instructionText];
touchAnywhereStep.title = ORKLocalizedString(@"RANGE_OF_MOTION_TITLE", nil);
touchAnywhereStep.image = kneeMaximumImage;
touchAnywhereStep.imageContentMode = UIViewContentModeCenter;
ORKStepArrayAddStep(steps, touchAnywhereStep);
touchAnywhereStep.spokenInstruction = touchAnywhereStep.text;
@@ -1117,6 +1174,8 @@ NSString *const ORKKneeRangeOfMotionStepIdentifier = @"knee.range.of.motion";
ORKDeviceMotionRecorderConfiguration *deviceMotionRecorderConfig = [[ORKDeviceMotionRecorderConfiguration alloc] initWithIdentifier:ORKDeviceMotionRecorderIdentifier frequency:100];
ORKRangeOfMotionStep *kneeRangeOfMotionStep = [[ORKRangeOfMotionStep alloc] initWithIdentifier:ORKKneeRangeOfMotionStepIdentifier limbOption:limbOption];
kneeRangeOfMotionStep.image = kneeStartImage;
kneeRangeOfMotionStep.imageContentMode = UIViewContentModeCenter;
kneeRangeOfMotionStep.title = ORKLocalizedString(@"RANGE_OF_MOTION_TITLE", nil);
kneeRangeOfMotionStep.text = ([limbType isEqualToString: ORKLocalizedString(@"LIMB_LEFT", nil)])? ORKLocalizedString(@"KNEE_RANGE_OF_MOTION_SPOKEN_INSTRUCTION_LEFT", nil) :
ORKLocalizedString(@"KNEE_RANGE_OF_MOTION_SPOKEN_INSTRUCTION_RIGHT", nil);
+2 -2
View File
@@ -161,7 +161,7 @@ const CGFloat PDFhideViewAnimationDuration = 0.5;
if (!_clearAnnotationsButton) {
_clearAnnotationsButton = [ORKBorderedButton new];
}
_clearAnnotationsButton.contentEdgeInsets = (UIEdgeInsets){.left = 6, .right = 6};
[_clearAnnotationsButton updateContentInsets:NSDirectionalEdgeInsetsMake(0, 6, 0, 6)];
_clearAnnotationsButton.translatesAutoresizingMaskIntoConstraints = NO;
_clearButtonView = [UIView new];
_clearButtonView.translatesAutoresizingMaskIntoConstraints = NO;
@@ -197,7 +197,7 @@ const CGFloat PDFhideViewAnimationDuration = 0.5;
if (!_applyAnnotationsButton) {
_applyAnnotationsButton = [ORKBorderedButton new];
}
_applyAnnotationsButton.contentEdgeInsets = (UIEdgeInsets){.left = 6, .right = 6};
[_applyAnnotationsButton updateContentInsets:NSDirectionalEdgeInsetsMake(0, 6, 0, 6)];
_applyAnnotationsButton.translatesAutoresizingMaskIntoConstraints = NO;
_applyButtonView = [UIView new];
_applyButtonView.translatesAutoresizingMaskIntoConstraints = NO;
@@ -126,8 +126,8 @@ static CGFloat const kForgotPasscodeHeight = 100.0f;
_originalForgotPasscodeY = self.view.bounds.size.height - kForgotPasscodeVerticalPadding - kForgotPasscodeHeight;
CGFloat width = self.view.bounds.size.width - 2 * kForgotPasscodeHorizontalPadding;
UIButton *forgotPasscodeButton = [ORKTextButton new];
forgotPasscodeButton.contentEdgeInsets = (UIEdgeInsets){12, 10, 8, 10};
ORKTextButton *forgotPasscodeButton = [ORKTextButton new];
[forgotPasscodeButton updateContentInsets: NSDirectionalEdgeInsetsMake(12, 10, 8, 10)];
forgotPasscodeButton.frame = CGRectMake(x, _originalForgotPasscodeY, width, kForgotPasscodeHeight);
NSString *buttonTitle = [self forgotPasscodeButtonText];
@@ -499,9 +499,9 @@ static CGFloat const kForgotPasscodeHeight = 100.0f;
- (void)removePasscodeFromKeychain {
NSError *error;
[ORKKeychainWrapper objectForKey:PasscodeKey error:&error];
id storedValue = [ORKKeychainWrapper objectOfClass:NSDictionary.self forKey:PasscodeKey error:&error];
if (!error) {
if (storedValue != nil) {
[ORKKeychainWrapper removeObjectForKey:PasscodeKey error:&error];
if (error) {
@@ -512,8 +512,10 @@ static CGFloat const kForgotPasscodeHeight = 100.0f;
- (BOOL)passcodeMatchesKeychain {
NSError *error;
NSDictionary *dictionary = (NSDictionary *) [ORKKeychainWrapper objectForKey:PasscodeKey error:&error];
if (error) {
NSDictionary *dictionary = (NSDictionary *) [ORKKeychainWrapper objectOfClass:NSDictionary.self
forKey:PasscodeKey
error:&error];
if (dictionary == nil) {
[self throwExceptionWithKeychainError:error];
}
@@ -523,8 +525,11 @@ static CGFloat const kForgotPasscodeHeight = 100.0f;
- (void)setValuesFromKeychain {
NSError *error;
NSDictionary *dictionary = (NSDictionary*) [ORKKeychainWrapper objectForKey:PasscodeKey error:&error];
if (error) {
NSDictionary *dictionary = (NSDictionary*) [ORKKeychainWrapper objectOfClass:NSDictionary.self
forKey:PasscodeKey
error:&error];
if (dictionary == nil) {
[self throwExceptionWithKeychainError:error];
}
+13 -1
View File
@@ -100,7 +100,19 @@
}
+ (BOOL)isPasscodeStoredInKeychain {
NSDictionary *dictionary = (NSDictionary *)[ORKKeychainWrapper objectForKey:PasscodeKey error:nil];
NSError *error;
NSDictionary *dictionary = (NSDictionary *)[ORKKeychainWrapper objectOfClass:NSDictionary.self
forKey:PasscodeKey
error:&error];
if (dictionary == nil) {
NSString *errorReason = error.localizedDescription;
if (error.code == errSecItemNotFound) {
errorReason = @"There is no passcode stored in the keychain.";
}
@throw [NSException exceptionWithName:NSGenericException reason:errorReason userInfo:nil];
}
return ([dictionary objectForKey:KeychainDictionaryPasscodeKey]) ? YES : NO;
}
+10
View File
@@ -28,12 +28,19 @@
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
@import Foundation;
#import <ResearchKit/ORKDefines.h>
NS_ASSUME_NONNULL_BEGIN
@class ORKHealthKitPermissionType;
@class ORKNotificationPermissionType;
@class ORKMotionActivityPermissionType;
@class ORKRequestPermissionView;
@class HKSampleType, HKObjectType;
typedef NS_OPTIONS(NSUInteger, UNAuthorizationOptions);
ORK_CLASS_AVAILABLE
@interface ORKPermissionType : NSObject
@@ -43,6 +50,9 @@ ORK_CLASS_AVAILABLE
+ (ORKHealthKitPermissionType *)healthKitPermissionTypeWithSampleTypesToWrite:(nullable NSSet<HKSampleType *> *)sampleTypesToWrite
objectTypesToRead:(nullable NSSet<HKObjectType *> *)objectTypesToRead;
+ (ORKNotificationPermissionType *)notificationPermissionType:(UNAuthorizationOptions)options;
+ (ORKMotionActivityPermissionType *)deviceMotionPermissionType;
@end
+10 -1
View File
@@ -29,6 +29,8 @@
*/
#import "ORKHealthKitPermissionType.h"
#import "ORKNotificationPermissionType.h"
#import "ORKMotionActivityPermissionType.h"
#import "ORKHelpers_Internal.h"
#import "ORKPermissionType.h"
@@ -39,5 +41,12 @@
objectTypesToRead:objectTypesToRead];
}
@end
+ (ORKNotificationPermissionType *)notificationPermissionType:(UNAuthorizationOptions)options {
return [[ORKNotificationPermissionType alloc] initWithAuthorizationOptions:options];
}
+ (ORKMotionActivityPermissionType *)deviceMotionPermissionType {
return [[ORKMotionActivityPermissionType alloc] init];
}
@end
@@ -0,0 +1,49 @@
/*
Copyright (c) 2021, 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;
@import Foundation;
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSInteger, ORKRequestPermissionsButtonState) {
ORKRequestPermissionsButtonStateDefault = 0,
ORKRequestPermissionsButtonStateConnected,
ORKRequestPermissionsButtonStateNotSupported,
ORKRequestPermissionsButtonStateError,
};
@interface ORKRequestPermissionButton : UIControl
- (void)setState:(ORKRequestPermissionsButtonState)state;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,159 @@
/*
Copyright (c) 2021, 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 "ORKRequestPermissionButton.h"
#import "ORKHelpers_Internal.h"
#import "ORKSkin.h"
static const CGFloat ButtonLabelVerticalPadding = 4;
static const CGFloat StandardPadding = 15.0;
static const CGFloat HighlightedOpacity = 0.5;
@implementation ORKRequestPermissionButton {
UILabel *_titleLabel;
ORKRequestPermissionsButtonState _state;
UIViewPropertyAnimator *highlightAnimator;
}
- (instancetype)init {
self = [super init];
if (self) {
highlightAnimator = [[UIViewPropertyAnimator alloc] initWithDuration:0.1 curve:UIViewAnimationCurveEaseOut animations:nil];
[self setupTitleLabel];
[self setState:ORKRequestPermissionsButtonStateDefault];
[self updateFonts];
}
return self;
}
- (void)setupTitleLabel {
_titleLabel = [UILabel new];
_titleLabel.numberOfLines = 0;
_titleLabel.textAlignment = NSTextAlignmentCenter;
[self addSubview:_titleLabel];
_titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
[_titleLabel.leadingAnchor constraintGreaterThanOrEqualToAnchor:self.leadingAnchor constant:StandardPadding],
[_titleLabel.trailingAnchor constraintLessThanOrEqualToAnchor:self.trailingAnchor constant:-StandardPadding],
[_titleLabel.topAnchor constraintEqualToAnchor:self.topAnchor constant:ButtonLabelVerticalPadding],
[_titleLabel.bottomAnchor constraintEqualToAnchor:self.bottomAnchor constant:-ButtonLabelVerticalPadding],
[_titleLabel.centerXAnchor constraintEqualToAnchor:self.centerXAnchor],
]];
}
- (void)setHighlighted:(BOOL)highlighted {
[super setHighlighted:highlighted];
// Animate the opacity transition
[highlightAnimator stopAnimation:true];
__weak __typeof__(self) weakSelf = self;
[highlightAnimator addAnimations:^{
weakSelf.alpha = highlighted ? HighlightedOpacity : 1;
}];
[highlightAnimator startAnimation];
}
- (void)setState:(ORKRequestPermissionsButtonState)state {
_state = state;
switch (state) {
case ORKRequestPermissionsButtonStateDefault:
_titleLabel.text = ORKLocalizedString(@"REQUEST_PERMISSION_BUTTON_STATE_DEFAULT", nil);
break;
case ORKRequestPermissionsButtonStateConnected:
_titleLabel.text = ORKLocalizedString(@"REQUEST_PERMISSION_BUTTON_STATE_CONNECTED", nil);
break;
case ORKRequestPermissionsButtonStateNotSupported:
_titleLabel.text = ORKLocalizedString(@"REQUEST_PERMISSION_BUTTON_STATE_NOT_SUPPORTED", nil);
break;
case ORKRequestPermissionsButtonStateError:
_titleLabel.text = ORKLocalizedString(@"REQUEST_PERMISSION_BUTTON_STATE_ERROR", nil);
break;
}
[self setStyleForState:state];
}
- (void)setStyleForState:(ORKRequestPermissionsButtonState)state {
switch (state) {
case ORKRequestPermissionsButtonStateDefault:
[self setBackgroundColor:self.tintColor];
_titleLabel.textColor = UIColor.whiteColor;
[self setEnabled:YES];
break;
case ORKRequestPermissionsButtonStateConnected:
case ORKRequestPermissionsButtonStateNotSupported:
case ORKRequestPermissionsButtonStateError:
_titleLabel.textColor = UIColor.systemGrayColor;
if (@available(iOS 13.0, *)) {
[self setBackgroundColor:UIColor.tertiarySystemFillColor];
} else {
[self setBackgroundColor:UIColor.lightGrayColor];
}
[self setEnabled:NO];
break;
}
}
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
[super traitCollectionDidChange:previousTraitCollection];
if (previousTraitCollection.preferredContentSizeCategory != self.traitCollection.preferredContentSizeCategory) {
[self updateFonts];
}
}
- (void)updateFonts {
if (_titleLabel) {
_titleLabel.font = [self fontWithTextStyle:UIFontTextStyleBody weight:UIFontWeightMedium];
}
}
- (UIFont *)fontWithTextStyle:(UIFontTextStyle)textStyle weight:(UIFontWeight)weight {
UIFontDescriptor *descriptor = [UIFontDescriptor preferredFontDescriptorWithTextStyle:textStyle];
return [UIFont systemFontOfSize:descriptor.pointSize weight:weight];
}
- (void)tintColorDidChange {
if (_state == ORKRequestPermissionsButtonStateDefault) {
[self setBackgroundColor:self.tintColor];
}
}
@end
@@ -31,6 +31,8 @@
@import UIKit;
@import Foundation;
@class ORKRequestPermissionButton;
NS_ASSUME_NONNULL_BEGIN
typedef NSString * ORKRequestPermissionsNotification NS_STRING_ENUM;
@@ -42,7 +44,7 @@ extern ORKRequestPermissionsNotification const ORKRequestPermissionsNotification
title:(NSString *)title
detailText:(NSString *)detailText;
@property (nonatomic, strong) UIButton *requestPermissionButton;
@property (nonatomic, strong) ORKRequestPermissionButton *requestPermissionButton;
@property (nonatomic) BOOL enableContinueButton;
- (void)updateIconTintColor:(UIColor *)iconTintColor;
+124 -106
View File
@@ -29,38 +29,42 @@
*/
#import "ORKRequestPermissionView.h"
#import "ORKRequestPermissionButton.h"
#import "ORKStepContainerView_Private.h"
#import "ORKHelpers_Internal.h"
#import "ORKSkin.h"
ORKRequestPermissionsNotification const ORKRequestPermissionsNotificationCardViewStatusChanged = @"ORKRequestPermissionsNotificationCardViewStatusChanged";
static const CGFloat RequestHealthDataViewTopBottomPadding = 15.0;
static const CGFloat StandardPadding = 8.0;
static const CGFloat IconImageViewWidthHeight = 48.0;
static const CGFloat StandardPadding = 15.0;
static const CGFloat IconImageViewWidthHeight = 40.0;
static const CGFloat IconImageViewBottomPadding = 10.0;
static const CGFloat DetailTextLabelBottomPadding = 10.0;
static const CGFloat RequestDataButtonWidth = 125.0;
static const CGFloat TitleTextLabelBottomPadding = 6.0;
static const CGFloat DetailTextLabelBottomPadding = 12;
static const CGFloat ContentStackViewBottomPadding = 12;
static const CGFloat CornerRadius = 10.0;
static const CGFloat ButtonWidth = 150;
@implementation ORKRequestPermissionView {
NSMutableArray *_constraints;
UIImage *_iconImage;
UIImageView *_iconImageView;
NSString *_title;
NSString *_detailText;
UILabel *_titleLabel;
UILabel *_detailTextLabel;
UILabel *_buttonStateMessageLabel;
UIImageView *_buttonStateImageView;
UIStackView *_contentStackView;
NSLayoutConstraint *_buttonWidthConstraint;
}
- (instancetype)initWithIconImage:(nullable UIImage *)iconImage title:(NSString *)title detailText:(NSString *)detailText {
self = [self initWithFrame:CGRectZero];
if (self) {
_iconImage = iconImage;
_title = title;
@@ -68,161 +72,175 @@ static const CGFloat RequestDataButtonWidth = 125.0;
_enableContinueButton = YES;
[self commonInit];
}
return self;
}
- (void)commonInit {
if (@available(iOS 13.0, *)) {
self.layer.borderColor = [[UIColor separatorColor] CGColor];
[self setBackgroundColor:[UIColor systemBackgroundColor]];
} else {
self.layer.borderColor = [[UIColor ork_midGrayTintColor] CGColor];
[self setBackgroundColor:[UIColor whiteColor]];
}
self.clipsToBounds = false;
self.layer.cornerRadius = CornerRadius;
[self setupSubviews];
[self setUpConstraints];
}
- (void)layoutSubviews {
self.translatesAutoresizingMaskIntoConstraints = NO;
if (@available(iOS 13.0, *)) {
[self setBackgroundColor:[UIColor systemBackgroundColor]];
} else {
[self setBackgroundColor:[UIColor whiteColor]];
}
self.layer.cornerRadius = 10.0;
self.clipsToBounds = YES;
}
- (void)setupSubviews {
[self setupIconImageView];
[self setUpTitleLabel];
[self setUpDetailTextLabel];
[self setupRequestDataButton];
[self setupContentStackView];
[self updateFonts];
}
- (void)setupContentStackView {
if (!_contentStackView) {
_contentStackView = [UIStackView new];
if (_iconImageView) {
[_contentStackView addArrangedSubview:_iconImageView];
[_contentStackView setCustomSpacing:IconImageViewBottomPadding afterView:_iconImageView];
}
if (_titleLabel) {
[_contentStackView addArrangedSubview:_titleLabel];
[_contentStackView setCustomSpacing:TitleTextLabelBottomPadding afterView:_titleLabel];
}
if (_detailTextLabel) {
[_contentStackView addArrangedSubview:_detailTextLabel];
[_contentStackView setCustomSpacing:DetailTextLabelBottomPadding afterView:_detailTextLabel];
}
if (_requestPermissionButton) {
[_contentStackView addArrangedSubview:_requestPermissionButton];
}
_contentStackView.alignment = UIStackViewAlignmentCenter;
_contentStackView.axis = UILayoutConstraintAxisVertical;
[self addSubview:_contentStackView];
}
}
- (void)setupIconImageView {
if (_iconImage) {
_iconImageView = [[UIImageView alloc] initWithImage:_iconImage];
_iconImageView.contentMode = UIViewContentModeScaleAspectFit;
_iconImageView.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:_iconImageView];
}
}
- (void)setUpTitleLabel {
if (_title) {
_titleLabel = [UILabel new];
_titleLabel = [self makeMultilineLabel];
_titleLabel.text = _title;
_titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
_titleLabel.numberOfLines = 0;
if (@available(iOS 13.0, *)) {
_titleLabel.textColor = [UIColor labelColor];
} else {
_titleLabel.textColor = [UIColor blackColor];
}
_titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
_titleLabel.textAlignment = NSTextAlignmentNatural;
UIFontDescriptor *descriptor = [UIFontDescriptor preferredFontDescriptorWithTextStyle:UIFontTextStyleBody];
UIFontDescriptor *fontDescriptor = [descriptor fontDescriptorWithSymbolicTraits:(UIFontDescriptorTraitBold)];
[_titleLabel setFont:[UIFont fontWithDescriptor:fontDescriptor size:[[fontDescriptor objectForKey: UIFontDescriptorSizeAttribute] doubleValue]]];
[self addSubview:_titleLabel];
_titleLabel.textAlignment = NSTextAlignmentCenter;
}
}
- (void)setUpDetailTextLabel {
if (_detailText) {
_detailTextLabel = [UILabel new];
_detailTextLabel.translatesAutoresizingMaskIntoConstraints = NO;
_detailTextLabel = [self makeMultilineLabel];
_detailTextLabel.textAlignment = NSTextAlignmentCenter;
_detailTextLabel.text = _detailText;
_detailTextLabel.numberOfLines = 0;
_detailTextLabel.lineBreakMode = NSLineBreakByWordWrapping;
_detailTextLabel.textAlignment = NSTextAlignmentNatural;
UIFontDescriptor *descriptorForDetailText = [UIFontDescriptor preferredFontDescriptorWithTextStyle:UIFontTextStyleSubheadline];
[_detailTextLabel setFont:[UIFont fontWithDescriptor:descriptorForDetailText size:[[descriptorForDetailText objectForKey: UIFontDescriptorSizeAttribute] doubleValue]]];
[self addSubview:_detailTextLabel];
[_detailTextLabel setFont:[UIFont preferredFontForTextStyle:UIFontTextStyleBody]];
_detailTextLabel.adjustsFontForContentSizeCategory = true;
}
}
- (void)setupRequestDataButton {
if (!_requestPermissionButton) {
_requestPermissionButton = [UIButton new];
_requestPermissionButton.translatesAutoresizingMaskIntoConstraints = NO;
_requestPermissionButton.layer.cornerRadius = 10.0;
_requestPermissionButton.clipsToBounds = YES;
_requestPermissionButton.titleLabel.font = [UIFont systemFontOfSize:12.0];
[_requestPermissionButton setTitleEdgeInsets:UIEdgeInsetsMake(5.0, 8.0, 5.0, 8.0)];
[self addSubview:_requestPermissionButton];
_requestPermissionButton = [ORKRequestPermissionButton new];
// The button's corner radius should match the corner radius of the parent.
// Equation: r_inner = r_inner - d
// r_inner = corner radius of the inner view
// r_outer = corner radius of the outer view
// d = Distance between the inner and outer view in pixels
_requestPermissionButton.clipsToBounds = false;
_requestPermissionButton.layer.cornerRadius =
CornerRadius -
(ContentStackViewBottomPadding / [[UIScreen mainScreen] scale]);
}
}
- (void)setupMessageStateSubviews {
if (_requestPermissionButton) {
[_requestPermissionButton removeFromSuperview];
- (UILabel *)makeMultilineLabel {
UILabel *label = [UILabel new];
label.numberOfLines = 0;
label.lineBreakMode = NSLineBreakByWordWrapping;
label.textAlignment = NSTextAlignmentNatural;
return label;
}
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
[super traitCollectionDidChange:previousTraitCollection];
if (previousTraitCollection.preferredContentSizeCategory != self.traitCollection.preferredContentSizeCategory) {
[self updateFonts];
// Scale the button width for the AX size
_buttonWidthConstraint.constant = [[UIFontMetrics defaultMetrics] scaledValueForValue:ButtonWidth];
}
if (!_buttonStateMessageLabel) {
_buttonStateMessageLabel = [UILabel new];
_buttonStateMessageLabel.translatesAutoresizingMaskIntoConstraints = NO;
_buttonStateMessageLabel.numberOfLines = 0;
_buttonStateMessageLabel.lineBreakMode = NSLineBreakByWordWrapping;
_buttonStateMessageLabel.textAlignment = NSTextAlignmentNatural;
UIFontDescriptor *descriptorForDetailText = [UIFontDescriptor preferredFontDescriptorWithTextStyle:UIFontTextStyleSubheadline];
[_buttonStateMessageLabel setFont:[UIFont fontWithDescriptor:descriptorForDetailText size:[[descriptorForDetailText objectForKey: UIFontDescriptorSizeAttribute] doubleValue]]];
[self addSubview:_buttonStateMessageLabel];
_buttonStateImageView = [UIImageView new];
_buttonStateImageView.contentMode = UIViewContentModeScaleAspectFit;
_buttonStateImageView.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:_buttonStateImageView];
}
- (void)updateFonts {
if (_titleLabel) {
_titleLabel.font = [self fontWithTextStyle:UIFontTextStyleBody weight:UIFontWeightBold];
}
[self setUpConstraints];
}
- (UIFont *)fontWithTextStyle:(UIFontTextStyle)textStyle weight:(UIFontWeight)weight {
UIFontDescriptor *descriptor = [UIFontDescriptor preferredFontDescriptorWithTextStyle:textStyle];
return [UIFont systemFontOfSize:descriptor.pointSize weight:weight];
}
- (void)setUpConstraints {
if (_constraints) {
[NSLayoutConstraint deactivateConstraints:_constraints];
}
_contentStackView.translatesAutoresizingMaskIntoConstraints = NO;
_requestPermissionButton.translatesAutoresizingMaskIntoConstraints = NO;
_constraints = [NSMutableArray array];
[_constraints addObject:[_iconImageView.topAnchor constraintEqualToAnchor:self.topAnchor constant:RequestHealthDataViewTopBottomPadding]];
[_constraints addObject:[_iconImageView.leftAnchor constraintEqualToAnchor:self.leftAnchor constant:StandardPadding]];
[_constraints addObject:[_iconImageView.widthAnchor constraintEqualToConstant:IconImageViewWidthHeight]];
[_constraints addObject:[_iconImageView.heightAnchor constraintEqualToConstant:IconImageViewWidthHeight]];
[_constraints addObject:[_titleLabel.topAnchor constraintEqualToAnchor:_iconImageView.bottomAnchor constant:IconImageViewBottomPadding]];
[_constraints addObject:[_titleLabel.leftAnchor constraintEqualToAnchor:self.leftAnchor constant:StandardPadding]];
[_constraints addObject:[_titleLabel.rightAnchor constraintEqualToAnchor:self.rightAnchor constant:-StandardPadding]];
[_constraints addObject:[_detailTextLabel.topAnchor constraintEqualToAnchor:_titleLabel.bottomAnchor constant:StandardPadding]];
[_constraints addObject:[_detailTextLabel.leftAnchor constraintEqualToAnchor:_titleLabel.leftAnchor]];
[_constraints addObject:[_detailTextLabel.rightAnchor constraintEqualToAnchor:self.rightAnchor constant:-StandardPadding]];
if (!_buttonStateMessageLabel) {
[_constraints addObject:[_requestPermissionButton.topAnchor constraintEqualToAnchor:_detailTextLabel.bottomAnchor constant:DetailTextLabelBottomPadding]];
[_constraints addObject:[_requestPermissionButton.leftAnchor constraintEqualToAnchor:_titleLabel.leftAnchor]];
[_constraints addObject:[_requestPermissionButton.widthAnchor constraintEqualToConstant:RequestDataButtonWidth]];
[_constraints addObject:[_requestPermissionButton.bottomAnchor constraintEqualToAnchor:self.bottomAnchor constant:-RequestHealthDataViewTopBottomPadding]];
if (@available(iOS 13.0, *)) {
[_iconImageView setPreferredSymbolConfiguration:[UIImageSymbolConfiguration configurationWithTextStyle:UIFontTextStyleLargeTitle]];
} else {
[_constraints addObject:[_buttonStateMessageLabel.topAnchor constraintEqualToAnchor:_detailTextLabel.bottomAnchor constant:DetailTextLabelBottomPadding]];
[_constraints addObject:[_buttonStateMessageLabel.leftAnchor constraintEqualToAnchor:_titleLabel.leftAnchor]];
[_constraints addObject:[_buttonStateMessageLabel.bottomAnchor constraintEqualToAnchor:self.bottomAnchor constant:-RequestHealthDataViewTopBottomPadding]];
[_constraints addObject:[_buttonStateImageView.leftAnchor constraintEqualToAnchor:_buttonStateMessageLabel.rightAnchor constant: StandardPadding]];
[_constraints addObject:[_buttonStateImageView.centerYAnchor constraintEqualToAnchor:_buttonStateMessageLabel.centerYAnchor]];
_iconImageView.translatesAutoresizingMaskIntoConstraints = NO;
[_constraints addObjectsFromArray:@[
[_iconImageView.widthAnchor constraintEqualToConstant:IconImageViewWidthHeight],
[_iconImageView.heightAnchor constraintEqualToConstant:IconImageViewWidthHeight]
]];
}
// Note, the button width is updated when the AX size changes
_buttonWidthConstraint = [_requestPermissionButton.widthAnchor constraintGreaterThanOrEqualToConstant:ButtonWidth];
// Lower the priority in case the width is too large for the screen
_buttonWidthConstraint.priority = UILayoutPriorityDefaultLow;
[_constraints addObjectsFromArray:@[
_buttonWidthConstraint,
[_contentStackView.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:StandardPadding],
[_contentStackView.trailingAnchor constraintEqualToAnchor:self.trailingAnchor constant:-StandardPadding],
[_contentStackView.topAnchor constraintEqualToAnchor:self.topAnchor constant:StandardPadding],
[_contentStackView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor constant:-ContentStackViewBottomPadding]
]];
[NSLayoutConstraint activateConstraints:_constraints];
}
- (void)updateIconTintColor:(UIColor *)iconTintColor {
if (_iconImageView) {
[_iconImageView setTintColor:[UIColor redColor]];
[_iconImageView setTintColor:iconTintColor];
}
}
@@ -68,11 +68,10 @@
UIView *lastView;
for (ORKRequestPermissionView *cardView in _cardViews) {
[[cardView.topAnchor constraintEqualToAnchor:lastView ? lastView.topAnchor : _contentView.topAnchor constant:10.0] setActive:YES];
[[cardView.centerXAnchor constraintEqualToAnchor:_contentView.centerXAnchor] setActive:YES];
[[cardView.topAnchor constraintEqualToAnchor:lastView ? lastView.bottomAnchor : _contentView.topAnchor constant:10.0] setActive:YES];
[[cardView.leadingAnchor constraintEqualToAnchor:_contentView.leadingAnchor] setActive:YES];
[[cardView.trailingAnchor constraintEqualToAnchor:_contentView.trailingAnchor] setActive:YES];
lastView = cardView;
}
+7 -2
View File
@@ -87,7 +87,7 @@ static const CGFloat kMargin = 25.0;
_slider = [[ORKScaleSlider alloc] initWithFrame:CGRectZero];
_slider.hideValueMarkers = [formatProvider shouldHideValueMarkers];
_slider.isWaitingForUserFeedback = ([formatProvider defaultAnswer] == nil && ![formatProvider isVertical]) ? YES : NO;
_slider.minimumTrackTintColor = [UIColor systemBlueColor];
_slider.minimumTrackTintColor = self.tintColor;
_slider.userInteractionEnabled = YES;
_slider.contentMode = UIViewContentModeRedraw;
self.accessibilityElements = [self.accessibilityElements arrayByAddingObject:_slider];
@@ -228,7 +228,7 @@ static const CGFloat kMargin = 25.0;
UIFontDescriptor *valueLabelDescriptor = [UIFontDescriptor preferredFontDescriptorWithTextStyle:UIFontTextStyleTitle2];
UIFontDescriptor *valueLabelFontDescriptor = [valueLabelDescriptor fontDescriptorWithSymbolicTraits:(UIFontDescriptorTraitBold)];
[_valueLabel setFont: [UIFont fontWithDescriptor:valueLabelFontDescriptor size:[[valueLabelFontDescriptor objectForKey: UIFontDescriptorSizeAttribute] doubleValue]]];
[_valueLabel setTextColor:[UIColor systemBlueColor]];
[_valueLabel setTextColor:self.tintColor];
}
- (void)setUpSliderAndRangeLabels {
@@ -645,6 +645,11 @@ static const CGFloat kMargin = 25.0;
[NSLayoutConstraint activateConstraints:constraints];
}
- (void)tintColorDidChange {
_valueLabel.textColor = self.tintColor;
_slider.minimumTrackTintColor = self.tintColor;
}
- (id<ORKTextScaleAnswerFormatProvider>)textScaleFormatProvider {
if ([[_formatProvider class] conformsToProtocol:@protocol(ORKTextScaleAnswerFormatProvider)]) {
return (id<ORKTextScaleAnswerFormatProvider>)_formatProvider;
@@ -72,7 +72,9 @@
if (self) {
{
_clearButton = [ORKTextButton new];
_clearButton.contentEdgeInsets = (UIEdgeInsets){12,10,8,10}; // insets adjusted to get correct vertical height from bottom of screen when aligned to margin
// insets adjusted to get correct vertical height from bottom of screen when aligned to margin
[_clearButton updateContentInsets:NSDirectionalEdgeInsetsMake(12, 10, 8, 10)];
_clearButton.exclusiveTouch = YES;
[_clearButton setTitle:ORKLocalizedString(@"BUTTON_CLEAR", nil) forState:UIControlStateNormal];
_clearButton.translatesAutoresizingMaskIntoConstraints = NO;
-1
View File
@@ -61,7 +61,6 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, strong, nullable) UIColor *lineColor;
@property (nonatomic) CGFloat lineWidth;
@property (nonatomic) BOOL enabled;
/**
lineWidthVariation defines the max amount by which the line
-5
View File
@@ -548,11 +548,6 @@ static CGPoint mmid_Point(CGPoint p1, CGPoint p2) {
[(ORKSignatureGestureRecognizer *)_signatureGestureRecognizer cancelAutoScrollTimer];
}
- (void)setEnabled:(BOOL)enabled {
_enabled = enabled;
[self setUserInteractionEnabled:enabled];
}
#pragma mark - Accessibility
- (BOOL)isAccessibilityElement {
+9 -2
View File
@@ -236,7 +236,14 @@ static UIWindow *ORKDefaultWindowIfWindowIsNil(UIWindow *window) {
// Use this method instead of UIApplication's keyWindow or UIApplication's delegate's window
// because we may need the window before the keyWindow is set (e.g., if a view controller
// loads programmatically on the app delegate to be assigned as the root view controller)
window = [UIApplication sharedApplication].windows.firstObject;
for (UIScene *scene in UIApplication.sharedApplication.connectedScenes) {
if ([scene.delegate conformsToProtocol:@protocol(UIWindowSceneDelegate)]) {
window = [(id<UIWindowSceneDelegate>)scene.delegate window];
break;
}
}
}
return window;
}
@@ -435,7 +442,7 @@ void ORKUpdateScrollViewBottomInset(UIScrollView *scrollView, CGFloat bottomInse
insets.bottom = bottomInset;
scrollView.contentInset = insets;
insets = scrollView.scrollIndicatorInsets;
insets = scrollView.verticalScrollIndicatorInsets;
insets.bottom = bottomInset;
scrollView.scrollIndicatorInsets = insets;
+1
View File
@@ -188,6 +188,7 @@ typedef NS_CLOSED_ENUM(NSInteger, ORKUpdateConstraintSequence) {
}
_topContentImageView.contentMode = UIViewContentModeScaleAspectFit;
[_topContentImageView setBackgroundColor:ORKColor(ORKTopContentImageViewBackgroundColorKey)];
[_topContentImageView setClipsToBounds:YES];
[self addSubview:_topContentImageView];
[self setTopContentImageViewConstraints];
}
+2 -1
View File
@@ -98,12 +98,13 @@
{
_learnMoreButton = [ORKTextButton new];
_learnMoreButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeading;
_learnMoreButton.contentEdgeInsets = (UIEdgeInsets){10,0,10,10};
[_learnMoreButton setTitle:nil forState:UIControlStateNormal];
[_learnMoreButton addTarget:self action:@selector(learnMoreAction:) forControlEvents:UIControlEventTouchUpInside];
_learnMoreButton.exclusiveTouch = YES;
_learnMoreButton.titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
_learnMoreButton.titleLabel.textAlignment = NSTextAlignmentNatural;
[_learnMoreButton updateContentInsets:NSDirectionalEdgeInsetsMake(10, 0, 10, 10)];
[self addSubview:_learnMoreButton];
self.learnMoreButtonItem = nil;
}
+1 -1
View File
@@ -434,7 +434,7 @@ static const CGFloat iPadStepTitleLabelFontSize = 50.0;
// The default values for a view controller'€™s supported interface orientations is set to
// UIInterfaceOrientationMaskAll for the iPad idiom and UIInterfaceOrientationMaskAllButUpsideDown for the iPhone idiom.
UIInterfaceOrientationMask supportedOrientations = UIInterfaceOrientationMaskAllButUpsideDown;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
supportedOrientations = UIInterfaceOrientationMaskAll;
}
return supportedOrientations;
+2 -2
View File
@@ -142,7 +142,7 @@
UITableView *tableView = ORKFirstObjectOfClass(UITableView, cell, superview);
_cachedContentInsets = tableView.contentInset;
_cachedScrollIndicatorInsets = tableView.scrollIndicatorInsets;
_cachedScrollIndicatorInsets = tableView.verticalScrollIndicatorInsets;
NSDictionary *userInfo = aNotification.userInfo;
CGSize keyboardSize = ((NSValue *)userInfo[UIKeyboardFrameEndUserInfoKey]).CGRectValue.size;
@@ -187,7 +187,7 @@
tableView.contentInset = _cachedContentInsets;
}
if (UIEdgeInsetsEqualToEdgeInsets(tableView.scrollIndicatorInsets, _cachedScrollIndicatorInsets) == NO) {
if (UIEdgeInsetsEqualToEdgeInsets(tableView.verticalScrollIndicatorInsets, _cachedScrollIndicatorInsets) == NO) {
tableView.scrollIndicatorInsets = _cachedScrollIndicatorInsets;
}
}
@@ -35,6 +35,7 @@
#import "ORKAnswerFormat_Internal.h"
#import "ORKQuestionStep.h"
#import "ORKQuestionStep_Internal.h"
#import "ORKHelpers_Internal.h"
#import "ORKSkin.h"
@@ -52,7 +53,7 @@
- (void)prepareView {
[super prepareView];
_selectionView = [[ORKImageSelectionView alloc] initWithImageChoiceAnswerFormat:(ORKImageChoiceAnswerFormat *)self.step.answerFormat answer:self.answer];
_selectionView = [[ORKImageSelectionView alloc] initWithImageChoiceAnswerFormat:(ORKImageChoiceAnswerFormat *)self.step.impliedAnswerFormat answer:self.answer];
_selectionView.delegate = self;
_selectionView.frame = self.bounds;
@@ -67,7 +67,7 @@
- (void)prepareView {
_selectionView = [[ORKLocationSelectionView alloc] initWithFormMode:NO
useCurrentLocation:((ORKLocationAnswerFormat *)self.step.answerFormat).useCurrentLocation
useCurrentLocation:((ORKLocationAnswerFormat *)self.step.impliedAnswerFormat).useCurrentLocation
leadingMargin:self.separatorInset.left];
_selectionView.delegate = self;
_selectionView.tintColor = self.tintColor;
@@ -76,7 +76,7 @@ static const CGFloat DontKnowButtonTopBottomPadding = 16.0;
_dontKnowButtonActive = NO;
ORKNumericAnswerFormat *numericAnswerFormat = (ORKNumericAnswerFormat *)self.step.answerFormat;
ORKNumericAnswerFormat *numericAnswerFormat = (ORKNumericAnswerFormat *)[self.step impliedAnswerFormat];
_textFieldView = [[ORKTextFieldView alloc] init];
_textFieldView.hideUnitWhenAnswerEmpty = numericAnswerFormat.hideUnitWhenAnswerIsEmpty;
@@ -31,6 +31,7 @@
#import "ORKSurveyAnswerCellForSES.h"
#import "ORKSESSelectionView.h"
#import "ORKQuestionStep.h"
#import "ORKQuestionStep_Internal.h"
@interface ORKSurveyAnswerCellForSES() <ORKSESSelectionViewDelegate>
@@ -50,7 +51,7 @@
- (void)prepareView {
[super prepareView];
_selectionView = [[ORKSESSelectionView alloc] initWithAnswerFormat:(ORKSESAnswerFormat *)self.step.answerFormat answer:self.answer];
_selectionView = [[ORKSESSelectionView alloc] initWithAnswerFormat:(ORKSESAnswerFormat *)self.step.impliedAnswerFormat answer:self.answer];
_selectionView.delegate = self;
_selectionView.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:_selectionView];
@@ -69,7 +69,7 @@ static const CGFloat CellBottomPadding = 5.0;
}
- (void)applyAnswerFormat {
ORKAnswerFormat *answerFormat = [self.step.answerFormat impliedAnswerFormat];
ORKAnswerFormat *answerFormat = [self.step impliedAnswerFormat];
if ([answerFormat isKindOfClass:[ORKTextAnswerFormat class]]) {
ORKTextAnswerFormat *textAnswerFormat = (ORKTextAnswerFormat *)answerFormat;
@@ -512,6 +512,8 @@ ORK_CLASS_AVAILABLE
*/
@property (nonatomic, strong, readonly, nullable) ORKStepViewController *currentStepViewController;
- (void)flipToFirstPage;
/**
Forces navigation to the next step.
+41 -16
View File
@@ -101,18 +101,27 @@ typedef void (^_ORKLocationAuthorizationRequestHandler)(BOOL success);
}
_started = YES;
NSString *whenInUseKey = (NSString *)[[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationWhenInUseUsageDescription"];
NSString *alwaysKey = (NSString *)[[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationAlwaysUsageDescription"];
NSString *allowedWhenInUse = (NSString *)[[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationWhenInUseUsageDescription"];
NSString *allowedAlways = (NSString *)[[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationAlwaysUsageDescription"];
CLAuthorizationStatus status = [CLLocationManager authorizationStatus];
if ((status == kCLAuthorizationStatusNotDetermined) && (whenInUseKey || alwaysKey)) {
if (alwaysKey) {
[_manager requestAlwaysAuthorization];
if (_manager) {
CLAuthorizationStatus status = kCLAuthorizationStatusNotDetermined;
if (@available(iOS 14.0, *)) {
status = _manager.authorizationStatus;
} else {
[_manager requestWhenInUseAuthorization];
status = [CLLocationManager authorizationStatus];
}
if ((status == kCLAuthorizationStatusNotDetermined) && (allowedWhenInUse || allowedAlways)) {
if (allowedAlways) {
[_manager requestAlwaysAuthorization];
} else {
[_manager requestWhenInUseAuthorization];
}
} else {
[self finishWithResult:(status != kCLAuthorizationStatusDenied)];
}
} else {
[self finishWithResult:(status != kCLAuthorizationStatusDenied)];
}
}
@@ -123,12 +132,27 @@ typedef void (^_ORKLocationAuthorizationRequestHandler)(BOOL success);
}
}
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
if (_handler && _started && status != kCLAuthorizationStatusNotDetermined) {
- (void)locationManagerDidChangeAuthorization:(CLLocationManager *)manager {
CLAuthorizationStatus status = kCLAuthorizationStatusNotDetermined;
if (@available(iOS 14.0, *)) {
status = manager.authorizationStatus;
} else {
status = [CLLocationManager authorizationStatus];
}
if (_started && status != kCLAuthorizationStatusNotDetermined) {
[self finishWithResult:(status != kCLAuthorizationStatusDenied)];
}
}
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
if (_started && status != kCLAuthorizationStatusNotDetermined) {
[self finishWithResult:(status != kCLAuthorizationStatusDenied)];
}
}
@end
@@ -184,6 +208,7 @@ static NSString *const _ChildNavigationControllerRestorationKey = @"childNavigat
[_childNavigationController.navigationBar setShadowImage:[UIImage new]];
[_childNavigationController.navigationBar setTranslucent:NO];
[_childNavigationController.navigationBar setBarTintColor:ORKColor(ORKBackgroundColorKey)];
[_childNavigationController.navigationBar setBackgroundColor:ORKColor(ORKBackgroundColorKey)];
if (@available(iOS 13.0, *)) {
[_childNavigationController.navigationBar setTitleTextAttributes:@{NSForegroundColorAttributeName : [UIColor secondaryLabelColor]}];
@@ -250,11 +275,11 @@ static NSString *const _ChildNavigationControllerRestorationKey = @"childNavigat
self.delegate = delegate;
if (data != nil) {
self.restorationClass = [self class];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingFromData:data error:nil];
[self decodeRestorableStateWithCoder:unarchiver];
[self applicationFinishedRestoringState];
if (unarchiver == nil) {
if (unarchiver == nil && errorOut != nil) {
*errorOut = [NSError errorWithDomain:ORKErrorDomain code:ORKErrorException userInfo:@{NSLocalizedDescriptionKey: ORKLocalizedString(@"RESTORE_ERROR_CANNOT_DECODE", nil)}];
}
}
@@ -724,12 +749,11 @@ static NSString *const _ChildNavigationControllerRestorationKey = @"childNavigat
}
- (NSData *)restorationData {
NSMutableData *data = [[NSMutableData alloc] init];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initRequiringSecureCoding:YES];
[self encodeRestorableStateWithCoder:archiver];
[archiver finishEncoding];
return [data copy];
return [archiver.encodedData copy];
}
- (void)ensureDirectoryExists:(NSURL *)outputDirectory {
@@ -1290,6 +1314,7 @@ static NSString *const _ChildNavigationControllerRestorationKey = @"childNavigat
}
- (void)flipToPreviousPageFrom:(ORKStepViewController *)fromController animated:(BOOL)animated {
if (fromController != _currentStepViewController) {
return;
}
+2
View File
@@ -47,6 +47,8 @@ ORK_CLASS_AVAILABLE
- (void)init_ORKTextButton;
- (void)updateContentInsets:(NSDirectionalEdgeInsets)contentInsets;
@end
NS_ASSUME_NONNULL_END
+8 -24
View File
@@ -72,6 +72,14 @@
[self setTitleColor:[[self tintColor] colorWithAlphaComponent:0.7] forState:UIControlStateHighlighted];
}
- (void)updateContentInsets:(NSDirectionalEdgeInsets)contentInsets {
if (@available(iOS 15.0, *)) {
UIButtonConfiguration *buttonConfiguration = [UIButtonConfiguration plainButtonConfiguration];
[buttonConfiguration setContentInsets:contentInsets];
[self setConfiguration:buttonConfiguration];
}
}
- (void)updateAppearance {
self.titleLabel.font = [[self class] defaultFont];
@@ -89,30 +97,6 @@
return [UIFont systemFontOfSize:((NSNumber *)[descriptor objectForKey: UIFontDescriptorSizeAttribute]).doubleValue + 2.0];
}
- (CGSize)intrinsicContentSize {
UILabel *label = nil;
for (UIView *view in self.subviews) {
if ([view isKindOfClass:[UILabel class]]) {
label = (UILabel *)view;
break;
}
}
// This is to avoid calling self.titleLabel when recalculation of content size is not needed.
// Calling self.titleLabel at here can cause weird layout error.
if (label && label.preferredMaxLayoutWidth > 0 && self.currentTitle.length > 0) {
CGSize labelSize = [self.titleLabel sizeThatFits:CGSizeMake(self.titleLabel.preferredMaxLayoutWidth, CGFLOAT_MAX)];
CGFloat verticalPadding = MAX(self.contentEdgeInsets.top, self.titleEdgeInsets.top) + MAX(self.contentEdgeInsets.bottom, self.titleEdgeInsets.bottom);
CGFloat horizontalPadding = MAX(self.contentEdgeInsets.left, self.titleEdgeInsets.left) + MAX(self.contentEdgeInsets.right, self.titleEdgeInsets.right);
return CGSizeMake(labelSize.width+horizontalPadding,
labelSize.height+verticalPadding);
}
return [super intrinsicContentSize];
}
- (UIAccessibilityTraits)accessibilityTraits {
// prevent VoiceOver from speaking "dimmed" when transitioning between pages
if (self.isInTransition) {
@@ -194,6 +194,14 @@
[super viewWillDisappear:animated];
}
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
if (_videoCaptureView) {
[_videoCaptureView orientationDidChange];
}
}
- (void)queue_SetupCaptureSession {
// Create the session
_captureSession = [AVCaptureSession new];
+2
View File
@@ -61,6 +61,8 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, strong, nullable) ORKVideoCaptureCameraPreviewView *previewView;
@property (nonatomic, strong, readonly) AVPlayerViewController *playerViewController;
- (void)orientationDidChange;
@end
NS_ASSUME_NONNULL_END
+22 -17
View File
@@ -100,7 +100,6 @@
[_navigationFooterView setAlpha:0.8];
[self addSubview:_navigationFooterView];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationDidChange) name:UIApplicationDidChangeStatusBarOrientationNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(queue_sessionRunning) name:AVCaptureSessionDidStartRunningNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sessionWasInterrupted:) name:AVCaptureSessionWasInterruptedNotification object:self.session];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sessionInterruptionEnded:) name:AVCaptureSessionInterruptionEndedNotification object:self.session];
@@ -121,24 +120,30 @@
}
- (void)orientationDidChange {
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
AVCaptureVideoOrientation orientation = AVCaptureVideoOrientationPortrait;
switch ([[UIApplication sharedApplication] statusBarOrientation]) {
case UIInterfaceOrientationLandscapeRight:
orientation = AVCaptureVideoOrientationLandscapeRight;
break;
case UIInterfaceOrientationLandscapeLeft:
orientation = AVCaptureVideoOrientationLandscapeLeft;
break;
case UIInterfaceOrientationPortraitUpsideDown:
orientation = AVCaptureVideoOrientationPortraitUpsideDown;
break;
case UIInterfaceOrientationPortrait:
orientation = AVCaptureVideoOrientationPortrait;
break;
case UIInterfaceOrientationUnknown:
// Do nothing in these cases, since we don't need to change display orientation.
return;
UIWindowScene *windowScene = weakSelf.window.windowScene;
if (windowScene) {
switch (windowScene.interfaceOrientation) {
case UIInterfaceOrientationLandscapeRight:
orientation = AVCaptureVideoOrientationLandscapeRight;
break;
case UIInterfaceOrientationLandscapeLeft:
orientation = AVCaptureVideoOrientationLandscapeLeft;
break;
case UIInterfaceOrientationPortraitUpsideDown:
orientation = AVCaptureVideoOrientationPortraitUpsideDown;
break;
case UIInterfaceOrientationPortrait:
orientation = AVCaptureVideoOrientationPortrait;
break;
case UIInterfaceOrientationUnknown:
// Do nothing in these cases, since we don't need to change display orientation.
return;
}
}
[_previewView setVideoOrientation:orientation];
+1 -1
View File
@@ -71,7 +71,7 @@
if (@available(iOS 13.0, *)) {
_activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleMedium];
} else {
_activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
_activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleMedium];
}
[_activityIndicatorView startAnimating];
self.customContentView = _activityIndicatorView;
@@ -397,10 +397,6 @@ static const CGFloat ORKSignatureTopPadding = 37.0;
// MARK: WKWebViewDelegate
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation {
[UIApplication sharedApplication].networkActivityIndicatorVisible = true;
}
- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation {
[webView evaluateJavaScript:@"document.readyState" completionHandler:^(id complete, NSError *readyError) {
if (complete != nil) {
@@ -421,8 +417,6 @@ static const CGFloat ORKSignatureTopPadding = 37.0;
}
}
}];
[UIApplication sharedApplication].networkActivityIndicatorVisible = false;
}
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
@@ -438,24 +432,13 @@ static const CGFloat ORKSignatureTopPadding = 37.0;
// MARK: UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
BOOL enabled = [self shouldEnableSignatureView] && scrollView.isDecelerating;
[_signatureView setEnabled:enabled];
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if ([_scrollView.panGestureRecognizer translationInView:_scrollView.superview].y > 0) {
// Scrolling upward
[_signatureView cancelAutoScrollTimer];
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
[_signatureView setEnabled:[self shouldEnableSignatureView]];
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
[_signatureView setEnabled:[self shouldEnableSignatureView]];
}
- (BOOL)shouldEnableSignatureView {
CGFloat bottomOfSignature = _signatureView.frame.size.height + _signatureView.frame.origin.y;
CGFloat signaturePosition = _scrollView.contentOffset.y + _scrollView.frame.size.height;
@@ -207,7 +207,7 @@ static NSString *const _FamilyNameIdentifier = @"family";
givenNameFormItem.optional = NO;
familyNameFormItem.optional = NO;
ORKFormItem *sectionTitleFormItem = [[ORKFormItem alloc] initWithSectionTitle:ORKLocalizedString(@"CONSENT_NAME_SECTION_TITLE", nil)];
ORKFormItem *sectionTitleFormItem = [[ORKFormItem alloc] initWithSectionTitle:ORKLocalizedString(@"CONSENT_NAME_TITLE", nil)];
NSArray *formItems = @[sectionTitleFormItem, givenNameFormItem, familyNameFormItem];
if (ORKCurrentLocalePresentsFamilyNameFirst())
+1 -1
View File
@@ -46,7 +46,7 @@ NSURL *ORKMovieURLForConsentSectionType(ORKConsentSectionType type) {
CGFloat scale = [UIScreen mainScreen].scale;
// For iPad, use the movie for the next scale up
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad && scale < 3) {
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad && scale < 3) {
scale++;
}
@@ -34,6 +34,8 @@
NS_ASSUME_NONNULL_BEGIN
//__attribute__((unavailable("ORKEAGLMoviePlayerView has been deprecated. Please use ORKInstructionStep instead.")))
API_DEPRECATED("Use ORKInstructionStep for obtaining consent.", ios(2.0, 11.0)) API_UNAVAILABLE(tvos, watchos, macos)
@interface ORKEAGLMoviePlayerView : UIView
@property (nonatomic) CGSize presentationSize;
@@ -123,6 +123,8 @@ static const GLfloat ColorConversion709[] = {
@end
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-implementations"
@implementation ORKEAGLMoviePlayerView
const GLfloat DefaultPreferredRotation = 0;
@@ -732,3 +734,4 @@ const GLfloat DefaultPreferredRotation = 0;
}
@end
#pragma clang diagnostic pop
+2 -1
View File
@@ -51,7 +51,8 @@ NS_ASSUME_NONNULL_BEGIN
An `ORKVisualConsentStep` object produces an `ORKStepResult` object, in which the dates indicate the total amount of time participants have spent in the consent process, and the route by which they can exit the consent process.
*/
ORK_CLASS_AVAILABLE
API_DEPRECATED("Use ORKInstructionStep for obtaining consent.", ios(3.0, 11.0)) API_UNAVAILABLE(watchos, tvos)
@interface ORKVisualConsentStep : ORKStep
/**
+3 -1
View File
@@ -38,7 +38,8 @@
#import "ORKHelpers_Internal.h"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-implementations"
@implementation ORKVisualConsentStep
+ (Class)stepViewControllerClass {
@@ -90,3 +91,4 @@
}
@end
#pragma clang diagnostic pop
@@ -88,8 +88,10 @@
@interface ORKAnimationPlaceholderView : UIView
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
@property (nonatomic, strong) ORKEAGLMoviePlayerView *playerView;
#pragma clang diagnostic pop
- (void)scrollToTopAnimated:(BOOL)animated completion:(void (^)(BOOL finished))completion;
@end
@@ -100,7 +102,10 @@
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
_playerView = [ORKEAGLMoviePlayerView new];
#pragma clang diagnostic pop
_playerView.hidden = YES;
[self addSubview:_playerView];
}
@@ -176,9 +181,12 @@
[self showViewController:[self viewControllerForIndex:0] forward:YES animated:NO];
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
- (ORKEAGLMoviePlayerView *)animationPlayerView {
return [(ORKAnimationPlaceholderView *)_animationView playerView];
}
#pragma clang diagnostic pop
- (void)viewDidLoad {
[super viewDidLoad];
@@ -208,10 +216,13 @@
[self updatePageIndex];
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
- (ORKVisualConsentStep *)visualConsentStep {
assert(!self.step || [self.step isKindOfClass:[ORKVisualConsentStep class]]);
return (ORKVisualConsentStep *)self.step;
}
#pragma clang diagnostic pop
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
@@ -37,8 +37,10 @@ NS_ASSUME_NONNULL_BEGIN
@class ORKEAGLMoviePlayerView;
@interface ORKVisualConsentStepViewController ()
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
- (nullable ORKEAGLMoviePlayerView *)animationPlayerView;
#pragma clang diagnostic pop
@end
@@ -229,9 +229,12 @@
- (void)initialFrameDidDisplay {
// Once our initial frame has definitely been drawn, we make ourselves visible
// and signal the caller that the animation has started.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
ORKEAGLMoviePlayerView *playerView = [_stepViewController animationPlayerView];
playerView.hidden = NO;
#pragma clang diagnostic pop
if (_pendingContext && !_pendingContext.hasCalledLoadHandler) {
if (_pendingContext.loadHandler) {
_pendingContext.loadHandler(self, _pendingContext.direction);
@@ -257,7 +260,11 @@
CVPixelBufferRef pixelBuffer = NULL;
pixelBuffer = [_videoOutput copyPixelBufferForItemTime:outputItemTime itemTimeForDisplay:NULL];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
ORKEAGLMoviePlayerView *playerView = [_stepViewController animationPlayerView];
#pragma clang diagnostic pop
CGSize playerItemPresentationSize = _playerItem.presentationSize;
if (!CGSizeEqualToSize(playerView.presentationSize, playerItemPresentationSize)) {
playerView.presentationSize = playerItemPresentationSize;
+1 -1
View File
@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>2.0.0</string>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
@@ -394,11 +394,14 @@
/* Reaction time active task. */
"REACTION_TIME_TASK_TITLE" = "Reaction Time";
"REACTION_TIME_TASK_NORM_BUTTON_TITLE" = "hold";
"REACTION_TIME_TASK_INTENDED_USE" = "This activity evaluates the time it takes for you to respond to a visual cue.";
"REACTION_TIME_TASK_INTRO_TEXT_FORMAT" = "Shake the device in any direction as soon as the blue dot appears on screen. You will be asked to do this %D times.";
"REACTION_TIME_TASK_CALL_TO_ACTION" = "Tap Get Started to begin.";
"REACTION_TIME_TASK_ATTEMPTS_FORMAT" = "Attempt %@ of %@";
"REACTION_TIME_TASK_ACTIVE_STEP_TITLE" = "Quickly shake the device when the blue circle appears";
"REACTION_TIME_NORMALIZED_TASK_ACTIVE_STEP_TITLE" = "Tap the hold button, and then the blue circle";
/* Stroop active task.*/
"STROOP_TASK_TITLE" = "Stroop";
@@ -413,6 +416,11 @@
"STROOP_COLOR_GREEN_INITIAL" = "G";
"STROOP_COLOR_BLUE_INITIAL" = "B";
"STROOP_COLOR_YELLOW_INITIAL" = "Y";
"TIME_OUT_TILE" = "Time Out";
"TIME_OUT_BODY" = "You have not responded in 30 seconds. Would you like to restart?";
"TIME_OUT_RESTART_ACTION" = "Restart";
"TIME_OUT_END_ACTION" = "End Task";
/* Tower of Hanoi active task */
"TOWER_OF_HANOI_TASK_TITLE" = "Tower of Hanoi";
@@ -438,10 +446,10 @@
"KNEE_RANGE_OF_MOTION_TEXT_INSTRUCTION_2_RIGHT" = "Place your device on your right knee with the screen facing out, as pictured.";
"KNEE_RANGE_OF_MOTION_TEXT_INSTRUCTION_3_LEFT" = "When ready, tap the screen to begin and extend your left knee as far as you can. Return to the start position and tap again when you are done.";
"KNEE_RANGE_OF_MOTION_TEXT_INSTRUCTION_3_RIGHT" = "When ready, tap the screen to begin and extend your right knee as far as you can. Return to the start position and tap again when you are done.";
"KNEE_RANGE_OF_MOTION_TOUCH_ANYWHERE_STEP_INSTRUCTION_LEFT" = "Place your device on your left knee. Tap the screen and extend your left knee as far as you can.";
"KNEE_RANGE_OF_MOTION_TOUCH_ANYWHERE_STEP_INSTRUCTION_RIGHT" = "Place your device on your right knee. Tap the screen and extend your right knee as far as you can.";
"KNEE_RANGE_OF_MOTION_SPOKEN_INSTRUCTION_LEFT" = "When you are done, return your left knee to the start position. Then tap anywhere.";
"KNEE_RANGE_OF_MOTION_SPOKEN_INSTRUCTION_RIGHT" = "When you are done, return your right knee to the start position. Then tap anywhere.";
"KNEE_RANGE_OF_MOTION_TOUCH_ANYWHERE_STEP_INSTRUCTION_LEFT" = "Place your device on your left knee.\n\nTap the screen and extend your left knee as far as you can.";
"KNEE_RANGE_OF_MOTION_TOUCH_ANYWHERE_STEP_INSTRUCTION_RIGHT" = "Place your device on your right knee.\n\nTap the screen and extend your right knee as far as you can.\n";
"KNEE_RANGE_OF_MOTION_SPOKEN_INSTRUCTION_LEFT" = "When you are done, return your left knee to the start position.\n\nThen tap anywhere.";
"KNEE_RANGE_OF_MOTION_SPOKEN_INSTRUCTION_RIGHT" = "When you are done, return your right knee to the start position.\n\nThen tap anywhere.";
/* Shoulder Range of Motion active task */
"SHOULDER_RANGE_OF_MOTION_TITLE_LEFT" = "Left Shoulder Range of Motion";
@@ -653,13 +661,23 @@
/* Review Step */
"REVIEW_STEP_PAGE" = "Page";
/* Request Permission Step */
"REQUEST_PERMISSION_BUTTON_STATE_DEFAULT" = "Review";
"REQUEST_PERMISSION_BUTTON_STATE_CONNECTED" = "Reviewed";
"REQUEST_PERMISSION_BUTTON_STATE_NOT_SUPPORTED" = "Not Supported";
"REQUEST_PERMISSION_BUTTON_STATE_ERROR" = "Error";
/* Request Health Data Step */
"REQUEST_HEALTH_DATA_STEP_VIEW_TITLE" = "Health App Data";
"REQUEST_HEALTH_DATA_STEP_VIEW_DESCRIPTION" = "The study is requesting data from the Health app";
"REQUEST_HEALTH_DATA_STEP_BUTTON_STATE_DEFAULT" = "Review Request";
"REQUEST_HEALTH_DATA_STEP_BUTTON_STATE_CONNECTED" = "Requested";
"REQUEST_HEALTH_DATA_STEP_BUTTON_STATE_NOT_SUPPORTED" = "HealthKit not supported by this device";
"REQUEST_HEALTH_DATA_STEP_BUTTON_STATE_ERROR" = "Encountered error";
"REQUEST_HEALTH_DATA_STEP_VIEW_TITLE" = "Health Data";
"REQUEST_HEALTH_DATA_STEP_VIEW_DESCRIPTION" = "The study is requesting access to Health data.";
/* Request Notification Permission Step */
"REQUEST_NOTIFICATIONS_STEP_VIEW_TITLE" = "Notifications";
"REQUEST_NOTIFICATIONS_STEP_VIEW_DESCRIPTION" = "The study is requesting to send you notifications.";
/* Request Device Motion Permission Step */
"REQUEST_MOTION_ACTIVITY_STEP_VIEW_TITLE" = "Device Motion";
"REQUEST_MOTION_ACTIVITY_STEP_VIEW_DESCRIPTION" = "The study is requesting access to motion data from your devices.";
/* Accessibility */
"AX_BUTTON_BACK" = "Back";
+2
View File
@@ -147,5 +147,7 @@
#import <ResearchKit/ORKUSDZModelManager.h>
#import <ResearchKit/ORKPermissionType.h>
#import <ResearchKit/ORKHealthKitPermissionType.h>
#import <ResearchKit/ORKNotificationPermissionType.h>
#import <ResearchKit/ORKMotionActivityPermissionType.h>
#import <ResearchKit/ORKDeprecated.h>
+1
View File
@@ -67,6 +67,7 @@
#import <ResearchKit/ORKPSATStep.h>
#import <ResearchKit/ORKRangeOfMotionStep.h>
#import <ResearchKit/ORKReactionTimeStep.h>
#import <ResearchKit/ORKNormalizedReactionTimeStep.h>
#import <ResearchKit/ORKShoulderRangeOfMotionStep.h>
#import <ResearchKit/ORKSpatialSpanMemoryStep.h>
#import <ResearchKit/ORKStroopStep.h>
@@ -33,15 +33,14 @@ import UIKit
class ORKContinueButtonTests: XCTestCase {
var button: ORKContinueButton!
var button = ORKContinueButton(title: "BUTTON", isDoneButton: true)
override func setUp() {
super.setUp()
button = ORKContinueButton(title: "BUTTON", isDoneButton: true)
}
func testAttributes() {
XCTAssertEqual(button.titleLabel?.text, "BUTTON")
XCTAssertEqual(button.currentTitle, "BUTTON")
XCTAssertEqual(button.isDoneButton, true)
}
}
+2 -2
View File
@@ -124,8 +124,8 @@
ORKTaskResult *taskResult1 = [self createTaskResultTree];
// Archive
id data = [NSKeyedArchiver archivedDataWithRootObject:taskResult1];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
id data = [NSKeyedArchiver archivedDataWithRootObject:taskResult1 requiringSecureCoding:YES error:nil];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingFromData:data error:nil];
unarchiver.requiresSecureCoding = YES;
ORKTaskResult *taskResult2 = [unarchiver decodeObjectOfClass:[ORKTaskResult class] forKey:NSKeyedArchiveRootObjectKey];
+51
View File
@@ -0,0 +1,51 @@
/*
Copyright (c) 2022, 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 XCTest
@testable import ResearchKit
class ORKTextButtonTests: XCTestCase {
let button = ORKTextButton(type: .custom)
override func setUp() {
super.setUp()
button.updateContentInsets(NSDirectionalEdgeInsets(top: 10, leading: 0, bottom: 10, trailing: 10))
}
func testProperties() {
XCTAssertNotNil(button.configuration)
XCTAssertEqual(button.configuration!.contentInsets.top, 10);
XCTAssertEqual(button.configuration!.contentInsets.leading, 0);
XCTAssertEqual(button.configuration!.contentInsets.bottom, 10);
XCTAssertEqual(button.configuration!.contentInsets.trailing, 10);
}
}
@@ -692,7 +692,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -737,7 +737,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
@@ -392,6 +392,36 @@ typedef NS_OPTIONS (NSInteger, SampleDataType) {
XCTAssertEqual(_errorCount, 2);
}
- (void)testDataCollectorSerialization {
ORKMotionActivityCollector *motionCollector;
ORKHealthCollector *healthCollector;
ORKHealthCorrelationCollector *healthCorrelationCollector;
NSError *error;
// Create
ORKDataCollectionManager *manager = createManagerWithCollectors([NSURL fileURLWithPath:[self cleanStorePath]],
[NSDate date],
&motionCollector,
&healthCollector,
&healthCorrelationCollector,
&error);
XCTAssertNil(error);
XCTAssertEqual(manager.collectors.count, 3);
ORKDataCollectionState *state = [[ORKDataCollectionState alloc] init];
[state setCollectors:manager.collectors];
id data = [NSKeyedArchiver archivedDataWithRootObject:state requiringSecureCoding:YES error:&error];
XCTAssertNil(error);
id decodedState = [NSKeyedUnarchiver unarchivedObjectOfClass:[ORKDataCollectionState self] fromData:data error:nil];
XCTAssertNotNil(decodedState);
XCTAssertTrue([decodedState isKindOfClass:[ORKDataCollectionState self]]);
}
#pragma mark - delegate
- (BOOL)healthCollector:(ORKHealthCollector *)collector

Some files were not shown because too many files have changed in this diff Show More