Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 29950b62e4 | |||
| 65de4b333c | |||
| 63fcf4918f | |||
| c9880b0139 | |||
| 13eabb7720 | |||
| 743b773ea3 | |||
| 249eee5dfb | |||
| 90c68d0d19 | |||
| d10a427911 | |||
| 05755a3213 | |||
| e18a633de1 | |||
| 0e68cdf744 | |||
| 7f119a8d0d | |||
| fde1e7e957 | |||
| 0ad96d505c | |||
| d4ff76fc25 | |||
| 85c1395361 | |||
| 1443e57c57 | |||
| 3b75f6213c | |||
| e9d5de64a5 | |||
| 19c61383f6 | |||
| cb6cc97fa4 | |||
| 0651bf0c2a | |||
| ae9b9e57cb |
+2
-2
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}";
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -243,7 +243,6 @@ static const CGFloat PieToLegendPadding = 8.0;
|
||||
|
||||
- (void)setBounds:(CGRect)bounds {
|
||||
[super setBounds:bounds];
|
||||
_shouldInvalidateLegendViewIntrinsicContentSize = YES;
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -47,6 +47,8 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
- (void)init_ORKTextButton;
|
||||
|
||||
- (void)updateContentInsets:(NSDirectionalEdgeInsets)contentInsets;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user