Compare commits

...

4 Commits

Author SHA1 Message Date
jessi-apple 8b2cf8889a Merge pull request #1592 from simon-apple/dev/swiftUI-beta-update
Public beta 2 - SwiftUI
2025-01-24 09:46:17 -08:00
Simon Tsai 3f83f6ab4f RK SwiftUI Beta 2 Release (#2534) 2025-01-22 11:18:03 -08:00
Corey 7abce4c982 fix: Use accent color instead of hardcoded blue (#1590) 2024-12-19 15:04:39 -08:00
simon-apple eedd39d0a8 Public beta release - SwiftUI 2024-10-21 14:54:38 -07:00
105 changed files with 8100 additions and 1594 deletions
+72
View File
@@ -79,6 +79,78 @@ The height question is presented in the figure below.
|---|---|
| ![height-question](https://github.com/ResearchKit/ResearchKit/assets/29615893/4f425329-83b7-45c3-84f9-58cdbcaf2529) | ![height-question-2](https://github.com/ResearchKit/ResearchKit/assets/29615893/2cc0dc2c-5c2a-4b50-a4be-834363fb64b5) |
### ResearchKit SwiftUI
We are excited to announce the release of a new beta API for surveys in ResearchKit. This API is designed to enhance the flexibility, customization, and cross-platform compatibility of surveys in your ResearchKit apps. Below are the key features and usage details.
New Form APIs offer an easily configurable and flexible UI, with the same look and feel of `ORKFormStep`:
* `ResearchForm`
* Manages the navigation between steps in a survey.
* `ResearchFormStep`
* Represents a step in a survey and lays out the header and questions on one page. Question numbers (e.g. 1 of 3) are automatically added at the top of each question to denote progress in a step.
* `ResearchFormCompletion`
* Represents the context for a survey's completion
* `ResearchFormResult`
* Represents responses for the different kinds of questions.
* `StepHeader`
* A step header containing an image, title, and subtitle.
* `QuestionHeader`
* A question header containing a title and detail.
* `InstructionBodyItem`
* Displays an image and text side by side.
* `questionRequired` (`ViewModifier`)
* Designates a question as required or optional.
#### Survey Question Types:
* `MultipleChoiceQuestion`
* `HeightQuestion`
* `WeightQuestion`
* `SliderQuestion`
* `TextQuestion`
* `DateTimeQuestion`
* `NumericQuestion`
* `ImageChoiceQuestion`
The example below shows how to create a `ResearchForm` to present a text question for the participant to answer, and then save their results.
```swift
import ResearchKitSwiftUI
ResearchForm(
id: "SurveyTask",
steps: {
ResearchFormStep(
title: "Demographics",
subtitle: "Tell us about yourself",
content: {
TextQuestion(
id: "textQuestion",
title: "What is your name?",
prompt: "Enter your name here",
lineLimit: .singleLine,
characterLimit: 0
)
.questionRequired(true)
}
)
},
onResearchFormCompletion: { completion in
switch completion {
case .completed(let results):
save(results)
case .discarded:
cancel()
default:
cancel()
}
}
)
```
#### Install as an embedded framework
Download the project source code and drag in the *ResearchKitSwiftUI* folder. In the dialog that pops up, choose to copy files to destination and create folders. Then hit the finish button. Finally, embed the *ResearchKitSwiftUI* framework in your app by adding it to the "Frameworks, Libraries, and Embedded Content" section for your target.
# Consent <a name="consent"></a>
The *ResearchKit framework* provides classes that you can customize to explain the
+3
View File
@@ -7,4 +7,7 @@
<FileRef
location = "group:samples/ORKCatalog/ORKCatalog.xcodeproj">
</FileRef>
<FileRef
location = "group:ResearchKitSwiftUI/ResearchKitSwiftUI.xcodeproj">
</FileRef>
</Workspace>
+213 -187
View File
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objectVersion = 60;
objects = {
/* Begin PBXAggregateTarget section */
@@ -24,19 +24,19 @@
/* End PBXAggregateTarget section */
/* Begin PBXBuildFile section */
00B1F7852241503900D022FE /* Speech.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00B1F7842241503900D022FE /* Speech.framework */; };
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 */; };
00B1F7852241503900D022FE /* Speech.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00B1F7842241503900D022FE /* Speech.framework */; platformFilter = ios; };
00C2668E23022CD400337E0B /* ORKCustomStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 00C2668C23022CD400337E0B /* ORKCustomStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
00C2668F23022CD400337E0B /* ORKCustomStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 00C2668D23022CD400337E0B /* ORKCustomStep.m */; platformFilter = ios; };
03057F492518ECDC00C4EC5B /* ORKAudioStepViewControllerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 03057F482518ECDC00C4EC5B /* ORKAudioStepViewControllerTests.m */; };
031A0FC124CF4ECD000E4455 /* ORKSensorPermissionType.h in Headers */ = {isa = PBXBuildFile; fileRef = 031A0FBF24CF4ECD000E4455 /* ORKSensorPermissionType.h */; settings = {ATTRIBUTES = (Public, ); }; };
031A0FC224CF4ECD000E4455 /* ORKSensorPermissionType.m in Sources */ = {isa = PBXBuildFile; fileRef = 031A0FC024CF4ECD000E4455 /* ORKSensorPermissionType.m */; };
031A0FC124CF4ECD000E4455 /* ORKSensorPermissionType.h in Headers */ = {isa = PBXBuildFile; fileRef = 031A0FBF24CF4ECD000E4455 /* ORKSensorPermissionType.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
031A0FC224CF4ECD000E4455 /* ORKSensorPermissionType.m in Sources */ = {isa = PBXBuildFile; fileRef = 031A0FC024CF4ECD000E4455 /* ORKSensorPermissionType.m */; platformFilter = ios; };
0324C1D825439E1800BBE77B /* ORKVideoInstructionStepViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0324C1D725439E1800BBE77B /* ORKVideoInstructionStepViewControllerTests.swift */; };
036B1E8D25351BAD008483DF /* ORKMotionActivityPermissionType.h in Headers */ = {isa = PBXBuildFile; fileRef = 036B1E8B25351BAD008483DF /* ORKMotionActivityPermissionType.h */; settings = {ATTRIBUTES = (Public, ); }; };
036B1E8E25351BAD008483DF /* ORKMotionActivityPermissionType.m in Sources */ = {isa = PBXBuildFile; fileRef = 036B1E8C25351BAD008483DF /* ORKMotionActivityPermissionType.m */; };
03BD9EA3253E62A0008ADBE1 /* ORKBundleAsset.h in Headers */ = {isa = PBXBuildFile; fileRef = 03BD9EA1253E62A0008ADBE1 /* ORKBundleAsset.h */; settings = {ATTRIBUTES = (Public, ); }; };
03BD9EA4253E62A0008ADBE1 /* ORKBundleAsset.m in Sources */ = {isa = PBXBuildFile; fileRef = 03BD9EA2253E62A0008ADBE1 /* ORKBundleAsset.m */; };
03EDD58024CA6B1D006245E9 /* ORKNotificationPermissionType.h in Headers */ = {isa = PBXBuildFile; fileRef = 03EDD57E24CA6B1D006245E9 /* ORKNotificationPermissionType.h */; settings = {ATTRIBUTES = (Public, ); }; };
03EDD58124CA6B1D006245E9 /* ORKNotificationPermissionType.m in Sources */ = {isa = PBXBuildFile; fileRef = 03EDD57F24CA6B1D006245E9 /* ORKNotificationPermissionType.m */; };
036B1E8D25351BAD008483DF /* ORKMotionActivityPermissionType.h in Headers */ = {isa = PBXBuildFile; fileRef = 036B1E8B25351BAD008483DF /* ORKMotionActivityPermissionType.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
036B1E8E25351BAD008483DF /* ORKMotionActivityPermissionType.m in Sources */ = {isa = PBXBuildFile; fileRef = 036B1E8C25351BAD008483DF /* ORKMotionActivityPermissionType.m */; platformFilter = ios; };
03BD9EA3253E62A0008ADBE1 /* ORKBundleAsset.h in Headers */ = {isa = PBXBuildFile; fileRef = 03BD9EA1253E62A0008ADBE1 /* ORKBundleAsset.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
03BD9EA4253E62A0008ADBE1 /* ORKBundleAsset.m in Sources */ = {isa = PBXBuildFile; fileRef = 03BD9EA2253E62A0008ADBE1 /* ORKBundleAsset.m */; platformFilter = ios; };
03EDD58024CA6B1D006245E9 /* ORKNotificationPermissionType.h in Headers */ = {isa = PBXBuildFile; fileRef = 03EDD57E24CA6B1D006245E9 /* ORKNotificationPermissionType.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
03EDD58124CA6B1D006245E9 /* ORKNotificationPermissionType.m in Sources */ = {isa = PBXBuildFile; fileRef = 03EDD57F24CA6B1D006245E9 /* ORKNotificationPermissionType.m */; platformFilter = ios; };
0B0852742BD872C400149963 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 0B0852732BD872C400149963 /* PrivacyInfo.xcprivacy */; };
0B0852762BD872D800149963 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 0B0852752BD872D800149963 /* PrivacyInfo.xcprivacy */; };
0B0852782BD872EA00149963 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 0B0852772BD872EA00149963 /* PrivacyInfo.xcprivacy */; };
@@ -50,11 +50,11 @@
0BD9B6112B75992800A64EF9 /* Sentence5.wav in Resources */ = {isa = PBXBuildFile; fileRef = 7118AC6320BF6A3A00D7A6BB /* Sentence5.wav */; };
0BD9B6122B75992C00A64EF9 /* Sentence6.wav in Resources */ = {isa = PBXBuildFile; fileRef = 7118AC6220BF6A3A00D7A6BB /* Sentence6.wav */; };
0BD9B6132B75992F00A64EF9 /* Sentence7.wav in Resources */ = {isa = PBXBuildFile; fileRef = 7118AC6020BF6A3900D7A6BB /* Sentence7.wav */; };
0BE9D5272947EA4900DA0625 /* ORKConsentDocument+ORKInstructionStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 0BE9D5252947EA4900DA0625 /* ORKConsentDocument+ORKInstructionStep.m */; };
0BE9D5272947EA4900DA0625 /* ORKConsentDocument+ORKInstructionStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 0BE9D5252947EA4900DA0625 /* ORKConsentDocument+ORKInstructionStep.m */; platformFilter = ios; };
0BFD27562B8D1D3B00B540E8 /* ORKJSONSerializationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 51AF19562B583BBA00D3B399 /* ORKJSONSerializationTests.m */; };
10FF9ADB1B7BA78400ECB5B4 /* ORKOrderedTask_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 10FF9AD91B7BA78400ECB5B4 /* ORKOrderedTask_Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
12F339BF26A1F09A000665E4 /* ORKLocationPermissionType.m in Sources */ = {isa = PBXBuildFile; fileRef = 12F339BD26A1F09A000665E4 /* ORKLocationPermissionType.m */; };
12F339C026A1F09A000665E4 /* ORKLocationPermissionType.h in Headers */ = {isa = PBXBuildFile; fileRef = 12F339BE26A1F09A000665E4 /* ORKLocationPermissionType.h */; settings = {ATTRIBUTES = (Public, ); }; };
10FF9ADB1B7BA78400ECB5B4 /* ORKOrderedTask_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 10FF9AD91B7BA78400ECB5B4 /* ORKOrderedTask_Private.h */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; };
12F339BF26A1F09A000665E4 /* ORKLocationPermissionType.m in Sources */ = {isa = PBXBuildFile; fileRef = 12F339BD26A1F09A000665E4 /* ORKLocationPermissionType.m */; platformFilter = ios; };
12F339C026A1F09A000665E4 /* ORKLocationPermissionType.h in Headers */ = {isa = PBXBuildFile; fileRef = 12F339BE26A1F09A000665E4 /* ORKLocationPermissionType.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
1483DBB5220125BE004C26B6 /* ORKActiveStepTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1483DBB4220125BE004C26B6 /* ORKActiveStepTests.swift */; };
148E58BD227B36DB00EEF915 /* ORKCompletionStepViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 148E58BC227B36DB00EEF915 /* ORKCompletionStepViewControllerTests.swift */; };
148E58C2227B753F00EEF915 /* ORKContinueButtonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 148E58C1227B753F00EEF915 /* ORKContinueButtonTests.swift */; };
@@ -73,26 +73,26 @@
14F7AC8B2269035200D52F41 /* ORKStepViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14F7AC8A2269035200D52F41 /* ORKStepViewControllerTests.swift */; };
22ED1847285290250052406B /* ORKAudiometryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 22ED1845285290250052406B /* ORKAudiometryTests.m */; };
22ED1848285290250052406B /* ORKAudiometryTestData.plist in Resources */ = {isa = PBXBuildFile; fileRef = 22ED1846285290250052406B /* ORKAudiometryTestData.plist */; };
2429D5721BBB5397003A512F /* ORKRegistrationStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 2429D5701BBB5397003A512F /* ORKRegistrationStep.h */; settings = {ATTRIBUTES = (Public, ); }; };
2429D5731BBB5397003A512F /* ORKRegistrationStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 2429D5711BBB5397003A512F /* ORKRegistrationStep.m */; };
242C9E051BBDFDAC0088B7F4 /* ORKVerificationStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 242C9E031BBDFDAC0088B7F4 /* ORKVerificationStep.h */; settings = {ATTRIBUTES = (Public, ); }; };
242C9E061BBDFDAC0088B7F4 /* ORKVerificationStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 242C9E041BBDFDAC0088B7F4 /* ORKVerificationStep.m */; };
2433C9E31B9A506F0052D375 /* ORKKeychainWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 2433C9E11B9A506F0052D375 /* ORKKeychainWrapper.h */; settings = {ATTRIBUTES = (Public, ); }; };
2429D5721BBB5397003A512F /* ORKRegistrationStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 2429D5701BBB5397003A512F /* ORKRegistrationStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
2429D5731BBB5397003A512F /* ORKRegistrationStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 2429D5711BBB5397003A512F /* ORKRegistrationStep.m */; platformFilter = ios; };
242C9E051BBDFDAC0088B7F4 /* ORKVerificationStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 242C9E031BBDFDAC0088B7F4 /* ORKVerificationStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
242C9E061BBDFDAC0088B7F4 /* ORKVerificationStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 242C9E041BBDFDAC0088B7F4 /* ORKVerificationStep.m */; platformFilter = ios; };
2433C9E31B9A506F0052D375 /* ORKKeychainWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 2433C9E11B9A506F0052D375 /* ORKKeychainWrapper.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
2433C9E41B9A506F0052D375 /* ORKKeychainWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 2433C9E21B9A506F0052D375 /* ORKKeychainWrapper.m */; };
244EFAD21BCEFD83001850D9 /* ORKAnswerFormat_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 244EFAD11BCEFD83001850D9 /* ORKAnswerFormat_Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
248604061B4C98760010C8A0 /* ORKAnswerFormatTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 248604051B4C98760010C8A0 /* ORKAnswerFormatTests.m */; };
2489F7B11D65214D008DEF20 /* ORKVideoCaptureStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 2489F7A91D65214D008DEF20 /* ORKVideoCaptureStep.h */; settings = {ATTRIBUTES = (Public, ); }; };
2489F7B21D65214D008DEF20 /* ORKVideoCaptureStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 2489F7AA1D65214D008DEF20 /* ORKVideoCaptureStep.m */; };
24A4DA141B8D1115009C797A /* ORKPasscodeStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 24A4DA121B8D1115009C797A /* ORKPasscodeStep.h */; settings = {ATTRIBUTES = (Public, ); }; };
24A4DA151B8D1115009C797A /* ORKPasscodeStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 24A4DA131B8D1115009C797A /* ORKPasscodeStep.m */; };
24BC5CEE1BC345D900846B43 /* ORKLoginStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 24BC5CEC1BC345D900846B43 /* ORKLoginStep.h */; settings = {ATTRIBUTES = (Public, ); }; };
24BC5CEF1BC345D900846B43 /* ORKLoginStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 24BC5CED1BC345D900846B43 /* ORKLoginStep.m */; };
24C296751BD052F800B42EF1 /* ORKVerificationStep_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 24C296741BD052F800B42EF1 /* ORKVerificationStep_Internal.h */; };
24C296771BD055B800B42EF1 /* ORKLoginStep_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 24C296761BD055B800B42EF1 /* ORKLoginStep_Internal.h */; };
2489F7B11D65214D008DEF20 /* ORKVideoCaptureStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 2489F7A91D65214D008DEF20 /* ORKVideoCaptureStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
2489F7B21D65214D008DEF20 /* ORKVideoCaptureStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 2489F7AA1D65214D008DEF20 /* ORKVideoCaptureStep.m */; platformFilter = ios; };
24A4DA141B8D1115009C797A /* ORKPasscodeStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 24A4DA121B8D1115009C797A /* ORKPasscodeStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
24A4DA151B8D1115009C797A /* ORKPasscodeStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 24A4DA131B8D1115009C797A /* ORKPasscodeStep.m */; platformFilter = ios; };
24BC5CEE1BC345D900846B43 /* ORKLoginStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 24BC5CEC1BC345D900846B43 /* ORKLoginStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
24BC5CEF1BC345D900846B43 /* ORKLoginStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 24BC5CED1BC345D900846B43 /* ORKLoginStep.m */; platformFilter = ios; };
24C296751BD052F800B42EF1 /* ORKVerificationStep_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 24C296741BD052F800B42EF1 /* ORKVerificationStep_Internal.h */; platformFilter = ios; };
24C296771BD055B800B42EF1 /* ORKLoginStep_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 24C296761BD055B800B42EF1 /* ORKLoginStep_Internal.h */; platformFilter = ios; };
2EBFE11D1AE1B32D00CB8254 /* ORKUIViewAccessibilityTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2EBFE11C1AE1B32D00CB8254 /* ORKUIViewAccessibilityTests.m */; };
2EBFE1201AE1B74100CB8254 /* ORKVoiceEngineTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2EBFE11F1AE1B74100CB8254 /* ORKVoiceEngineTests.m */; };
511987C3246330CA004FC2C7 /* ORKRequestPermissionsStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 511987C1246330CA004FC2C7 /* ORKRequestPermissionsStep.h */; settings = {ATTRIBUTES = (Public, ); }; };
511987C4246330CA004FC2C7 /* ORKRequestPermissionsStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 511987C2246330CA004FC2C7 /* ORKRequestPermissionsStep.m */; };
511987C3246330CA004FC2C7 /* ORKRequestPermissionsStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 511987C1246330CA004FC2C7 /* ORKRequestPermissionsStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
511987C4246330CA004FC2C7 /* ORKRequestPermissionsStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 511987C2246330CA004FC2C7 /* ORKRequestPermissionsStep.m */; platformFilter = ios; };
511BB024298DCCC200936EC0 /* ORKSpeechRecognitionStepViewController_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 511BB022298DCCC200936EC0 /* ORKSpeechRecognitionStepViewController_Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
511E8D622995C20E00A384A5 /* ORKEnvironmentSPLMeterStepViewController_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 511E8D602995C20E00A384A5 /* ORKEnvironmentSPLMeterStepViewController_Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
512741272B1557220045A449 /* ResearchKit.docc in Sources */ = {isa = PBXBuildFile; fileRef = 512741262B1557220045A449 /* ResearchKit.docc */; };
@@ -180,20 +180,20 @@
5192BEED2AE043D3006E43FB /* ORKTimedWalkStepViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BEE92AE043D3006E43FB /* ORKTimedWalkStepViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
5192BEEE2AE043D3006E43FB /* ORKTimedWalkContentView.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BEEA2AE043D3006E43FB /* ORKTimedWalkContentView.h */; };
5192BEEF2AE043D3006E43FB /* ORKTimedWalkStepViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5192BEEB2AE043D3006E43FB /* ORKTimedWalkStepViewController.m */; };
5192BF502AE09673006E43FB /* ORKPredicateFormItemVisibilityRule.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BF4D2AE09672006E43FB /* ORKPredicateFormItemVisibilityRule.h */; settings = {ATTRIBUTES = (Public, ); }; };
5192BF512AE09673006E43FB /* ORKPredicateFormItemVisibilityRule.m in Sources */ = {isa = PBXBuildFile; fileRef = 5192BF4E2AE09672006E43FB /* ORKPredicateFormItemVisibilityRule.m */; };
5192BF522AE09673006E43FB /* ORKPredicateFormItemVisibilityRule_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BF4F2AE09672006E43FB /* ORKPredicateFormItemVisibilityRule_Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
5192BF502AE09673006E43FB /* ORKPredicateFormItemVisibilityRule.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BF4D2AE09672006E43FB /* ORKPredicateFormItemVisibilityRule.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
5192BF512AE09673006E43FB /* ORKPredicateFormItemVisibilityRule.m in Sources */ = {isa = PBXBuildFile; fileRef = 5192BF4E2AE09672006E43FB /* ORKPredicateFormItemVisibilityRule.m */; platformFilter = ios; };
5192BF522AE09673006E43FB /* ORKPredicateFormItemVisibilityRule_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BF4F2AE09672006E43FB /* ORKPredicateFormItemVisibilityRule_Private.h */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; };
5192BF592AE09794006E43FB /* ORKFormItemVisibilityRule.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BF572AE09793006E43FB /* ORKFormItemVisibilityRule.h */; settings = {ATTRIBUTES = (Public, ); }; };
5192BF5A2AE09794006E43FB /* ORKFormItemVisibilityRule.m in Sources */ = {isa = PBXBuildFile; fileRef = 5192BF582AE09794006E43FB /* ORKFormItemVisibilityRule.m */; };
5192BF5D2AE19036006E43FB /* frequency_dBSPL_AIRPODSPROV2.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5192BF5C2AE19036006E43FB /* frequency_dBSPL_AIRPODSPROV2.plist */; };
5192BF7D2AE1A87D006E43FB /* retspl_AIRPODSPROV2.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5192BF7C2AE1A87D006E43FB /* retspl_AIRPODSPROV2.plist */; };
5192BF7F2AE1A9BA006E43FB /* volume_curve_AIRPODSPROV2.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5192BF7E2AE1A9BA006E43FB /* volume_curve_AIRPODSPROV2.plist */; };
5192BF842AE1BA47006E43FB /* ORKFrontFacingCameraStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BF802AE1BA47006E43FB /* ORKFrontFacingCameraStep.h */; settings = {ATTRIBUTES = (Public, ); }; };
5192BF852AE1BA47006E43FB /* ORKFrontFacingCameraStepResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 5192BF812AE1BA47006E43FB /* ORKFrontFacingCameraStepResult.m */; };
5192BF862AE1BA47006E43FB /* ORKFrontFacingCameraStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 5192BF822AE1BA47006E43FB /* ORKFrontFacingCameraStep.m */; };
5192BF872AE1BA47006E43FB /* ORKFrontFacingCameraStepResult.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BF832AE1BA47006E43FB /* ORKFrontFacingCameraStepResult.h */; settings = {ATTRIBUTES = (Public, ); }; };
5192BF8A2AE1BBB0006E43FB /* ORKSecondaryTaskStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 5192BF882AE1BBB0006E43FB /* ORKSecondaryTaskStep.m */; };
5192BF8B2AE1BBB0006E43FB /* ORKSecondaryTaskStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BF892AE1BBB0006E43FB /* ORKSecondaryTaskStep.h */; settings = {ATTRIBUTES = (Public, ); }; };
5192BF842AE1BA47006E43FB /* ORKFrontFacingCameraStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BF802AE1BA47006E43FB /* ORKFrontFacingCameraStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
5192BF852AE1BA47006E43FB /* ORKFrontFacingCameraStepResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 5192BF812AE1BA47006E43FB /* ORKFrontFacingCameraStepResult.m */; platformFilter = ios; };
5192BF862AE1BA47006E43FB /* ORKFrontFacingCameraStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 5192BF822AE1BA47006E43FB /* ORKFrontFacingCameraStep.m */; platformFilter = ios; };
5192BF872AE1BA47006E43FB /* ORKFrontFacingCameraStepResult.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BF832AE1BA47006E43FB /* ORKFrontFacingCameraStepResult.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
5192BF8A2AE1BBB0006E43FB /* ORKSecondaryTaskStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 5192BF882AE1BBB0006E43FB /* ORKSecondaryTaskStep.m */; platformFilter = ios; };
5192BF8B2AE1BBB0006E43FB /* ORKSecondaryTaskStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BF892AE1BBB0006E43FB /* ORKSecondaryTaskStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
5192BF8F2AE1C051006E43FB /* ORKAgePicker.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BF8D2AE1C051006E43FB /* ORKAgePicker.h */; };
5192BF902AE1C051006E43FB /* ORKAgePicker.m in Sources */ = {isa = PBXBuildFile; fileRef = 5192BF8E2AE1C051006E43FB /* ORKAgePicker.m */; };
5192BF932AE1C2F8006E43FB /* ORKChoiceViewCell+ORKColorChoice.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BF912AE1C2F7006E43FB /* ORKChoiceViewCell+ORKColorChoice.h */; };
@@ -204,19 +204,19 @@
5192BF9A2AE1D8A4006E43FB /* ORKChoiceViewCell+ORKTextChoice.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B8444642A79C25000292DEA /* ORKChoiceViewCell+ORKTextChoice.m */; };
5192BF9D2AE1DE62006E43FB /* ORKColorChoiceCellGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 5192BF9B2AE1DE62006E43FB /* ORKColorChoiceCellGroup.m */; };
5192BF9E2AE1DE62006E43FB /* ORKColorChoiceCellGroup.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BF9C2AE1DE62006E43FB /* ORKColorChoiceCellGroup.h */; settings = {ATTRIBUTES = (Public, ); }; };
5192BFA12AEAD7B5006E43FB /* Artwork.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 861610BF1A8D8EDD00245F7A /* Artwork.xcassets */; };
519CE8222C6582BE003BB584 /* ORKHealthCondition.m in Sources */ = {isa = PBXBuildFile; fileRef = 519CE8162C6582BD003BB584 /* ORKHealthCondition.m */; };
519CE8232C6582BE003BB584 /* ORKFamilyHistoryStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 519CE8172C6582BD003BB584 /* ORKFamilyHistoryStep.m */; };
519CE8242C6582BE003BB584 /* ORKHealthCondition.h in Headers */ = {isa = PBXBuildFile; fileRef = 519CE8182C6582BD003BB584 /* ORKHealthCondition.h */; settings = {ATTRIBUTES = (Public, ); }; };
519CE8252C6582BE003BB584 /* ORKFamilyHistoryResult.h in Headers */ = {isa = PBXBuildFile; fileRef = 519CE8192C6582BD003BB584 /* ORKFamilyHistoryResult.h */; settings = {ATTRIBUTES = (Public, ); }; };
519CE8262C6582BE003BB584 /* ORKRelatedPerson.m in Sources */ = {isa = PBXBuildFile; fileRef = 519CE81A2C6582BD003BB584 /* ORKRelatedPerson.m */; };
519CE8272C6582BE003BB584 /* ORKRelativeGroup.h in Headers */ = {isa = PBXBuildFile; fileRef = 519CE81B2C6582BD003BB584 /* ORKRelativeGroup.h */; settings = {ATTRIBUTES = (Public, ); }; };
519CE8282C6582BE003BB584 /* ORKRelativeGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 519CE81C2C6582BD003BB584 /* ORKRelativeGroup.m */; };
519CE8292C6582BE003BB584 /* ORKConditionStepConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = 519CE81D2C6582BE003BB584 /* ORKConditionStepConfiguration.m */; };
519CE82A2C6582BE003BB584 /* ORKConditionStepConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = 519CE81E2C6582BE003BB584 /* ORKConditionStepConfiguration.h */; settings = {ATTRIBUTES = (Public, ); }; };
519CE82B2C6582BE003BB584 /* ORKFamilyHistoryStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 519CE81F2C6582BE003BB584 /* ORKFamilyHistoryStep.h */; settings = {ATTRIBUTES = (Public, ); }; };
519CE82C2C6582BE003BB584 /* ORKRelatedPerson.h in Headers */ = {isa = PBXBuildFile; fileRef = 519CE8202C6582BE003BB584 /* ORKRelatedPerson.h */; settings = {ATTRIBUTES = (Public, ); }; };
519CE82D2C6582BE003BB584 /* ORKFamilyHistoryResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 519CE8212C6582BE003BB584 /* ORKFamilyHistoryResult.m */; };
5192BFA12AEAD7B5006E43FB /* Artwork.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 861610BF1A8D8EDD00245F7A /* Artwork.xcassets */; platformFilter = ios; };
519CE8222C6582BE003BB584 /* ORKHealthCondition.m in Sources */ = {isa = PBXBuildFile; fileRef = 519CE8162C6582BD003BB584 /* ORKHealthCondition.m */; platformFilter = ios; };
519CE8232C6582BE003BB584 /* ORKFamilyHistoryStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 519CE8172C6582BD003BB584 /* ORKFamilyHistoryStep.m */; platformFilter = ios; };
519CE8242C6582BE003BB584 /* ORKHealthCondition.h in Headers */ = {isa = PBXBuildFile; fileRef = 519CE8182C6582BD003BB584 /* ORKHealthCondition.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
519CE8252C6582BE003BB584 /* ORKFamilyHistoryResult.h in Headers */ = {isa = PBXBuildFile; fileRef = 519CE8192C6582BD003BB584 /* ORKFamilyHistoryResult.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
519CE8262C6582BE003BB584 /* ORKRelatedPerson.m in Sources */ = {isa = PBXBuildFile; fileRef = 519CE81A2C6582BD003BB584 /* ORKRelatedPerson.m */; platformFilter = ios; };
519CE8272C6582BE003BB584 /* ORKRelativeGroup.h in Headers */ = {isa = PBXBuildFile; fileRef = 519CE81B2C6582BD003BB584 /* ORKRelativeGroup.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
519CE8282C6582BE003BB584 /* ORKRelativeGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 519CE81C2C6582BD003BB584 /* ORKRelativeGroup.m */; platformFilter = ios; };
519CE8292C6582BE003BB584 /* ORKConditionStepConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = 519CE81D2C6582BE003BB584 /* ORKConditionStepConfiguration.m */; platformFilter = ios; };
519CE82A2C6582BE003BB584 /* ORKConditionStepConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = 519CE81E2C6582BE003BB584 /* ORKConditionStepConfiguration.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
519CE82B2C6582BE003BB584 /* ORKFamilyHistoryStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 519CE81F2C6582BE003BB584 /* ORKFamilyHistoryStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
519CE82C2C6582BE003BB584 /* ORKRelatedPerson.h in Headers */ = {isa = PBXBuildFile; fileRef = 519CE8202C6582BE003BB584 /* ORKRelatedPerson.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
519CE82D2C6582BE003BB584 /* ORKFamilyHistoryResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 519CE8212C6582BE003BB584 /* ORKFamilyHistoryResult.m */; platformFilter = ios; };
519CE8352C658617003BB584 /* ORKFamilyHistoryStepViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 519CE8302C658617003BB584 /* ORKFamilyHistoryStepViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
519CE8382C658617003BB584 /* ORKFamilyHistoryStepViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 519CE8332C658617003BB584 /* ORKFamilyHistoryStepViewController.m */; };
519CE8412C658654003BB584 /* ORKFamilyHistoryRelatedPersonCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 519CE83B2C658653003BB584 /* ORKFamilyHistoryRelatedPersonCell.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -227,13 +227,12 @@
519CE8462C658654003BB584 /* ORKFamilyHistoryTableHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 519CE8402C658654003BB584 /* ORKFamilyHistoryTableHeaderView.m */; };
519CE85C2C6AD858003BB584 /* ORKFamilyHistoryStepViewController_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 519CE85B2C6AD858003BB584 /* ORKFamilyHistoryStepViewController_Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
519CE85F2C6BC1DB003BB584 /* ORKFamilyHistoryResultTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519CE85E2C6BC1DB003BB584 /* ORKFamilyHistoryResultTests.swift */; };
51A11F172BD08D5E0060C07E /* HKSample+ORKJSONDictionary.h in Headers */ = {isa = PBXBuildFile; fileRef = 51A11F132BD08D5D0060C07E /* HKSample+ORKJSONDictionary.h */; settings = {ATTRIBUTES = (Public, ); }; };
51A11F182BD08D5E0060C07E /* CMMotionActivity+ORKJSONDictionary.m in Sources */ = {isa = PBXBuildFile; fileRef = 51A11F142BD08D5D0060C07E /* CMMotionActivity+ORKJSONDictionary.m */; };
51A11F192BD08D5E0060C07E /* HKSample+ORKJSONDictionary.m in Sources */ = {isa = PBXBuildFile; fileRef = 51A11F152BD08D5D0060C07E /* HKSample+ORKJSONDictionary.m */; };
51A11F1A2BD08D5E0060C07E /* CMMotionActivity+ORKJSONDictionary.h in Headers */ = {isa = PBXBuildFile; fileRef = 51A11F162BD08D5E0060C07E /* CMMotionActivity+ORKJSONDictionary.h */; settings = {ATTRIBUTES = (Public, ); }; };
51A11F172BD08D5E0060C07E /* HKSample+ORKJSONDictionary.h in Headers */ = {isa = PBXBuildFile; fileRef = 51A11F132BD08D5D0060C07E /* HKSample+ORKJSONDictionary.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
51A11F182BD08D5E0060C07E /* CMMotionActivity+ORKJSONDictionary.m in Sources */ = {isa = PBXBuildFile; fileRef = 51A11F142BD08D5D0060C07E /* CMMotionActivity+ORKJSONDictionary.m */; platformFilter = ios; };
51A11F192BD08D5E0060C07E /* HKSample+ORKJSONDictionary.m in Sources */ = {isa = PBXBuildFile; fileRef = 51A11F152BD08D5D0060C07E /* HKSample+ORKJSONDictionary.m */; platformFilter = ios; };
51A11F1A2BD08D5E0060C07E /* CMMotionActivity+ORKJSONDictionary.h in Headers */ = {isa = PBXBuildFile; fileRef = 51A11F162BD08D5E0060C07E /* CMMotionActivity+ORKJSONDictionary.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
51A11F222BD152660060C07E /* ORKActiveStepCustomView.h in Headers */ = {isa = PBXBuildFile; fileRef = 51A11F202BD152660060C07E /* ORKActiveStepCustomView.h */; settings = {ATTRIBUTES = (Public, ); }; };
51A11F232BD152660060C07E /* ORKActiveStepCustomView.m in Sources */ = {isa = PBXBuildFile; fileRef = 51A11F212BD152660060C07E /* ORKActiveStepCustomView.m */; };
51A11F242BD1548C0060C07E /* UIImageView+ResearchKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B9CC5662A68C02C00080E29 /* UIImageView+ResearchKit.m */; };
51AEAAB22B744AC200F4D107 /* ORKQuestionStepViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 51AEAAA82B743FA500F4D107 /* ORKQuestionStepViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
51AEAAB32B744ACC00F4D107 /* ORKQuestionStepViewController_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 51AEAAA72B743FA500F4D107 /* ORKQuestionStepViewController_Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
51AEAAB42B744B5700F4D107 /* ORKQuestionStepViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 51AEAAA62B743FA500F4D107 /* ORKQuestionStepViewController.m */; };
@@ -242,15 +241,15 @@
51AF19582B583BBA00D3B399 /* ORKDataCollectionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 51AF19532B583BB900D3B399 /* ORKDataCollectionTests.m */; };
51AF19592B583BBA00D3B399 /* samples.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 51AF19542B583BB900D3B399 /* samples.bundle */; };
51AF19662B583D0F00D3B399 /* ORKESerialization.m in Sources */ = {isa = PBXBuildFile; fileRef = 51AF19552B583BB900D3B399 /* ORKESerialization.m */; };
51AF1B152B67F30500D3B399 /* ORKSignatureFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = 51AF1B132B67F30500D3B399 /* ORKSignatureFormatter.h */; settings = {ATTRIBUTES = (Public, ); }; };
51AF1B162B67F30500D3B399 /* ORKSignatureFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 51AF1B142B67F30500D3B399 /* ORKSignatureFormatter.m */; };
51AF1B202B683C3400D3B399 /* ORKWebViewStepResult_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 51AF1B1F2B683C3400D3B399 /* ORKWebViewStepResult_Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
51AF1B152B67F30500D3B399 /* ORKSignatureFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = 51AF1B132B67F30500D3B399 /* ORKSignatureFormatter.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
51AF1B162B67F30500D3B399 /* ORKSignatureFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 51AF1B142B67F30500D3B399 /* ORKSignatureFormatter.m */; platformFilter = ios; };
51AF1B202B683C3400D3B399 /* ORKWebViewStepResult_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 51AF1B1F2B683C3400D3B399 /* ORKWebViewStepResult_Private.h */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; };
51AF1B232B69803A00D3B399 /* Window.wav in Resources */ = {isa = PBXBuildFile; fileRef = 51AF1B212B69803A00D3B399 /* Window.wav */; };
51AF1B242B69803A00D3B399 /* Noise.wav in Resources */ = {isa = PBXBuildFile; fileRef = 51AF1B222B69803A00D3B399 /* Noise.wav */; };
51B76DFB2CB5B5A30061698A /* ResearchKitActiveTask.docc in Sources */ = {isa = PBXBuildFile; fileRef = 51B76DFA2CB5B5A30061698A /* ResearchKitActiveTask.docc */; };
51B76DFD2CB5C73C0061698A /* ResearchKitUI.docc in Sources */ = {isa = PBXBuildFile; fileRef = 51B76DFC2CB5C73C0061698A /* ResearchKitUI.docc */; };
51B94DBD2B311E810039B0E7 /* CLLocationManager+ResearchKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 51B94DBC2B311E810039B0E7 /* CLLocationManager+ResearchKit.h */; settings = {ATTRIBUTES = (Private, ); }; };
51B94DC72B3254FE0039B0E7 /* CLLocationManager+ResearchKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 51B94DC62B3254FD0039B0E7 /* CLLocationManager+ResearchKit.m */; };
51B94DBD2B311E810039B0E7 /* CLLocationManager+ResearchKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 51B94DBC2B311E810039B0E7 /* CLLocationManager+ResearchKit.h */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; };
51B94DC72B3254FE0039B0E7 /* CLLocationManager+ResearchKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 51B94DC62B3254FD0039B0E7 /* CLLocationManager+ResearchKit.m */; platformFilter = ios; };
51BD8FC929D60FB60001D54E /* frequency_dBSPL_AIRPODSMAX.plist in Resources */ = {isa = PBXBuildFile; fileRef = E293655D25757CC700092A7C /* frequency_dBSPL_AIRPODSMAX.plist */; };
51BD8FCA29D60FB60001D54E /* frequency_dBSPL_EARPODS.plist in Resources */ = {isa = PBXBuildFile; fileRef = 713D4B1B20FE5464002BE28D /* frequency_dBSPL_EARPODS.plist */; };
51BD8FCB29D60FB60001D54E /* frequency_dBSPL_AIRPODS.plist in Resources */ = {isa = PBXBuildFile; fileRef = 71B7B4D820AA91D400C5768A /* frequency_dBSPL_AIRPODS.plist */; };
@@ -269,12 +268,12 @@
51CB80D62AFEBE7E00A1F410 /* ORKFormStepViewControllerConditionalFormItemsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51CB80D52AFEBE7E00A1F410 /* ORKFormStepViewControllerConditionalFormItemsTests.swift */; };
51CB80D82AFEBEC600A1F410 /* ORKPredicateFormItemVisibilityRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51CB80D72AFEBEC600A1F410 /* ORKPredicateFormItemVisibilityRuleTests.swift */; };
51CB80DA2AFEBF3800A1F410 /* ORKFormItemVisibilityRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51CB80D92AFEBF3800A1F410 /* ORKFormItemVisibilityRuleTests.swift */; };
51E03D6324919711008F8406 /* ORKPermissionType.h in Headers */ = {isa = PBXBuildFile; fileRef = 51E03D6124919711008F8406 /* ORKPermissionType.h */; settings = {ATTRIBUTES = (Public, ); }; };
51E03D6424919711008F8406 /* ORKPermissionType.m in Sources */ = {isa = PBXBuildFile; fileRef = 51E03D6224919711008F8406 /* ORKPermissionType.m */; };
51E03D682491A601008F8406 /* ORKHealthKitPermissionType.h in Headers */ = {isa = PBXBuildFile; fileRef = 51E03D662491A601008F8406 /* ORKHealthKitPermissionType.h */; settings = {ATTRIBUTES = (Public, ); }; };
51E03D692491A601008F8406 /* ORKHealthKitPermissionType.m in Sources */ = {isa = PBXBuildFile; fileRef = 51E03D672491A601008F8406 /* ORKHealthKitPermissionType.m */; };
51EB9A532B8408D50064A515 /* ORKInstructionStepHTMLFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = 51EB9A512B8408D50064A515 /* ORKInstructionStepHTMLFormatter.h */; };
51EB9A542B8408D50064A515 /* ORKInstructionStepHTMLFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 51EB9A522B8408D50064A515 /* ORKInstructionStepHTMLFormatter.m */; };
51E03D6324919711008F8406 /* ORKPermissionType.h in Headers */ = {isa = PBXBuildFile; fileRef = 51E03D6124919711008F8406 /* ORKPermissionType.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
51E03D6424919711008F8406 /* ORKPermissionType.m in Sources */ = {isa = PBXBuildFile; fileRef = 51E03D6224919711008F8406 /* ORKPermissionType.m */; platformFilter = ios; };
51E03D682491A601008F8406 /* ORKHealthKitPermissionType.h in Headers */ = {isa = PBXBuildFile; fileRef = 51E03D662491A601008F8406 /* ORKHealthKitPermissionType.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
51E03D692491A601008F8406 /* ORKHealthKitPermissionType.m in Sources */ = {isa = PBXBuildFile; fileRef = 51E03D672491A601008F8406 /* ORKHealthKitPermissionType.m */; platformFilter = ios; };
51EB9A532B8408D50064A515 /* ORKInstructionStepHTMLFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = 51EB9A512B8408D50064A515 /* ORKInstructionStepHTMLFormatter.h */; platformFilter = ios; };
51EB9A542B8408D50064A515 /* ORKInstructionStepHTMLFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 51EB9A522B8408D50064A515 /* ORKInstructionStepHTMLFormatter.m */; platformFilter = ios; };
51EB9A5E2B8D3BA70064A515 /* ORKInstructionStepHTMLFormatterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 51EB9A5D2B8D3BA70064A515 /* ORKInstructionStepHTMLFormatterTests.m */; };
51F716C1297E288A00D8ACF7 /* ORKNormalizedReactionTimeStimulusView.m in Sources */ = {isa = PBXBuildFile; fileRef = 51F716B7297E288900D8ACF7 /* ORKNormalizedReactionTimeStimulusView.m */; };
51F716C2297E288A00D8ACF7 /* ORKNormalizedReactionTimeStimulusView.h in Headers */ = {isa = PBXBuildFile; fileRef = 51F716B8297E288900D8ACF7 /* ORKNormalizedReactionTimeStimulusView.h */; };
@@ -286,7 +285,7 @@
51F716C8297E288A00D8ACF7 /* ORKNormalizedReactionTimeStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 51F716BE297E288900D8ACF7 /* ORKNormalizedReactionTimeStep.m */; };
51F716C9297E288A00D8ACF7 /* ORKNormalizedReactionTimeViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 51F716BF297E288A00D8ACF7 /* ORKNormalizedReactionTimeViewController.h */; settings = {ATTRIBUTES = (Private, ); }; };
51F716CA297E288A00D8ACF7 /* ORKNormalizedReactionTimeContentView.h in Headers */ = {isa = PBXBuildFile; fileRef = 51F716C0297E288A00D8ACF7 /* ORKNormalizedReactionTimeContentView.h */; };
51F716CB297E2A1100D8ACF7 /* ORKConsentDocument+ORKInstructionStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 0BE9D5242947EA4900DA0625 /* ORKConsentDocument+ORKInstructionStep.h */; settings = {ATTRIBUTES = (Public, ); }; };
51F716CB297E2A1100D8ACF7 /* ORKConsentDocument+ORKInstructionStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 0BE9D5242947EA4900DA0625 /* ORKConsentDocument+ORKInstructionStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
51F716D1297E2CC400D8ACF7 /* ORKConsentLearnMoreViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 51F716CD297E2CB600D8ACF7 /* ORKConsentLearnMoreViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
51F716D3297E2FB600D8ACF7 /* ORKConsentLearnMoreViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 51F716CE297E2CB600D8ACF7 /* ORKConsentLearnMoreViewController.m */; };
51F716EB2981B49000D8ACF7 /* ORKSpeechInNoiseStepViewController_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 51F716E82981AF1200D8ACF7 /* ORKSpeechInNoiseStepViewController_Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
@@ -295,7 +294,7 @@
5D04884C25EF4CC30006C68B /* ORKQuestionStep_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D04884B25EF4CC30006C68B /* ORKQuestionStep_Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
5D04885725F19A7A0006C68B /* ORKDevice.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D04885525F19A7A0006C68B /* ORKDevice.h */; settings = {ATTRIBUTES = (Private, ); }; };
5D04885825F19A7A0006C68B /* ORKDevice.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D04885625F19A7A0006C68B /* ORKDevice.m */; };
5D04885C25F1B3AB0006C68B /* ORKDevice_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D04885B25F1B3AB0006C68B /* ORKDevice_Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
5D04885C25F1B3AB0006C68B /* ORKDevice_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D04885B25F1B3AB0006C68B /* ORKDevice_Private.h */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; };
5D43C5D424255675006F4084 /* ORKBodyItem_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D43C5D324255675006F4084 /* ORKBodyItem_Internal.h */; settings = {ATTRIBUTES = (Private, ); }; };
5DABE5AE24DA173F00570C57 /* ResearchKit_Prefix.pch in Headers */ = {isa = PBXBuildFile; fileRef = 5DABE5AD24DA16E600570C57 /* ResearchKit_Prefix.pch */; };
5E6AB7DF2BC86900009ED0D5 /* ORKTaskViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E6AB7DE2BC86900009ED0D5 /* ORKTaskViewControllerTests.swift */; };
@@ -304,26 +303,26 @@
5EB91CC82BCE2EFD00BBF23E /* splMeter_sensitivity_offset.plist in Resources */ = {isa = PBXBuildFile; fileRef = 71F3B27F21001DEC00FB1C41 /* splMeter_sensitivity_offset.plist */; };
5EB91CC92BCE2F1D00BBF23E /* SentencesList.txt in Resources */ = {isa = PBXBuildFile; fileRef = BA22F76C20C4F884006E6E11 /* SentencesList.txt */; };
622774402C5CF7DC00F2E741 /* retspl_dBFS_AIRPODSPROV2.plist in Resources */ = {isa = PBXBuildFile; fileRef = 6227743F2C5CF7DC00F2E741 /* retspl_dBFS_AIRPODSPROV2.plist */; };
714080DB235FD14700281E04 /* ResearchKit.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 714080D9235FD14700281E04 /* ResearchKit.stringsdict */; };
714080DB235FD14700281E04 /* ResearchKit.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 714080D9235FD14700281E04 /* ResearchKit.stringsdict */; platformFilter = ios; };
714151D0225C4A23002CA33B /* ORKPasscodeViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14A92C6922444F93007547F2 /* ORKPasscodeViewControllerTests.swift */; };
7141EA2222EFBC0C00650145 /* ORKLoggingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7141EA2122EFBC0C00650145 /* ORKLoggingTests.m */; };
7167D028231B1EAA00AAB4DD /* ORKFormStep_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 7167D027231B1EAA00AAB4DD /* ORKFormStep_Internal.h */; };
8419D66E1FB73CC80088D7E5 /* ORKWebViewStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 8419D66C1FB73CC80088D7E5 /* ORKWebViewStep.h */; settings = {ATTRIBUTES = (Public, ); }; };
8419D66F1FB73CC80088D7E5 /* ORKWebViewStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 8419D66D1FB73CC80088D7E5 /* ORKWebViewStep.m */; };
7167D028231B1EAA00AAB4DD /* ORKFormStep_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 7167D027231B1EAA00AAB4DD /* ORKFormStep_Internal.h */; platformFilter = ios; };
8419D66E1FB73CC80088D7E5 /* ORKWebViewStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 8419D66C1FB73CC80088D7E5 /* ORKWebViewStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
8419D66F1FB73CC80088D7E5 /* ORKWebViewStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 8419D66D1FB73CC80088D7E5 /* ORKWebViewStep.m */; platformFilter = ios; };
861D11AD1AA7951F003C98A7 /* ORKChoiceAnswerFormatHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = 861D11AB1AA7951F003C98A7 /* ORKChoiceAnswerFormatHelper.h */; settings = {ATTRIBUTES = (Private, ); }; };
861D11AE1AA7951F003C98A7 /* ORKChoiceAnswerFormatHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 861D11AC1AA7951F003C98A7 /* ORKChoiceAnswerFormatHelper.m */; };
866DA51F1D63D04700C9AF3F /* ORKCollector_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 866DA5131D63D04700C9AF3F /* ORKCollector_Internal.h */; };
866DA5201D63D04700C9AF3F /* ORKCollector.h in Headers */ = {isa = PBXBuildFile; fileRef = 866DA5141D63D04700C9AF3F /* ORKCollector.h */; settings = {ATTRIBUTES = (Public, ); }; };
866DA5211D63D04700C9AF3F /* ORKCollector.m in Sources */ = {isa = PBXBuildFile; fileRef = 866DA5151D63D04700C9AF3F /* ORKCollector.m */; };
866DA5221D63D04700C9AF3F /* ORKDataCollectionManager_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 866DA5161D63D04700C9AF3F /* ORKDataCollectionManager_Internal.h */; };
866DA5231D63D04700C9AF3F /* ORKDataCollectionManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 866DA5171D63D04700C9AF3F /* ORKDataCollectionManager.h */; settings = {ATTRIBUTES = (Public, ); }; };
866DA5241D63D04700C9AF3F /* ORKDataCollectionManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 866DA5181D63D04700C9AF3F /* ORKDataCollectionManager.m */; };
866DA5251D63D04700C9AF3F /* ORKHealthSampleQueryOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 866DA5191D63D04700C9AF3F /* ORKHealthSampleQueryOperation.h */; };
866DA5261D63D04700C9AF3F /* ORKHealthSampleQueryOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 866DA51A1D63D04700C9AF3F /* ORKHealthSampleQueryOperation.m */; };
866DA5271D63D04700C9AF3F /* ORKMotionActivityQueryOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 866DA51B1D63D04700C9AF3F /* ORKMotionActivityQueryOperation.h */; };
866DA5281D63D04700C9AF3F /* ORKMotionActivityQueryOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 866DA51C1D63D04700C9AF3F /* ORKMotionActivityQueryOperation.m */; };
866DA5291D63D04700C9AF3F /* ORKOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 866DA51D1D63D04700C9AF3F /* ORKOperation.h */; };
866DA52A1D63D04700C9AF3F /* ORKOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 866DA51E1D63D04700C9AF3F /* ORKOperation.m */; };
866DA51F1D63D04700C9AF3F /* ORKCollector_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 866DA5131D63D04700C9AF3F /* ORKCollector_Internal.h */; platformFilter = ios; };
866DA5201D63D04700C9AF3F /* ORKCollector.h in Headers */ = {isa = PBXBuildFile; fileRef = 866DA5141D63D04700C9AF3F /* ORKCollector.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
866DA5211D63D04700C9AF3F /* ORKCollector.m in Sources */ = {isa = PBXBuildFile; fileRef = 866DA5151D63D04700C9AF3F /* ORKCollector.m */; platformFilter = ios; };
866DA5221D63D04700C9AF3F /* ORKDataCollectionManager_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 866DA5161D63D04700C9AF3F /* ORKDataCollectionManager_Internal.h */; platformFilter = ios; };
866DA5231D63D04700C9AF3F /* ORKDataCollectionManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 866DA5171D63D04700C9AF3F /* ORKDataCollectionManager.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
866DA5241D63D04700C9AF3F /* ORKDataCollectionManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 866DA5181D63D04700C9AF3F /* ORKDataCollectionManager.m */; platformFilter = ios; };
866DA5251D63D04700C9AF3F /* ORKHealthSampleQueryOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 866DA5191D63D04700C9AF3F /* ORKHealthSampleQueryOperation.h */; platformFilter = ios; };
866DA5261D63D04700C9AF3F /* ORKHealthSampleQueryOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 866DA51A1D63D04700C9AF3F /* ORKHealthSampleQueryOperation.m */; platformFilter = ios; };
866DA5271D63D04700C9AF3F /* ORKMotionActivityQueryOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 866DA51B1D63D04700C9AF3F /* ORKMotionActivityQueryOperation.h */; platformFilter = ios; };
866DA5281D63D04700C9AF3F /* ORKMotionActivityQueryOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 866DA51C1D63D04700C9AF3F /* ORKMotionActivityQueryOperation.m */; platformFilter = ios; };
866DA5291D63D04700C9AF3F /* ORKOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 866DA51D1D63D04700C9AF3F /* ORKOperation.h */; platformFilter = ios; };
866DA52A1D63D04700C9AF3F /* ORKOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 866DA51E1D63D04700C9AF3F /* ORKOperation.m */; platformFilter = ios; };
86C40CC01A8D7C5C00081FAC /* ORKCompletionStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B521A8D7C5B00081FAC /* ORKCompletionStep.h */; settings = {ATTRIBUTES = (Public, ); }; };
86C40CC21A8D7C5C00081FAC /* ORKCompletionStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B531A8D7C5B00081FAC /* ORKCompletionStep.m */; };
86C40CE41A8D7C5C00081FAC /* ORKAnswerFormat.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B641A8D7C5B00081FAC /* ORKAnswerFormat.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -333,11 +332,11 @@
86C40D141A8D7C5C00081FAC /* ORKHelpers_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B7C1A8D7C5C00081FAC /* ORKHelpers_Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
86C40D161A8D7C5C00081FAC /* ORKErrors.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B7D1A8D7C5C00081FAC /* ORKErrors.h */; settings = {ATTRIBUTES = (Private, ); }; };
86C40D181A8D7C5C00081FAC /* ORKErrors.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B7E1A8D7C5C00081FAC /* ORKErrors.m */; };
86C40D1A1A8D7C5C00081FAC /* ORKFormItem_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B7F1A8D7C5C00081FAC /* ORKFormItem_Internal.h */; };
86C40D1A1A8D7C5C00081FAC /* ORKFormItem_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B7F1A8D7C5C00081FAC /* ORKFormItem_Internal.h */; platformFilter = ios; };
86C40D201A8D7C5C00081FAC /* ORKFormStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B821A8D7C5C00081FAC /* ORKFormStep.h */; settings = {ATTRIBUTES = (Public, ); }; };
86C40D221A8D7C5C00081FAC /* ORKFormStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B831A8D7C5C00081FAC /* ORKFormStep.m */; };
86C40D301A8D7C5C00081FAC /* ORKHealthAnswerFormat.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B8A1A8D7C5C00081FAC /* ORKHealthAnswerFormat.h */; settings = {ATTRIBUTES = (Public, ); }; };
86C40D321A8D7C5C00081FAC /* ORKHealthAnswerFormat.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B8B1A8D7C5C00081FAC /* ORKHealthAnswerFormat.m */; };
86C40D301A8D7C5C00081FAC /* ORKHealthAnswerFormat.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B8A1A8D7C5C00081FAC /* ORKHealthAnswerFormat.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
86C40D321A8D7C5C00081FAC /* ORKHealthAnswerFormat.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B8B1A8D7C5C00081FAC /* ORKHealthAnswerFormat.m */; platformFilter = ios; };
86C40D361A8D7C5C00081FAC /* ORKHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B8D1A8D7C5C00081FAC /* ORKHelpers.m */; };
86C40D401A8D7C5C00081FAC /* ORKInstructionStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B921A8D7C5C00081FAC /* ORKInstructionStep.h */; settings = {ATTRIBUTES = (Public, ); }; };
86C40D421A8D7C5C00081FAC /* ORKInstructionStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B931A8D7C5C00081FAC /* ORKInstructionStep.m */; };
@@ -345,7 +344,7 @@
86C40D581A8D7C5C00081FAC /* ORKOrderedTask.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B9E1A8D7C5C00081FAC /* ORKOrderedTask.m */; };
86C40D5E1A8D7C5C00081FAC /* ORKQuestionStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BA11A8D7C5C00081FAC /* ORKQuestionStep.h */; settings = {ATTRIBUTES = (Public, ); }; };
86C40D601A8D7C5C00081FAC /* ORKQuestionStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40BA21A8D7C5C00081FAC /* ORKQuestionStep.m */; };
86C40D621A8D7C5C00081FAC /* ORKQuestionStep_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BA31A8D7C5C00081FAC /* ORKQuestionStep_Internal.h */; };
86C40D621A8D7C5C00081FAC /* ORKQuestionStep_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BA31A8D7C5C00081FAC /* ORKQuestionStep_Internal.h */; platformFilter = ios; };
86C40D6A1A8D7C5C00081FAC /* ORKResult.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BA71A8D7C5C00081FAC /* ORKResult.h */; settings = {ATTRIBUTES = (Public, ); }; };
86C40D6C1A8D7C5C00081FAC /* ORKResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40BA81A8D7C5C00081FAC /* ORKResult.m */; };
86C40D6E1A8D7C5C00081FAC /* ORKResult_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BA91A8D7C5C00081FAC /* ORKResult_Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
@@ -353,16 +352,16 @@
86C40D901A8D7C5C00081FAC /* ORKStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40BBA1A8D7C5C00081FAC /* ORKStep.m */; };
86C40D921A8D7C5C00081FAC /* ORKStep_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BBB1A8D7C5C00081FAC /* ORKStep_Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
86C40DC61A8D7C5C00081FAC /* ORKTask.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BD51A8D7C5C00081FAC /* ORKTask.h */; settings = {ATTRIBUTES = (Public, ); }; };
86C40DFE1A8D7C5C00081FAC /* ORKConsentDocument.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BF21A8D7C5C00081FAC /* ORKConsentDocument.h */; settings = {ATTRIBUTES = (Public, ); }; };
86C40E001A8D7C5C00081FAC /* ORKConsentDocument.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40BF31A8D7C5C00081FAC /* ORKConsentDocument.m */; };
86C40E021A8D7C5C00081FAC /* ORKConsentDocument_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BF41A8D7C5C00081FAC /* ORKConsentDocument_Internal.h */; };
86C40E081A8D7C5C00081FAC /* ORKConsentReviewStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BF71A8D7C5C00081FAC /* ORKConsentReviewStep.h */; settings = {ATTRIBUTES = (Public, ); }; };
86C40E0A1A8D7C5C00081FAC /* ORKConsentReviewStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40BF81A8D7C5C00081FAC /* ORKConsentReviewStep.m */; };
86C40E181A8D7C5C00081FAC /* ORKConsentSection.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BFF1A8D7C5C00081FAC /* ORKConsentSection.h */; settings = {ATTRIBUTES = (Public, ); }; };
86C40E1A1A8D7C5C00081FAC /* ORKConsentSection.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40C001A8D7C5C00081FAC /* ORKConsentSection.m */; };
86C40E1C1A8D7C5C00081FAC /* ORKConsentSection_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40C011A8D7C5C00081FAC /* ORKConsentSection_Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
86C40E1E1A8D7C5C00081FAC /* ORKConsentSignature.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40C021A8D7C5C00081FAC /* ORKConsentSignature.h */; settings = {ATTRIBUTES = (Public, ); }; };
86C40E201A8D7C5C00081FAC /* ORKConsentSignature.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40C031A8D7C5C00081FAC /* ORKConsentSignature.m */; };
86C40DFE1A8D7C5C00081FAC /* ORKConsentDocument.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BF21A8D7C5C00081FAC /* ORKConsentDocument.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
86C40E001A8D7C5C00081FAC /* ORKConsentDocument.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40BF31A8D7C5C00081FAC /* ORKConsentDocument.m */; platformFilter = ios; };
86C40E021A8D7C5C00081FAC /* ORKConsentDocument_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BF41A8D7C5C00081FAC /* ORKConsentDocument_Internal.h */; platformFilter = ios; };
86C40E081A8D7C5C00081FAC /* ORKConsentReviewStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BF71A8D7C5C00081FAC /* ORKConsentReviewStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
86C40E0A1A8D7C5C00081FAC /* ORKConsentReviewStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40BF81A8D7C5C00081FAC /* ORKConsentReviewStep.m */; platformFilter = ios; };
86C40E181A8D7C5C00081FAC /* ORKConsentSection.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BFF1A8D7C5C00081FAC /* ORKConsentSection.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
86C40E1A1A8D7C5C00081FAC /* ORKConsentSection.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40C001A8D7C5C00081FAC /* ORKConsentSection.m */; platformFilter = ios; };
86C40E1C1A8D7C5C00081FAC /* ORKConsentSection_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40C011A8D7C5C00081FAC /* ORKConsentSection_Private.h */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; };
86C40E1E1A8D7C5C00081FAC /* ORKConsentSignature.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40C021A8D7C5C00081FAC /* ORKConsentSignature.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
86C40E201A8D7C5C00081FAC /* ORKConsentSignature.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40C031A8D7C5C00081FAC /* ORKConsentSignature.m */; platformFilter = ios; };
86CC8EB31AC09383001CCD89 /* ORKAccessibilityTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 86CC8EA81AC09383001CCD89 /* ORKAccessibilityTests.m */; };
86CC8EB41AC09383001CCD89 /* ORKChoiceAnswerFormatHelperTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 86CC8EA91AC09383001CCD89 /* ORKChoiceAnswerFormatHelperTests.m */; };
86CC8EB51AC09383001CCD89 /* ORKConsentTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 86CC8EAA1AC09383001CCD89 /* ORKConsentTests.m */; };
@@ -372,21 +371,21 @@
86D348021AC161B0006DB02B /* ORKRecorderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 86D348001AC16175006DB02B /* ORKRecorderTests.m */; };
AE75433A24E32CCC00E4C7CF /* ORKEarlyTerminationConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = AE75433824E32CCC00E4C7CF /* ORKEarlyTerminationConfiguration.h */; settings = {ATTRIBUTES = (Public, ); }; };
AE75433B24E32CCC00E4C7CF /* ORKEarlyTerminationConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = AE75433924E32CCC00E4C7CF /* ORKEarlyTerminationConfiguration.m */; };
B11C54991A9EEF8800265E61 /* ORKConsentSharingStep.h in Headers */ = {isa = PBXBuildFile; fileRef = B11C54961A9EEF8800265E61 /* ORKConsentSharingStep.h */; settings = {ATTRIBUTES = (Public, ); }; };
B11C549B1A9EEF8800265E61 /* ORKConsentSharingStep.m in Sources */ = {isa = PBXBuildFile; fileRef = B11C54971A9EEF8800265E61 /* ORKConsentSharingStep.m */; };
B11C54991A9EEF8800265E61 /* ORKConsentSharingStep.h in Headers */ = {isa = PBXBuildFile; fileRef = B11C54961A9EEF8800265E61 /* ORKConsentSharingStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
B11C549B1A9EEF8800265E61 /* ORKConsentSharingStep.m in Sources */ = {isa = PBXBuildFile; fileRef = B11C54971A9EEF8800265E61 /* ORKConsentSharingStep.m */; platformFilter = ios; };
B183A4A21A8535D100C76870 /* ResearchKit_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = B1B894391A00345200C5CF2D /* ResearchKit_Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
B183A4DD1A8535D100C76870 /* ResearchKit.h in Headers */ = {isa = PBXBuildFile; fileRef = B1C1DE4F196F541F00F75544 /* ResearchKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
B1C0F4E41A9BA65F0022C153 /* ResearchKit.strings in Resources */ = {isa = PBXBuildFile; fileRef = B1C0F4E11A9BA65F0022C153 /* ResearchKit.strings */; };
B1C7955E1A9FBF04007279BA /* HealthKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B1C7955D1A9FBF04007279BA /* HealthKit.framework */; settings = {ATTRIBUTES = (Required, ); }; };
B1C0F4E41A9BA65F0022C153 /* ResearchKit.strings in Resources */ = {isa = PBXBuildFile; fileRef = B1C0F4E11A9BA65F0022C153 /* ResearchKit.strings */; platformFilter = ios; };
B1C7955E1A9FBF04007279BA /* HealthKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B1C7955D1A9FBF04007279BA /* HealthKit.framework */; platformFilter = ios; settings = {ATTRIBUTES = (Required, ); }; };
BA473FE8224DB38900A362E3 /* ORKBodyItem.h in Headers */ = {isa = PBXBuildFile; fileRef = BA473FE6224DB38900A362E3 /* ORKBodyItem.h */; settings = {ATTRIBUTES = (Public, ); }; };
BA473FE9224DB38900A362E3 /* ORKBodyItem.m in Sources */ = {isa = PBXBuildFile; fileRef = BA473FE7224DB38900A362E3 /* ORKBodyItem.m */; };
BA8C5021226FFB04001896D0 /* ORKLearnMoreItem.h in Headers */ = {isa = PBXBuildFile; fileRef = BA8C501F226FFB04001896D0 /* ORKLearnMoreItem.h */; settings = {ATTRIBUTES = (Public, ); }; };
BAB96755226E5D67006AAC56 /* ORKStepContainerViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BAB96754226E5D67006AAC56 /* ORKStepContainerViewTests.m */; };
BABBB1AE2097D97200CB29E5 /* ORKPDFViewerStep.h in Headers */ = {isa = PBXBuildFile; fileRef = BABBB1AC2097D97200CB29E5 /* ORKPDFViewerStep.h */; settings = {ATTRIBUTES = (Public, ); }; };
BABBB1AF2097D97200CB29E5 /* ORKPDFViewerStep.m in Sources */ = {isa = PBXBuildFile; fileRef = BABBB1AD2097D97200CB29E5 /* ORKPDFViewerStep.m */; };
BABBB1AE2097D97200CB29E5 /* ORKPDFViewerStep.h in Headers */ = {isa = PBXBuildFile; fileRef = BABBB1AC2097D97200CB29E5 /* ORKPDFViewerStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
BABBB1AF2097D97200CB29E5 /* ORKPDFViewerStep.m in Sources */ = {isa = PBXBuildFile; fileRef = BABBB1AD2097D97200CB29E5 /* ORKPDFViewerStep.m */; platformFilter = ios; };
BAD9E9122255E9750014FA29 /* ORKLearnMoreInstructionStep.h in Headers */ = {isa = PBXBuildFile; fileRef = BAD9E9102255E9750014FA29 /* ORKLearnMoreInstructionStep.h */; settings = {ATTRIBUTES = (Public, ); }; };
BAD9E9132255E9750014FA29 /* ORKLearnMoreInstructionStep.m in Sources */ = {isa = PBXBuildFile; fileRef = BAD9E9112255E9750014FA29 /* ORKLearnMoreInstructionStep.m */; };
BC081DE224CBC4DE00AD92AA /* ORKTypes_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = BC081DE124CBC4DE00AD92AA /* ORKTypes_Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
BC081DE224CBC4DE00AD92AA /* ORKTypes_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = BC081DE124CBC4DE00AD92AA /* ORKTypes_Private.h */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; };
BC13CE391B0660220044153C /* ORKNavigableOrderedTask.h in Headers */ = {isa = PBXBuildFile; fileRef = BC13CE371B0660220044153C /* ORKNavigableOrderedTask.h */; settings = {ATTRIBUTES = (Public, ); }; };
BC13CE3A1B0660220044153C /* ORKNavigableOrderedTask.m in Sources */ = {isa = PBXBuildFile; fileRef = BC13CE381B0660220044153C /* ORKNavigableOrderedTask.m */; };
BC13CE3C1B0662990044153C /* ORKStepNavigationRule_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = BC13CE3B1B0662990044153C /* ORKStepNavigationRule_Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
@@ -395,28 +394,28 @@
BC2908BC1FBD628F0030AB89 /* ORKTypes.m in Sources */ = {isa = PBXBuildFile; fileRef = BC2908BB1FBD628F0030AB89 /* ORKTypes.m */; };
BC40696121104DDF00A4100C /* ResearchKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B183A5951A8535D100C76870 /* ResearchKit.framework */; };
BC4D521F27B326EA0099DC18 /* ORKSecureCodingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC4D521E27B326EA0099DC18 /* ORKSecureCodingTests.swift */; };
BC94EF311E962F7400143081 /* ORKDeprecated.h in Headers */ = {isa = PBXBuildFile; fileRef = BC94EF2F1E962F7400143081 /* ORKDeprecated.h */; settings = {ATTRIBUTES = (Public, ); }; };
BC94EF361E96394C00143081 /* ORKRegistrationStep_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = BC94EF351E96394C00143081 /* ORKRegistrationStep_Internal.h */; };
BC94EF311E962F7400143081 /* ORKDeprecated.h in Headers */ = {isa = PBXBuildFile; fileRef = BC94EF2F1E962F7400143081 /* ORKDeprecated.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
BC94EF361E96394C00143081 /* ORKRegistrationStep_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = BC94EF351E96394C00143081 /* ORKRegistrationStep_Internal.h */; platformFilter = ios; };
BCA5C0351AEC05F20092AC8D /* ORKStepNavigationRule.h in Headers */ = {isa = PBXBuildFile; fileRef = BCA5C0331AEC05F20092AC8D /* ORKStepNavigationRule.h */; settings = {ATTRIBUTES = (Public, ); }; };
BCA5C0361AEC05F20092AC8D /* ORKStepNavigationRule.m in Sources */ = {isa = PBXBuildFile; fileRef = BCA5C0341AEC05F20092AC8D /* ORKStepNavigationRule.m */; };
BCAD50E81B0201EE0034806A /* ORKTaskTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BCAD50E71B0201EE0034806A /* ORKTaskTests.m */; };
BCB080A11B83EFB900A3F400 /* ORKStepNavigationRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCB080A01B83EFB900A3F400 /* ORKStepNavigationRule.swift */; };
BCB8133C1C98367A00346561 /* ORKTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = BCB8133B1C98367A00346561 /* ORKTypes.h */; settings = {ATTRIBUTES = (Public, ); }; };
BCB96C131B19C0EC002A0B96 /* ORKStepTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BCB96C121B19C0EC002A0B96 /* ORKStepTests.m */; };
BCCE9EC121104B2200B809F8 /* ORKConsentDocument_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = BCCE9EC021104B2200B809F8 /* ORKConsentDocument_Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
BCCE9EC121104B2200B809F8 /* ORKConsentDocument_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = BCCE9EC021104B2200B809F8 /* ORKConsentDocument_Private.h */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; };
BCFF24BD1B0798D10044EC35 /* ORKResultPredicate.m in Sources */ = {isa = PBXBuildFile; fileRef = BCFF24BC1B0798D10044EC35 /* ORKResultPredicate.m */; };
BF1D43851D4904C6007EE90B /* ORKVideoInstructionStep.h in Headers */ = {isa = PBXBuildFile; fileRef = BF1D43831D4904C6007EE90B /* ORKVideoInstructionStep.h */; settings = {ATTRIBUTES = (Public, ); }; };
BF1D43861D4904C6007EE90B /* ORKVideoInstructionStep.m in Sources */ = {isa = PBXBuildFile; fileRef = BF1D43841D4904C6007EE90B /* ORKVideoInstructionStep.m */; };
BF5161501BE9C53D00174DDD /* ORKWaitStep.h in Headers */ = {isa = PBXBuildFile; fileRef = BF9155A11BDE8DA9007FA459 /* ORKWaitStep.h */; settings = {ATTRIBUTES = (Public, ); }; };
BF91559B1BDE8D7D007FA459 /* ORKReviewStep_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = BF9155951BDE8D7D007FA459 /* ORKReviewStep_Internal.h */; };
BF91559C1BDE8D7D007FA459 /* ORKReviewStep.h in Headers */ = {isa = PBXBuildFile; fileRef = BF9155961BDE8D7D007FA459 /* ORKReviewStep.h */; settings = {ATTRIBUTES = (Public, ); }; };
BF91559D1BDE8D7D007FA459 /* ORKReviewStep.m in Sources */ = {isa = PBXBuildFile; fileRef = BF9155971BDE8D7D007FA459 /* ORKReviewStep.m */; };
BF9155A81BDE8DA9007FA459 /* ORKWaitStep.m in Sources */ = {isa = PBXBuildFile; fileRef = BF9155A21BDE8DA9007FA459 /* ORKWaitStep.m */; };
BF1D43851D4904C6007EE90B /* ORKVideoInstructionStep.h in Headers */ = {isa = PBXBuildFile; fileRef = BF1D43831D4904C6007EE90B /* ORKVideoInstructionStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
BF1D43861D4904C6007EE90B /* ORKVideoInstructionStep.m in Sources */ = {isa = PBXBuildFile; fileRef = BF1D43841D4904C6007EE90B /* ORKVideoInstructionStep.m */; platformFilter = ios; };
BF5161501BE9C53D00174DDD /* ORKWaitStep.h in Headers */ = {isa = PBXBuildFile; fileRef = BF9155A11BDE8DA9007FA459 /* ORKWaitStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
BF91559B1BDE8D7D007FA459 /* ORKReviewStep_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = BF9155951BDE8D7D007FA459 /* ORKReviewStep_Internal.h */; platformFilter = ios; };
BF91559C1BDE8D7D007FA459 /* ORKReviewStep.h in Headers */ = {isa = PBXBuildFile; fileRef = BF9155961BDE8D7D007FA459 /* ORKReviewStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
BF91559D1BDE8D7D007FA459 /* ORKReviewStep.m in Sources */ = {isa = PBXBuildFile; fileRef = BF9155971BDE8D7D007FA459 /* ORKReviewStep.m */; platformFilter = ios; };
BF9155A81BDE8DA9007FA459 /* ORKWaitStep.m in Sources */ = {isa = PBXBuildFile; fileRef = BF9155A21BDE8DA9007FA459 /* ORKWaitStep.m */; platformFilter = ios; };
CA08054028AD7CC8001695EF /* ORKViewControllerProviding.h in Headers */ = {isa = PBXBuildFile; fileRef = CA08053F28AD7CC8001695EF /* ORKViewControllerProviding.h */; settings = {ATTRIBUTES = (Private, ); }; };
CA0AC56928BD4FAC00E80040 /* ORKStepViewControllerHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA0AC56828BD4FAB00E80040 /* ORKStepViewControllerHelpers.swift */; };
CA1C7A44288B0C68004DAB3A /* ResearchKitUI.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D06632F24FEF272005D9B40 /* ResearchKitUI.h */; settings = {ATTRIBUTES = (Public, ); }; };
CA1C7A5C288B0CA2004DAB3A /* ResearchKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B183A5951A8535D100C76870 /* ResearchKit.framework */; };
CA23A31D2899941700EC6E91 /* ORKSkin_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = CA23A31C2899941700EC6E91 /* ORKSkin_Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
CA23A31D2899941700EC6E91 /* ORKSkin_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = CA23A31C2899941700EC6E91 /* ORKSkin_Private.h */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; };
CA2616D32894649A008B1425 /* ORKRequestPermissionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 51E03D6C2491B6DB008F8406 /* ORKRequestPermissionView.m */; };
CA2616D42894649A008B1425 /* ORKRequestPermissionButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 5170009C254A197300CACE12 /* ORKRequestPermissionButton.m */; };
CA2B8F6128A16B050025B773 /* ORKSpeechInNoiseStepViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7118AC6E20BF6A7700D7A6BB /* ORKSpeechInNoiseStepViewController.m */; };
@@ -538,20 +537,20 @@
CA2B8FF928A177C30025B773 /* ORKTrailmakingStepViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 781D540E1DF886AB00223305 /* ORKTrailmakingStepViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
CA2B8FFA28A177DE0025B773 /* ORKWalkingTaskStepViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B211A8D7C5B00081FAC /* ORKWalkingTaskStepViewController.m */; };
CA2B8FFB28A177E40025B773 /* ORKWalkingTaskStepViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B201A8D7C5B00081FAC /* ORKWalkingTaskStepViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
CA2B900928A17AD90025B773 /* ORKActiveStep_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B321A8D7C5B00081FAC /* ORKActiveStep_Internal.h */; settings = {ATTRIBUTES = (Private, ); }; };
CA2B900A28A17ADE0025B773 /* ORKActiveStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B311A8D7C5B00081FAC /* ORKActiveStep.m */; };
CA2B900B28A17AE10025B773 /* ORKActiveStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B301A8D7C5B00081FAC /* ORKActiveStep.h */; settings = {ATTRIBUTES = (Public, ); }; };
CA2B900928A17AD90025B773 /* ORKActiveStep_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B321A8D7C5B00081FAC /* ORKActiveStep_Internal.h */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; };
CA2B900A28A17ADE0025B773 /* ORKActiveStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B311A8D7C5B00081FAC /* ORKActiveStep.m */; platformFilter = ios; };
CA2B900B28A17AE10025B773 /* ORKActiveStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B301A8D7C5B00081FAC /* ORKActiveStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
CA2B901028A17BEE0025B773 /* ORKActiveTaskResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A261E81A87B005C2A1E /* ORKActiveTaskResult.h */; settings = {ATTRIBUTES = (Public, ); }; };
CA2B901128A17D120025B773 /* ORKFileResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A391E81AF1D005C2A1E /* ORKFileResult.h */; settings = {ATTRIBUTES = (Public, ); }; };
CA2B901228A17D170025B773 /* ORKFileResult.m in Sources */ = {isa = PBXBuildFile; fileRef = FF919A3A1E81AF1D005C2A1E /* ORKFileResult.m */; };
CA2B901128A17D120025B773 /* ORKFileResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A391E81AF1D005C2A1E /* ORKFileResult.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
CA2B901228A17D170025B773 /* ORKFileResult.m in Sources */ = {isa = PBXBuildFile; fileRef = FF919A3A1E81AF1D005C2A1E /* ORKFileResult.m */; platformFilter = ios; };
CA2B901628A1802C0025B773 /* ORKOrderedTask+ORKPredefinedActiveTask.m in Sources */ = {isa = PBXBuildFile; fileRef = FF154FB31E82EF5E004ED908 /* ORKOrderedTask+ORKPredefinedActiveTask.m */; };
CA2B901728A180480025B773 /* ORKOrderedTask+ORKPredefinedActiveTask.h in Headers */ = {isa = PBXBuildFile; fileRef = FF154FB21E82EF5E004ED908 /* ORKOrderedTask+ORKPredefinedActiveTask.h */; settings = {ATTRIBUTES = (Public, ); }; };
CA2B901F28A1864A0025B773 /* ORKRecorder.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B471A8D7C5B00081FAC /* ORKRecorder.h */; settings = {ATTRIBUTES = (Public, ); }; };
CA2B902028A186500025B773 /* ORKRecorder_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B491A8D7C5B00081FAC /* ORKRecorder_Internal.h */; };
CA2B902128A186550025B773 /* ORKRecorder_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B4A1A8D7C5B00081FAC /* ORKRecorder_Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
CA2B902228A1867E0025B773 /* ORKRecorder.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B481A8D7C5B00081FAC /* ORKRecorder.m */; };
CA2B902328A186A80025B773 /* ORKDataLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B3C1A8D7C5B00081FAC /* ORKDataLogger.h */; settings = {ATTRIBUTES = (Private, ); }; };
CA2B902428A186AF0025B773 /* ORKDataLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B3D1A8D7C5B00081FAC /* ORKDataLogger.m */; };
CA2B901F28A1864A0025B773 /* ORKRecorder.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B471A8D7C5B00081FAC /* ORKRecorder.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
CA2B902028A186500025B773 /* ORKRecorder_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B491A8D7C5B00081FAC /* ORKRecorder_Internal.h */; platformFilter = ios; };
CA2B902128A186550025B773 /* ORKRecorder_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B4A1A8D7C5B00081FAC /* ORKRecorder_Private.h */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; };
CA2B902228A1867E0025B773 /* ORKRecorder.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B481A8D7C5B00081FAC /* ORKRecorder.m */; platformFilter = ios; };
CA2B902328A186A80025B773 /* ORKDataLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B3C1A8D7C5B00081FAC /* ORKDataLogger.h */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; };
CA2B902428A186AF0025B773 /* ORKDataLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B3D1A8D7C5B00081FAC /* ORKDataLogger.m */; platformFilter = ios; };
CA2B902628A187390025B773 /* ORKTask_Util.m in Sources */ = {isa = PBXBuildFile; fileRef = CA2B902528A187390025B773 /* ORKTask_Util.m */; };
CA2B902728A18EA60025B773 /* ORKActiveStepViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B381A8D7C5B00081FAC /* ORKActiveStepViewController.m */; };
CA2B902828A18EAD0025B773 /* ORKActiveStepViewController_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B391A8D7C5B00081FAC /* ORKActiveStepViewController_Internal.h */; settings = {ATTRIBUTES = (Private, ); }; };
@@ -567,15 +566,15 @@
CA6A0D7F288B51D30048C1EF /* ORKSkin.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40BB81A8D7C5C00081FAC /* ORKSkin.m */; };
CA6A0D81288B54650048C1EF /* ORKConsentReviewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BEC1A8D7C5C00081FAC /* ORKConsentReviewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
CA6A0D82288B54650048C1EF /* ORKConsentReviewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40BED1A8D7C5C00081FAC /* ORKConsentReviewController.m */; };
CA6A0D83288B5B370048C1EF /* ORKHTMLPDFWriter.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B8E1A8D7C5C00081FAC /* ORKHTMLPDFWriter.h */; settings = {ATTRIBUTES = (Public, ); }; };
CA6A0D84288B5B370048C1EF /* ORKHTMLPDFPageRenderer.h in Headers */ = {isa = PBXBuildFile; fileRef = 8DE27B3E1D5BC072009A26E3 /* ORKHTMLPDFPageRenderer.h */; };
CA6A0D85288B5B370048C1EF /* ORKHTMLPDFPageRenderer.m in Sources */ = {isa = PBXBuildFile; fileRef = 8DE27B3F1D5BC0B9009A26E3 /* ORKHTMLPDFPageRenderer.m */; };
CA6A0D86288B5B370048C1EF /* ORKHTMLPDFWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B8F1A8D7C5C00081FAC /* ORKHTMLPDFWriter.m */; };
CA6A0D83288B5B370048C1EF /* ORKHTMLPDFWriter.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B8E1A8D7C5C00081FAC /* ORKHTMLPDFWriter.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
CA6A0D84288B5B370048C1EF /* ORKHTMLPDFPageRenderer.h in Headers */ = {isa = PBXBuildFile; fileRef = 8DE27B3E1D5BC072009A26E3 /* ORKHTMLPDFPageRenderer.h */; platformFilter = ios; };
CA6A0D85288B5B370048C1EF /* ORKHTMLPDFPageRenderer.m in Sources */ = {isa = PBXBuildFile; fileRef = 8DE27B3F1D5BC0B9009A26E3 /* ORKHTMLPDFPageRenderer.m */; platformFilter = ios; };
CA6A0D86288B5B370048C1EF /* ORKHTMLPDFWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B8F1A8D7C5C00081FAC /* ORKHTMLPDFWriter.m */; platformFilter = ios; };
CA6A0D9E288F1CA20048C1EF /* ResearchKitUI_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = CA6A0D9D288F1BDC0048C1EF /* ResearchKitUI_Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
CA6A0DA0288F2C1C0048C1EF /* ResearchKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CA1C7A5A288B0C68004DAB3A /* ResearchKitUI.framework */; };
CA954B6E28AD8A8C0020A35C /* ORKStep+ResearchKitActiveTask.m in Sources */ = {isa = PBXBuildFile; fileRef = CA954B6D28AD8A8C0020A35C /* ORKStep+ResearchKitActiveTask.m */; };
CA954B6F28AD8DA50020A35C /* ORKStep+ResearchKitUI.m in Sources */ = {isa = PBXBuildFile; fileRef = CA08054328AD7EBA001695EF /* ORKStep+ResearchKitUI.m */; };
CA994D5628AB08410019DEA4 /* ORKDeprecated.m in Sources */ = {isa = PBXBuildFile; fileRef = BC94EF301E962F7400143081 /* ORKDeprecated.m */; };
CA994D5628AB08410019DEA4 /* ORKDeprecated.m in Sources */ = {isa = PBXBuildFile; fileRef = BC94EF301E962F7400143081 /* ORKDeprecated.m */; platformFilter = ios; };
CAA20D4E288B3D5700EDC764 /* ORKRecordButton.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D43C5CF24217393006F4084 /* ORKRecordButton.h */; settings = {ATTRIBUTES = (Public, ); }; };
CAA20D4F288B3D5700EDC764 /* ORKRecordButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D43C5D024217393006F4084 /* ORKRecordButton.m */; };
CAA20D50288B3D6400EDC764 /* ORKCheckmarkView.h in Headers */ = {isa = PBXBuildFile; fileRef = BAD6FAAF2332A2FD006647E7 /* ORKCheckmarkView.h */; settings = {ATTRIBUTES = (Private, ); }; };
@@ -989,41 +988,42 @@
CAFAA6C228A198BD0010BBDE /* ResearchKitActiveTask_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = CAFAA6C128A198BD0010BBDE /* ResearchKitActiveTask_Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
CAFAA6C428A19E200010BBDE /* ResearchKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B183A5951A8535D100C76870 /* ResearchKit.framework */; };
CAFAA6C528A19E260010BBDE /* ResearchKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CA1C7A5A288B0C68004DAB3A /* ResearchKitUI.framework */; };
D44239791AF17F5100559D96 /* ORKImageCaptureStep.h in Headers */ = {isa = PBXBuildFile; fileRef = D44239771AF17F5100559D96 /* ORKImageCaptureStep.h */; settings = {ATTRIBUTES = (Public, ); }; };
D442397A1AF17F5100559D96 /* ORKImageCaptureStep.m in Sources */ = {isa = PBXBuildFile; fileRef = D44239781AF17F5100559D96 /* ORKImageCaptureStep.m */; };
D44239791AF17F5100559D96 /* ORKImageCaptureStep.h in Headers */ = {isa = PBXBuildFile; fileRef = D44239771AF17F5100559D96 /* ORKImageCaptureStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
D442397A1AF17F5100559D96 /* ORKImageCaptureStep.m in Sources */ = {isa = PBXBuildFile; fileRef = D44239781AF17F5100559D96 /* ORKImageCaptureStep.m */; platformFilter = ios; };
E454438D2CC1C78E0004E385 /* ResearchKit_Prefix.pch in Headers */ = {isa = PBXBuildFile; fileRef = 5DABE5AD24DA16E600570C57 /* ResearchKit_Prefix.pch */; };
FA7A9D2B1B082688005A2BEA /* ORKConsentDocumentTests.m in Sources */ = {isa = PBXBuildFile; fileRef = FA7A9D2A1B082688005A2BEA /* ORKConsentDocumentTests.m */; };
FA7A9D2F1B083DD3005A2BEA /* ORKConsentSectionFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = FA7A9D2D1B083DD3005A2BEA /* ORKConsentSectionFormatter.h */; };
FA7A9D301B083DD3005A2BEA /* ORKConsentSectionFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = FA7A9D2E1B083DD3005A2BEA /* ORKConsentSectionFormatter.m */; };
FA7A9D331B0843A9005A2BEA /* ORKConsentSignatureFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = FA7A9D311B0843A9005A2BEA /* ORKConsentSignatureFormatter.h */; };
FA7A9D341B0843A9005A2BEA /* ORKConsentSignatureFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = FA7A9D321B0843A9005A2BEA /* ORKConsentSignatureFormatter.m */; };
FA7A9D2F1B083DD3005A2BEA /* ORKConsentSectionFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = FA7A9D2D1B083DD3005A2BEA /* ORKConsentSectionFormatter.h */; platformFilter = ios; };
FA7A9D301B083DD3005A2BEA /* ORKConsentSectionFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = FA7A9D2E1B083DD3005A2BEA /* ORKConsentSectionFormatter.m */; platformFilter = ios; };
FA7A9D331B0843A9005A2BEA /* ORKConsentSignatureFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = FA7A9D311B0843A9005A2BEA /* ORKConsentSignatureFormatter.h */; platformFilter = ios; };
FA7A9D341B0843A9005A2BEA /* ORKConsentSignatureFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = FA7A9D321B0843A9005A2BEA /* ORKConsentSignatureFormatter.m */; platformFilter = ios; };
FA7A9D371B09365F005A2BEA /* ORKConsentSectionFormatterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = FA7A9D361B09365F005A2BEA /* ORKConsentSectionFormatterTests.m */; };
FA7A9D391B0969A7005A2BEA /* ORKConsentSignatureFormatterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = FA7A9D381B0969A7005A2BEA /* ORKConsentSignatureFormatterTests.m */; };
FF0CB38A1FD5C4C3002D838C /* ORKWebViewStepResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF0CB3881FD5C4C3002D838C /* ORKWebViewStepResult.h */; settings = {ATTRIBUTES = (Public, ); }; };
FF0CB38B1FD5C4C3002D838C /* ORKWebViewStepResult.m in Sources */ = {isa = PBXBuildFile; fileRef = FF0CB3891FD5C4C3002D838C /* ORKWebViewStepResult.m */; };
FF5051ED1D668FF80065E677 /* ORKPageStep_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = FF5051EC1D668FF80065E677 /* ORKPageStep_Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
FF5051F01D66908C0065E677 /* ORKNavigablePageStep.h in Headers */ = {isa = PBXBuildFile; fileRef = FF5051EE1D66908C0065E677 /* ORKNavigablePageStep.h */; settings = {ATTRIBUTES = (Public, ); }; };
FF5051F11D66908C0065E677 /* ORKNavigablePageStep.m in Sources */ = {isa = PBXBuildFile; fileRef = FF5051EF1D66908C0065E677 /* ORKNavigablePageStep.m */; };
FF5CA6121D2C2670001660A3 /* ORKTableStep.h in Headers */ = {isa = PBXBuildFile; fileRef = FF5CA6101D2C2670001660A3 /* ORKTableStep.h */; settings = {ATTRIBUTES = (Public, ); }; };
FF5CA6131D2C2670001660A3 /* ORKTableStep.m in Sources */ = {isa = PBXBuildFile; fileRef = FF5CA6111D2C2670001660A3 /* ORKTableStep.m */; };
FF5CA61B1D2C6453001660A3 /* ORKSignatureStep.h in Headers */ = {isa = PBXBuildFile; fileRef = FF5CA6191D2C6453001660A3 /* ORKSignatureStep.h */; settings = {ATTRIBUTES = (Public, ); }; };
FF5CA61C1D2C6453001660A3 /* ORKSignatureStep.m in Sources */ = {isa = PBXBuildFile; fileRef = FF5CA61A1D2C6453001660A3 /* ORKSignatureStep.m */; };
FF0CB38A1FD5C4C3002D838C /* ORKWebViewStepResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF0CB3881FD5C4C3002D838C /* ORKWebViewStepResult.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
FF0CB38B1FD5C4C3002D838C /* ORKWebViewStepResult.m in Sources */ = {isa = PBXBuildFile; fileRef = FF0CB3891FD5C4C3002D838C /* ORKWebViewStepResult.m */; platformFilter = ios; };
FF5051ED1D668FF80065E677 /* ORKPageStep_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = FF5051EC1D668FF80065E677 /* ORKPageStep_Private.h */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; };
FF5051F01D66908C0065E677 /* ORKNavigablePageStep.h in Headers */ = {isa = PBXBuildFile; fileRef = FF5051EE1D66908C0065E677 /* ORKNavigablePageStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
FF5051F11D66908C0065E677 /* ORKNavigablePageStep.m in Sources */ = {isa = PBXBuildFile; fileRef = FF5051EF1D66908C0065E677 /* ORKNavigablePageStep.m */; platformFilter = ios; };
FF5CA6121D2C2670001660A3 /* ORKTableStep.h in Headers */ = {isa = PBXBuildFile; fileRef = FF5CA6101D2C2670001660A3 /* ORKTableStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
FF5CA6131D2C2670001660A3 /* ORKTableStep.m in Sources */ = {isa = PBXBuildFile; fileRef = FF5CA6111D2C2670001660A3 /* ORKTableStep.m */; platformFilter = ios; };
FF5CA61B1D2C6453001660A3 /* ORKSignatureStep.h in Headers */ = {isa = PBXBuildFile; fileRef = FF5CA6191D2C6453001660A3 /* ORKSignatureStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
FF5CA61C1D2C6453001660A3 /* ORKSignatureStep.m in Sources */ = {isa = PBXBuildFile; fileRef = FF5CA61A1D2C6453001660A3 /* ORKSignatureStep.m */; platformFilter = ios; };
FF919A531E81BEB5005C2A1E /* ORKCollectionResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A511E81BEB5005C2A1E /* ORKCollectionResult.h */; settings = {ATTRIBUTES = (Public, ); }; };
FF919A541E81BEB5005C2A1E /* ORKCollectionResult.m in Sources */ = {isa = PBXBuildFile; fileRef = FF919A521E81BEB5005C2A1E /* ORKCollectionResult.m */; };
FF919A561E81BEE0005C2A1E /* ORKCollectionResult_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A551E81BEDD005C2A1E /* ORKCollectionResult_Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
FF919A591E81C628005C2A1E /* ORKQuestionResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A571E81C628005C2A1E /* ORKQuestionResult.h */; settings = {ATTRIBUTES = (Public, ); }; };
FF919A5A1E81C628005C2A1E /* ORKQuestionResult.m in Sources */ = {isa = PBXBuildFile; fileRef = FF919A581E81C628005C2A1E /* ORKQuestionResult.m */; };
FF919A5C1E81C641005C2A1E /* ORKQuestionResult_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A5B1E81C63B005C2A1E /* ORKQuestionResult_Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
FF919A5F1E81CF07005C2A1E /* ORKVideoInstructionStepResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A5D1E81CF07005C2A1E /* ORKVideoInstructionStepResult.h */; settings = {ATTRIBUTES = (Public, ); }; };
FF919A601E81CF07005C2A1E /* ORKVideoInstructionStepResult.m in Sources */ = {isa = PBXBuildFile; fileRef = FF919A5E1E81CF07005C2A1E /* ORKVideoInstructionStepResult.m */; };
FF919A631E81D04D005C2A1E /* ORKSignatureResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A611E81D04D005C2A1E /* ORKSignatureResult.h */; settings = {ATTRIBUTES = (Public, ); }; };
FF919A641E81D04D005C2A1E /* ORKSignatureResult.m in Sources */ = {isa = PBXBuildFile; fileRef = FF919A621E81D04D005C2A1E /* ORKSignatureResult.m */; };
FF919A661E81D168005C2A1E /* ORKSignatureResult_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A651E81D164005C2A1E /* ORKSignatureResult_Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
FF919A691E81D255005C2A1E /* ORKConsentSignatureResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A671E81D255005C2A1E /* ORKConsentSignatureResult.h */; settings = {ATTRIBUTES = (Public, ); }; };
FF919A6A1E81D255005C2A1E /* ORKConsentSignatureResult.m in Sources */ = {isa = PBXBuildFile; fileRef = FF919A681E81D255005C2A1E /* ORKConsentSignatureResult.m */; };
FF919A6D1E81D3B0005C2A1E /* ORKPasscodeResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A6B1E81D3B0005C2A1E /* ORKPasscodeResult.h */; settings = {ATTRIBUTES = (Public, ); }; };
FF919A6E1E81D3B0005C2A1E /* ORKPasscodeResult.m in Sources */ = {isa = PBXBuildFile; fileRef = FF919A6C1E81D3B0005C2A1E /* ORKPasscodeResult.m */; };
FFDDD8491D3555EA00446806 /* ORKPageStep.h in Headers */ = {isa = PBXBuildFile; fileRef = FFDDD8471D3555EA00446806 /* ORKPageStep.h */; settings = {ATTRIBUTES = (Public, ); }; };
FFDDD84A1D3555EA00446806 /* ORKPageStep.m in Sources */ = {isa = PBXBuildFile; fileRef = FFDDD8481D3555EA00446806 /* ORKPageStep.m */; };
FF919A5F1E81CF07005C2A1E /* ORKVideoInstructionStepResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A5D1E81CF07005C2A1E /* ORKVideoInstructionStepResult.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
FF919A601E81CF07005C2A1E /* ORKVideoInstructionStepResult.m in Sources */ = {isa = PBXBuildFile; fileRef = FF919A5E1E81CF07005C2A1E /* ORKVideoInstructionStepResult.m */; platformFilter = ios; };
FF919A631E81D04D005C2A1E /* ORKSignatureResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A611E81D04D005C2A1E /* ORKSignatureResult.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
FF919A641E81D04D005C2A1E /* ORKSignatureResult.m in Sources */ = {isa = PBXBuildFile; fileRef = FF919A621E81D04D005C2A1E /* ORKSignatureResult.m */; platformFilter = ios; };
FF919A661E81D168005C2A1E /* ORKSignatureResult_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A651E81D164005C2A1E /* ORKSignatureResult_Private.h */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; };
FF919A691E81D255005C2A1E /* ORKConsentSignatureResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A671E81D255005C2A1E /* ORKConsentSignatureResult.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
FF919A6A1E81D255005C2A1E /* ORKConsentSignatureResult.m in Sources */ = {isa = PBXBuildFile; fileRef = FF919A681E81D255005C2A1E /* ORKConsentSignatureResult.m */; platformFilter = ios; };
FF919A6D1E81D3B0005C2A1E /* ORKPasscodeResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A6B1E81D3B0005C2A1E /* ORKPasscodeResult.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
FF919A6E1E81D3B0005C2A1E /* ORKPasscodeResult.m in Sources */ = {isa = PBXBuildFile; fileRef = FF919A6C1E81D3B0005C2A1E /* ORKPasscodeResult.m */; platformFilter = ios; };
FFDDD8491D3555EA00446806 /* ORKPageStep.h in Headers */ = {isa = PBXBuildFile; fileRef = FFDDD8471D3555EA00446806 /* ORKPageStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
FFDDD84A1D3555EA00446806 /* ORKPageStep.m in Sources */ = {isa = PBXBuildFile; fileRef = FFDDD8481D3555EA00446806 /* ORKPageStep.m */; platformFilter = ios; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -4669,20 +4669,20 @@
866DA5201D63D04700C9AF3F /* ORKCollector.h in Headers */,
51A11F1A2BD08D5E0060C07E /* CMMotionActivity+ORKJSONDictionary.h in Headers */,
BF91559B1BDE8D7D007FA459 /* ORKReviewStep_Internal.h in Headers */,
FF919A561E81BEE0005C2A1E /* ORKCollectionResult_Private.h in Headers */,
FF919A661E81D168005C2A1E /* ORKSignatureResult_Private.h in Headers */,
FF5CA61B1D2C6453001660A3 /* ORKSignatureStep.h in Headers */,
BC081DE224CBC4DE00AD92AA /* ORKTypes_Private.h in Headers */,
5D04885C25F1B3AB0006C68B /* ORKDevice_Private.h in Headers */,
00C2668E23022CD400337E0B /* ORKCustomStep.h in Headers */,
BCCE9EC121104B2200B809F8 /* ORKConsentDocument_Private.h in Headers */,
86C40D201A8D7C5C00081FAC /* ORKFormStep.h in Headers */,
CA2B901F28A1864A0025B773 /* ORKRecorder.h in Headers */,
BA473FE8224DB38900A362E3 /* ORKBodyItem.h in Headers */,
2429D5721BBB5397003A512F /* ORKRegistrationStep.h in Headers */,
5192BF842AE1BA47006E43FB /* ORKFrontFacingCameraStep.h in Headers */,
FF919A561E81BEE0005C2A1E /* ORKCollectionResult_Private.h in Headers */,
FF919A661E81D168005C2A1E /* ORKSignatureResult_Private.h in Headers */,
BC081DE224CBC4DE00AD92AA /* ORKTypes_Private.h in Headers */,
5D04885C25F1B3AB0006C68B /* ORKDevice_Private.h in Headers */,
BCCE9EC121104B2200B809F8 /* ORKConsentDocument_Private.h in Headers */,
86C40D201A8D7C5C00081FAC /* ORKFormStep.h in Headers */,
51B94DBD2B311E810039B0E7 /* CLLocationManager+ResearchKit.h in Headers */,
B183A4A21A8535D100C76870 /* ResearchKit_Private.h in Headers */,
5192BF842AE1BA47006E43FB /* ORKFrontFacingCameraStep.h in Headers */,
CA2B900B28A17AE10025B773 /* ORKActiveStep.h in Headers */,
AE75433A24E32CCC00E4C7CF /* ORKEarlyTerminationConfiguration.h in Headers */,
86C40CE81A8D7C5C00081FAC /* ORKAnswerFormat_Internal.h in Headers */,
@@ -4707,6 +4707,16 @@
519CE82A2C6582BE003BB584 /* ORKConditionStepConfiguration.h in Headers */,
51AF1B202B683C3400D3B399 /* ORKWebViewStepResult_Private.h in Headers */,
5D04884C25EF4CC30006C68B /* ORKQuestionStep_Private.h in Headers */,
8419D66E1FB73CC80088D7E5 /* ORKWebViewStep.h in Headers */,
FFDDD8491D3555EA00446806 /* ORKPageStep.h in Headers */,
D44239791AF17F5100559D96 /* ORKImageCaptureStep.h in Headers */,
FF919A6D1E81D3B0005C2A1E /* ORKPasscodeResult.h in Headers */,
5192BF502AE09673006E43FB /* ORKPredicateFormItemVisibilityRule.h in Headers */,
FF0CB38A1FD5C4C3002D838C /* ORKWebViewStepResult.h in Headers */,
86C40D301A8D7C5C00081FAC /* ORKHealthAnswerFormat.h in Headers */,
FF5CA6121D2C2670001660A3 /* ORKTableStep.h in Headers */,
51A11F172BD08D5E0060C07E /* HKSample+ORKJSONDictionary.h in Headers */,
BC13CE401B0666FD0044153C /* ORKResultPredicate.h in Headers */,
86C40D621A8D7C5C00081FAC /* ORKQuestionStep_Internal.h in Headers */,
B183A4DD1A8535D100C76870 /* ResearchKit.h in Headers */,
BAD9E9122255E9750014FA29 /* ORKLearnMoreInstructionStep.h in Headers */,
@@ -4729,25 +4739,16 @@
86C40D5E1A8D7C5C00081FAC /* ORKQuestionStep.h in Headers */,
519CE82C2C6582BE003BB584 /* ORKRelatedPerson.h in Headers */,
FF919A591E81C628005C2A1E /* ORKQuestionResult.h in Headers */,
8419D66E1FB73CC80088D7E5 /* ORKWebViewStep.h in Headers */,
FFDDD8491D3555EA00446806 /* ORKPageStep.h in Headers */,
D44239791AF17F5100559D96 /* ORKImageCaptureStep.h in Headers */,
FF919A6D1E81D3B0005C2A1E /* ORKPasscodeResult.h in Headers */,
24C296771BD055B800B42EF1 /* ORKLoginStep_Internal.h in Headers */,
86C40CC01A8D7C5C00081FAC /* ORKCompletionStep.h in Headers */,
5192BF502AE09673006E43FB /* ORKPredicateFormItemVisibilityRule.h in Headers */,
519CE8272C6582BE003BB584 /* ORKRelativeGroup.h in Headers */,
14A92C4822440195007547F2 /* ORKHelpers_Internal.h in Headers */,
FF0CB38A1FD5C4C3002D838C /* ORKWebViewStepResult.h in Headers */,
5D43C5D424255675006F4084 /* ORKBodyItem_Internal.h in Headers */,
86C40D301A8D7C5C00081FAC /* ORKHealthAnswerFormat.h in Headers */,
7167D028231B1EAA00AAB4DD /* ORKFormStep_Internal.h in Headers */,
FF5CA6121D2C2670001660A3 /* ORKTableStep.h in Headers */,
FA7A9D2F1B083DD3005A2BEA /* ORKConsentSectionFormatter.h in Headers */,
51A11F172BD08D5E0060C07E /* HKSample+ORKJSONDictionary.h in Headers */,
86C40D561A8D7C5C00081FAC /* ORKOrderedTask.h in Headers */,
FF5051F01D66908C0065E677 /* ORKNavigablePageStep.h in Headers */,
BC13CE401B0666FD0044153C /* ORKResultPredicate.h in Headers */,
51EB9A532B8408D50064A515 /* ORKInstructionStepHTMLFormatter.h in Headers */,
86C40E081A8D7C5C00081FAC /* ORKConsentReviewStep.h in Headers */,
519CE82B2C6582BE003BB584 /* ORKFamilyHistoryStep.h in Headers */,
@@ -4890,6 +4891,7 @@
CAA20D7C288B3D9100EDC764 /* ORKSurveyAnswerCellForSES.h in Headers */,
CAA20F3B288B3F6D00EDC764 /* UIView+ORKAccessibility.h in Headers */,
CAA20D61288B3D9100EDC764 /* ORKScaleSlider.h in Headers */,
E454438D2CC1C78E0004E385 /* ResearchKit_Prefix.pch in Headers */,
CAA20DEF288B3DB400EDC764 /* ORKCaption1Label.h in Headers */,
CAA20F31288B3F5B00EDC764 /* ORKConsentReviewStepViewController.h in Headers */,
CAA20DC8288B3DB400EDC764 /* ORKRoundTappingButton.h in Headers */,
@@ -5237,7 +5239,7 @@
attributes = {
BuildIndependentTargetsInParallel = YES;
CLASSPREFIX = ORK;
LastSwiftUpdateCheck = 1530;
LastSwiftUpdateCheck = 1600;
LastUpgradeCheck = 1500;
ORGANIZATIONNAME = researchkit.org;
TargetAttributes = {
@@ -5259,7 +5261,7 @@
};
};
buildConfigurationList = 3FFF18381829DB1D00167070 /* Build configuration list for PBXProject "ResearchKit" */;
compatibilityVersion = "Xcode 3.2";
compatibilityVersion = "Xcode 15.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
@@ -5530,18 +5532,18 @@
519CE8292C6582BE003BB584 /* ORKConditionStepConfiguration.m in Sources */,
86C40D6C1A8D7C5C00081FAC /* ORKResult.m in Sources */,
86C40D181A8D7C5C00081FAC /* ORKErrors.m in Sources */,
CA2B902428A186AF0025B773 /* ORKDataLogger.m in Sources */,
5192BF852AE1BA47006E43FB /* ORKFrontFacingCameraStepResult.m in Sources */,
031A0FC224CF4ECD000E4455 /* ORKSensorPermissionType.m in Sources */,
FF919A6A1E81D255005C2A1E /* ORKConsentSignatureResult.m in Sources */,
CA6A0D85288B5B370048C1EF /* ORKHTMLPDFPageRenderer.m in Sources */,
519CE8232C6582BE003BB584 /* ORKFamilyHistoryStep.m in Sources */,
51A11F182BD08D5E0060C07E /* CMMotionActivity+ORKJSONDictionary.m in Sources */,
24A4DA151B8D1115009C797A /* ORKPasscodeStep.m in Sources */,
BCA5C0361AEC05F20092AC8D /* ORKStepNavigationRule.m in Sources */,
FF919A6A1E81D255005C2A1E /* ORKConsentSignatureResult.m in Sources */,
5192BF5A2AE09794006E43FB /* ORKFormItemVisibilityRule.m in Sources */,
86C40CC21A8D7C5C00081FAC /* ORKCompletionStep.m in Sources */,
BF9155A81BDE8DA9007FA459 /* ORKWaitStep.m in Sources */,
BCB080A11B83EFB900A3F400 /* ORKStepNavigationRule.swift in Sources */,
AE75433B24E32CCC00E4C7CF /* ORKEarlyTerminationConfiguration.m in Sources */,
519CE8282C6582BE003BB584 /* ORKRelativeGroup.m in Sources */,
86C40D361A8D7C5C00081FAC /* ORKHelpers.m in Sources */,
@@ -5561,6 +5563,7 @@
51A11F192BD08D5E0060C07E /* HKSample+ORKJSONDictionary.m in Sources */,
86C40D581A8D7C5C00081FAC /* ORKOrderedTask.m in Sources */,
86C40D601A8D7C5C00081FAC /* ORKQuestionStep.m in Sources */,
BCB080A11B83EFB900A3F400 /* ORKStepNavigationRule.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -5692,7 +5695,6 @@
CAA20F3A288B3F6D00EDC764 /* ORKAccessibilityFunctions.m in Sources */,
5192BF982AE1C5A1006E43FB /* ORKAnswerFormat+FormStepViewControllerAdditions.m in Sources */,
CAA20E01288B3E8100EDC764 /* ORKRequestPermissionsStepContainerView.m in Sources */,
51A11F242BD1548C0060C07E /* UIImageView+ResearchKit.m in Sources */,
CAA20F38288B3F6700EDC764 /* ORKVerificationStepView.m in Sources */,
CAA20D6C288B3D9100EDC764 /* ORKSurveyAnswerCellForScale.m in Sources */,
CAA20E2A288B3E8200EDC764 /* ORKStepView.m in Sources */,
@@ -6018,11 +6020,29 @@
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
0BC672DA2BD9C52D005798AC /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = "";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
0BC672DC2BD9C52D005798AC /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = "";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
3FFF18691829DB1E00167070 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 5D000ED12620F27100E5442A /* Project-Debug.xcconfig */;
buildSettings = {
ENABLE_MODULE_VERIFIER = YES;
ENABLE_MODULE_VERIFIER = NO;
ENABLE_TESTING_SEARCH_PATHS = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
};
@@ -6076,9 +6096,11 @@
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MODULEMAP_PRIVATE_FILE = ResearchKit/ResearchKit_Private.modulemap;
"OTHER_SWIFT_FLAGS[arch=*]" = "$(inherited) -runtime-compatibility-version none";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator watchos watchsimulator xros xrsimulator";
SUPPORTS_MACCATALYST = NO;
TARGETED_DEVICE_FAMILY = "1,2";
TARGETED_DEVICE_FAMILY = "1,2,4,7";
WATCHOS_DEPLOYMENT_TARGET = 10.0;
XROS_DEPLOYMENT_TARGET = 1.0;
};
name = Debug;
};
@@ -6088,9 +6110,11 @@
buildSettings = {
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MODULEMAP_PRIVATE_FILE = ResearchKit/ResearchKit_Private.modulemap;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator watchos watchsimulator xros xrsimulator";
SUPPORTS_MACCATALYST = NO;
TARGETED_DEVICE_FAMILY = "1,2";
TARGETED_DEVICE_FAMILY = "1,2,4,7";
WATCHOS_DEPLOYMENT_TARGET = 10.0;
XROS_DEPLOYMENT_TARGET = 1.0;
};
name = Release;
};
@@ -6102,6 +6126,7 @@
CURRENT_PROJECT_VERSION = "";
ENABLE_MODULE_VERIFIER = YES;
ENABLE_TESTING_SEARCH_PATHS = YES;
GCC_PREFIX_HEADER = ResearchKit/ResearchKit_Prefix.pch;
MODULEMAP_PRIVATE_FILE = ResearchKitUI/ResearchKitUI_Private.modulemap;
PRODUCT_NAME = ResearchKitUI;
SUPPORTS_MACCATALYST = NO;
@@ -6118,6 +6143,7 @@
CURRENT_PROJECT_VERSION = "";
ENABLE_MODULE_VERIFIER = YES;
ENABLE_TESTING_SEARCH_PATHS = YES;
GCC_PREFIX_HEADER = ResearchKit/ResearchKit_Prefix.pch;
MODULEMAP_PRIVATE_FILE = ResearchKitUI/ResearchKitUI_Private.modulemap;
PRODUCT_NAME = ResearchKitUI;
SUPPORTS_MACCATALYST = NO;
@@ -54,6 +54,36 @@
<Test
Identifier = "ORKDataCollectionTests">
</Test>
<Test
Identifier = "ORKJSONSerializationTests">
</Test>
<Test
Identifier = "ORKJSONSerializationTests/testAddResult">
</Test>
<Test
Identifier = "ORKJSONSerializationTests/testDateComponentsSerialization">
</Test>
<Test
Identifier = "ORKJSONSerializationTests/testEquality">
</Test>
<Test
Identifier = "ORKJSONSerializationTests/testInvalidDBHLValue">
</Test>
<Test
Identifier = "ORKJSONSerializationTests/testMissingDefaultValueKeyInScaleAnswerFormat">
</Test>
<Test
Identifier = "ORKJSONSerializationTests/testORKSampleDeserialization">
</Test>
<Test
Identifier = "ORKJSONSerializationTests/testORKSerialization">
</Test>
<Test
Identifier = "ORKJSONSerializationTests/testSecureCoding">
</Test>
<Test
Identifier = "ORKJSONSerializationTests/testTaskModel">
</Test>
</SkippedTests>
</TestableReference>
</Testables>
+5 -1
View File
@@ -14,7 +14,11 @@
"testTargets" : [
{
"skippedTests" : [
"ORKDataCollectionTests"
"ORKDataCollectionTests",
"ORKJSONSerializationTests\/testEquality",
"ORKJSONSerializationTests\/testORKSampleDeserialization",
"ORKJSONSerializationTests\/testORKSerialization",
"ORKJSONSerializationTests\/testSecureCoding"
],
"target" : {
"containerPath" : "container:ResearchKit.xcodeproj",
+4 -13
View File
@@ -32,11 +32,7 @@
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import <UIKit/UIKit.h>
#if TARGET_OS_IOS
#import <ResearchKit/ORKTypes.h>
@class ORKScaleAnswerFormat;
@@ -56,7 +52,6 @@
@class ORKLocationAnswerFormat;
@class ORKSESAnswerFormat;
@class ORKImageChoice;
#endif
NS_ASSUME_NONNULL_BEGIN
@@ -439,10 +434,6 @@ ORK_CLASS_AVAILABLE
@end
#pragma mark - iOS
#if TARGET_OS_IOS
@interface ORKAnswerFormat()
/// @name Factory methods
@@ -532,7 +523,7 @@ ORK_CLASS_AVAILABLE
minimumValue:(double)minimumValue
maximumValue:(double)maximumValue
defaultValue:(double)defaultValue;
#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION
#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && TARGET_OS_IOS
+ (ORKLocationAnswerFormat *)locationAnswerFormat;
#endif
@@ -1790,6 +1781,7 @@ This By default, the value of this property is `NO`.
*/
@property (copy, nullable) NSString *placeholder;
#if !TARGET_OS_WATCH
/**
The autocapitalization type that applies to the user's input.
@@ -1832,6 +1824,7 @@ This By default, the value of this property is `NO`.
If specified, overrides the default password generation rules for fields with secureTextEntry.
*/
@property (nonatomic, copy, nullable) UITextInputPasswordRules *passwordRules API_AVAILABLE(ios(12));
#endif
@end
@@ -2214,7 +2207,7 @@ ORK_CLASS_AVAILABLE
An `ORKLocationAnswerFormat` object produces an `ORKLocationQuestionResult` object.
*/
#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION
#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && TARGET_OS_IOS
ORK_CLASS_AVAILABLE
@interface ORKLocationAnswerFormat : ORKAnswerFormat
@@ -2250,6 +2243,4 @@ ORK_CLASS_AVAILABLE
@end
#endif
NS_ASSUME_NONNULL_END
+10 -26
View File
@@ -90,11 +90,11 @@ NSString *ORKQuestionTypeString(ORKQuestionType questionType) {
}
#if TARGET_OS_IOS
static NSNumberFormatterStyle ORKNumberFormattingStyleConvert(ORKNumberFormattingStyle style) {
return style == ORKNumberFormattingStylePercent ? NSNumberFormatterPercentStyle : NSNumberFormatterDecimalStyle;
}
#if TARGET_OS_IOS
@implementation ORKAnswerDefaultSource {
NSMutableDictionary *_unitsTable;
}
@@ -289,8 +289,6 @@ static NSNumberFormatterStyle ORKNumberFormattingStyleConvert(ORKNumberFormattin
@implementation ORKAnswerFormat
#if TARGET_OS_IOS
+ (ORKScaleAnswerFormat *)scaleAnswerFormatWithMaximumValue:(NSInteger)scaleMaximum
minimumValue:(NSInteger)scaleMinimum
defaultValue:(NSInteger)defaultValue
@@ -424,7 +422,7 @@ static NSNumberFormatterStyle ORKNumberFormattingStyleConvert(ORKNumberFormattin
calendar:calendar];
[answerFormat setDaysBeforeCurrentDateToSetMinimumDate:daysBefore];
[answerFormat setDaysAfterCurrentDateToSetMinimumDate:daysAfter];
return answerFormat;
}
@@ -489,7 +487,7 @@ static NSNumberFormatterStyle ORKNumberFormattingStyleConvert(ORKNumberFormattin
defaultValue:defaultValue];
}
#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION
#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && TARGET_OS_IOS
+ (ORKLocationAnswerFormat *)locationAnswerFormat {
return [ORKLocationAnswerFormat new];
}
@@ -501,8 +499,6 @@ static NSNumberFormatterStyle ORKNumberFormattingStyleConvert(ORKNumberFormattin
bottomRungText:bottomRungText];
}
#endif
+ (ORKBooleanAnswerFormat *)booleanAnswerFormat {
return [ORKBooleanAnswerFormat new];
}
@@ -688,7 +684,6 @@ static NSArray *ork_processTextChoices(NSArray<ORKTextChoice *> *textChoices) {
return choices;
}
#if TARGET_OS_IOS
@implementation ORKValuePickerAnswerFormat {
ORKChoiceAnswerFormatHelper *_helper;
ORKTextChoice *_nullTextChoice;
@@ -841,7 +836,7 @@ static NSArray *ork_processTextChoices(NSArray<ORKTextChoice *> *textChoices) {
- (BOOL)isEqual:(id)object {
BOOL isParentSame = [super isEqual:object];
__typeof(self) castObject = object;
return (isParentSame &&
ORKEqualObjects(self.valuePickers, castObject.valuePickers));
@@ -890,7 +885,7 @@ static NSArray *ork_processTextChoices(NSArray<ORKTextChoice *> *textChoices) {
if (![answer isKindOfClass:[NSArray class]] || ([(NSArray*)answer count] != self.valuePickers.count)) {
return nil;
}
NSArray *answers = (NSArray*)answer;
__block NSMutableArray <NSString *> *answerTexts = [NSMutableArray new];
[answers enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
@@ -901,11 +896,11 @@ static NSArray *ork_processTextChoices(NSArray<ORKTextChoice *> *textChoices) {
*stop = YES;
}
}];
if (answerTexts.count != self.valuePickers.count) {
return nil;
}
return [answerTexts componentsJoinedByString:self.separator];
}
@@ -1027,8 +1022,6 @@ static NSArray *ork_processTextChoices(NSArray<ORKTextChoice *> *textChoices) {
}
@end
#endif
#pragma mark - ORKTextChoiceAnswerFormat
@@ -1488,7 +1481,6 @@ NSArray<Class> *ORKAllowableValueClasses(void) {
#pragma mark - ORKTextChoiceOther
#if TARGET_OS_IOS
@implementation ORKTextChoiceOther
+ (instancetype)new {
@@ -1712,7 +1704,6 @@ NSArray<Class> *ORKAllowableValueClasses(void) {
}
@end
#endif
#pragma mark - ORKBooleanAnswerFormat
@@ -1800,7 +1791,7 @@ NSArray<Class> *ORKAllowableValueClasses(void) {
#pragma mark - ORKTimeOfDayAnswerFormat
#if TARGET_OS_IOS
@implementation ORKTimeOfDayAnswerFormat
- (instancetype)init {
@@ -2827,7 +2818,7 @@ NSArray<Class> *ORKAllowableValueClasses(void) {
#pragma mark - ORKTextScaleAnswerFormat
#if TARGET_OS_IOS
@interface ORKTextScaleAnswerFormat () {
ORKChoiceAnswerFormatHelper *_helper;
@@ -3038,19 +3029,15 @@ NSArray<Class> *ORKAllowableValueClasses(void) {
@end
#endif
#pragma mark - ORKTextAnswerFormat
@interface ORKTextAnswerFormat()
@end
@implementation ORKTextAnswerFormat
- (Class)questionResultClass {
return [ORKTextQuestionResult class];
}
@@ -3069,7 +3056,6 @@ NSArray<Class> *ORKAllowableValueClasses(void) {
}
- (instancetype)initWithMaximumLength:(NSInteger)maximumLength {
self = [super init];
if (self) {
@@ -3321,7 +3307,6 @@ static NSString *const kSecureTextEntryEscapeString = @"*";
return answerString;
}
- (ORKQuestionResult *)resultWithIdentifier:(NSString *)identifier answer:(id)answer {
ORKQuestionResult *questionResult = nil;
questionResult = (ORKQuestionResult *)[super resultWithIdentifier:identifier answer:answer];
@@ -4019,7 +4004,7 @@ static const NSInteger ORKAgeAnswerDefaultMaxAge = 125;
@end
#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION
#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && TARGET_OS_IOS
#pragma mark - ORKLocationAnswerFormat
@implementation ORKLocationAnswerFormat
@@ -4153,4 +4138,3 @@ static const NSInteger ORKAgeAnswerDefaultMaxAge = 125;
}
@end
#endif
@@ -33,10 +33,9 @@
#import <HealthKit/HealthKit.h>
#endif
#if TARGET_OS_IOS
#import <ResearchKit/ORKAnswerFormat_Private.h>
#import <ResearchKit/ORKChoiceAnswerFormatHelper.h>
#endif
@class ORKChoiceAnswerFormatHelper;
NS_ASSUME_NONNULL_BEGIN
@@ -58,7 +57,7 @@ NSString *ORKQuestionTypeString(ORKQuestionType questionType);
@end
ORK_DESIGNATE_CODING_AND_SERIALIZATION_INITIALIZERS(ORKAnswerFormat)
#if TARGET_OS_IOS
#if TARGET_OS_IOS || TARGET_OS_VISION
ORK_DESIGNATE_CODING_AND_SERIALIZATION_INITIALIZERS(ORKImageChoiceAnswerFormat)
ORK_DESIGNATE_CODING_AND_SERIALIZATION_INITIALIZERS(ORKValuePickerAnswerFormat)
ORK_DESIGNATE_CODING_AND_SERIALIZATION_INITIALIZERS(ORKMultipleValuePickerAnswerFormat)
@@ -135,6 +134,11 @@ ORK_DESIGNATE_CODING_AND_SERIALIZATION_INITIALIZERS(ORKTextChoice)
@end
@interface ORKDateAnswerFormat () {
NSDate *_currentDateOverride;
}
@end
#if TARGET_OS_IOS
@protocol ORKScaleAnswerFormatProvider <NSObject>
@@ -198,6 +202,7 @@ NSArray<Class> *ORKAllowableValueClasses(void);
@end
#if TARGET_OS_IOS
@interface ORKValuePickerAnswerFormat ()
- (instancetype)initWithTextChoices:(NSArray<ORKTextChoice *> *)textChoices nullChoice:(ORKTextChoice *)nullChoice NS_DESIGNATED_INITIALIZER;
@@ -206,7 +211,6 @@ NSArray<Class> *ORKAllowableValueClasses(void);
@end
@interface ORKImageChoice () <ORKAnswerOption>
@end
@@ -218,11 +222,7 @@ NSArray<Class> *ORKAllowableValueClasses(void);
@end
@interface ORKDateAnswerFormat () {
NSDate *_currentDateOverride;
}
@interface ORKDateAnswerFormat ()
- (NSDate *)pickerDefaultDate;
- (nullable NSDate *)pickerMinimumDate;
- (nullable NSDate *)pickerMaximumDate;
@@ -28,12 +28,7 @@
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#if TARGET_OS_IOS
#import <ResearchKit/ORKAnswerFormat.h>
#endif
NS_ASSUME_NONNULL_BEGIN
@@ -60,7 +55,6 @@ ORK_EXTERN id ORKNullAnswerValue(void) ORK_AVAILABLE_DECL;
An `ORKConfirmTextAnswerFormat` object produces an `ORKBooleanQuestionResult` object.
*/
#if TARGET_OS_IOS
ORK_CLASS_AVAILABLE
@interface ORKConfirmTextAnswerFormat : ORKTextAnswerFormat
@@ -95,7 +89,6 @@ ORK_CLASS_AVAILABLE
@property (nonatomic, copy, readonly) NSString *errorMessage;
@end
#endif
@protocol ORKAnswerFormatPlatterPresentable <NSObject>
+6 -1
View File
@@ -30,16 +30,20 @@
#import "ORKBodyItem.h"
#import "ORKBodyItem_Internal.h"
#import "ORKLearnMoreInstructionStep.h"
#import "ORKHelpers_Internal.h"
#if !TARGET_OS_WATCH
#import "ORKBodyItem_Internal.h"
#endif
@implementation ORKBodyItem
{
// For our internal custom button type
BOOL _isCustomButtonType;
}
#if !TARGET_OS_WATCH
- (instancetype)initWithCustomButtonConfigurationHandler:(void(^)(UIButton *button))configurationHandler
{
self = [super init];
@@ -50,6 +54,7 @@
}
return self;
}
#endif
- (BOOL)isCustomButtonItemType
{
@@ -34,9 +34,11 @@ NS_ASSUME_NONNULL_BEGIN
@interface ORKBodyItem ()
#if !TARGET_OS_WATCH
@property (nonatomic, copy, nullable) void (^customButtonConfigurationHandler)(UIButton *button);
- (instancetype)initWithCustomButtonConfigurationHandler:(void(^)(UIButton *button))configurationHandler;
#endif
- (BOOL)isCustomButtonItemType;
-3
View File
@@ -28,10 +28,7 @@
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#if TARGET_OS_IOS
#import <ResearchKit/ORKResult.h>
#endif
NS_ASSUME_NONNULL_BEGIN
@@ -29,11 +29,7 @@
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#if TARGET_OS_IOS
#import <ResearchKit/ORKCollectionResult.h>
#endif
NS_ASSUME_NONNULL_BEGIN
+1 -2
View File
@@ -30,10 +30,9 @@
#import <Foundation/Foundation.h>
#import <ResearchKit/ORKInstructionStep.h>
#if TARGET_OS_IOS
#import <ResearchKit/ORKInstructionStep.h>
#import <ResearchKit/ORKTask.h>
#endif
@@ -28,6 +28,7 @@
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#if !TARGET_OS_VISION
#import <Foundation/Foundation.h>
#import <ResearchKit/ResearchKit.h>
@@ -200,3 +201,5 @@ ORK_CLASS_AVAILABLE
@end
NS_ASSUME_NONNULL_END
#endif
-3
View File
@@ -29,10 +29,7 @@
*/
#import <Foundation/Foundation.h>
#if TARGET_OS_IOS
#import <ResearchKit/ORKDefines.h>
#endif
NS_ASSUME_NONNULL_BEGIN
-3
View File
@@ -28,10 +28,7 @@
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#if TARGET_OS_IOS
#import <ResearchKit/ORKDevice.h>
#endif
NS_ASSUME_NONNULL_BEGIN
@@ -29,11 +29,7 @@
*/
#import <Foundation/Foundation.h>
#if TARGET_OS_IOS
#import <ResearchKit/ORKDefines.h>
#endif
NS_ASSUME_NONNULL_BEGIN
-5
View File
@@ -30,12 +30,7 @@
#import <Foundation/Foundation.h>
#if TARGET_OS_IOS
#import <ResearchKit/ORKDefines.h>
#endif
NS_ASSUME_NONNULL_BEGIN
+1 -5
View File
@@ -30,12 +30,8 @@
#import <Foundation/Foundation.h>
#if TARGET_OS_IOS
#import <ResearchKit/ORKStep.h>
#import <ResearchKit/ORKDefines.h>
#endif
#import <ResearchKit/ORKStep.h>
/**
Values that determine the style
+14 -1
View File
@@ -39,7 +39,10 @@
#import "ORKStep_Private.h"
#import "ORKHelpers_Internal.h"
#if !TARGET_OS_WATCH
#import "ORKFormItemVisibilityRule.h"
#endif
@implementation ORKFormStep
@@ -182,7 +185,7 @@
}
}
#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION
#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION && TARGET_OS_IOS
- (NSSet<HKObjectType *> *)requestedHealthKitTypesForReading {
NSMutableSet<HKObjectType *> *healthTypes = [NSMutableSet set];
@@ -262,6 +265,7 @@
return self;
}
#if TARGET_OS_IOS
- (ORKFormItem *)confirmationAnswerFormItemWithIdentifier:(NSString *)identifier
text:(nullable NSString *)text
errorMessage:(NSString *)errorMessage {
@@ -281,6 +285,7 @@
optional:self.optional];
return item;
}
#endif
+ (BOOL)supportsSecureCoding {
return YES;
@@ -294,7 +299,9 @@
item->_learnMoreItem = [_learnMoreItem copy];
item->_showsProgress = _showsProgress;
item->_tagText = [_tagText copy];
#if !TARGET_OS_WATCH
item->_visibilityRule = [_visibilityRule copy];
#endif
return item;
}
@@ -311,7 +318,9 @@
ORK_DECODE_OBJ_CLASS(aDecoder, answerFormat, ORKAnswerFormat);
ORK_DECODE_OBJ_CLASS(aDecoder, step, ORKFormStep);
ORK_DECODE_OBJ_CLASS(aDecoder, tagText, NSString);
#if !TARGET_OS_WATCH
ORK_DECODE_OBJ_CLASS(aDecoder, visibilityRule, ORKFormItemVisibilityRule);
#endif
}
return self;
}
@@ -350,8 +359,12 @@
}
- (NSUInteger)hash {
#if !TARGET_OS_WATCH
// Ignore the step reference - it's not part of the content of this item
return _identifier.hash ^ _text.hash ^ _placeholder.hash ^ _answerFormat.hash ^ (_optional ? 0xf : 0x0) ^ _detailText.hash ^ _learnMoreItem.hash ^ (_showsProgress ? 0xf : 0x0) ^ _tagText.hash ^ _visibilityRule.hash;
#else
return _identifier.hash ^ _text.hash ^ _placeholder.hash ^ _answerFormat.hash ^ (_optional ? 0xf : 0x0) ^ _detailText.hash ^ _learnMoreItem.hash ^ (_showsProgress ? 0xf : 0x0) ^ _tagText.hash;
#endif
}
- (ORKAnswerFormat *)impliedAnswerFormat {
+5 -4
View File
@@ -31,7 +31,7 @@
#import <ResearchKit/ORKAnswerFormat.h>
#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION
#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION && TARGET_OS_IOS
#import <HealthKit/HealthKit.h>
#endif
@@ -74,7 +74,7 @@ ORK_EXTERN ORKBloodTypeIdentifier const ORKBloodTypeIdentifierONegative;
You can use the HealthKit characteristic answer format to let users autofill information, such as their blood type or date of birth.
*/
#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION
#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION && TARGET_OS_IOS
ORK_CLASS_AVAILABLE
@interface ORKHealthKitCharacteristicTypeAnswerFormat : ORKAnswerFormat
@@ -226,6 +226,7 @@ included in the question result generated by form items or question steps
- (NSString *)localizedUnitString;
@end
#endif
NS_ASSUME_NONNULL_END
#endif
NS_ASSUME_NONNULL_END
+4 -10
View File
@@ -29,19 +29,13 @@
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import <UIKit/UIKit.h>
#if TARGET_OS_IOS
#import <ResearchKit/ORKTypes.h>
#import <ResearchKit/ORKHelpers_Private.h>
#import <ResearchKit/ORKErrors.h>
#endif
#import <Foundation/Foundation.h>
#import <os/log.h>
#import <UIKit/UIKit.h>
#import <ResearchKit/ORKErrors.h>
#import <ResearchKit/ORKHelpers_Private.h>
#import <ResearchKit/ORKTypes.h>
NS_ASSUME_NONNULL_BEGIN
-4
View File
@@ -30,11 +30,7 @@
#import <Foundation/Foundation.h>
#if TARGET_OS_IOS
#import <ResearchKit/ORKDefines.h>
#endif
NS_ASSUME_NONNULL_BEGIN
-6
View File
@@ -28,14 +28,8 @@
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import <UIKit/UIKit.h>
#if TARGET_OS_IOS
#import <ResearchKit/ORKStep.h>
#endif
NS_ASSUME_NONNULL_BEGIN
-5
View File
@@ -30,12 +30,7 @@
#import <UIKit/UIKit.h>
#if TARGET_OS_IOS
#import <ResearchKit/ORKTask.h>
#endif
NS_ASSUME_NONNULL_BEGIN
+10 -16
View File
@@ -30,16 +30,19 @@
*/
#import "ORKOrderedTask.h"
#import "ORKAnswerFormat.h"
#import "ORKInstructionStep.h"
#import "ORKCompletionStep.h"
#import "ORKStep_Private.h"
#import "ORKHelpers_Internal.h"
#import "ORKSkin.h"
#if TARGET_OS_IOS
#import "ORKQuestionStep.h"
#import "ORKFormStep.h"
#import "ORKFormItem_Internal.h"
#import "ORKHelpers_Internal.h"
#import "ORKInstructionStep.h"
#import "ORKQuestionStep.h"
#import "ORKSkin.h"
#import "ORKStep_Private.h"
#if TARGET_OS_IOS
#import "ORKActiveStep_Internal.h"
#import "ORKEarlyTerminationConfiguration.h"
#endif
@@ -263,7 +266,6 @@
int currentStepStartingProgressNumber = 0;
for (ORKStep *step in self.steps) {
#if TARGET_OS_IOS
if ([step isKindOfClass:[ORKFormStep class]]) {
ORKFormStep *formStep = (ORKFormStep *)step;
if (formStep.identifier == currentStep.identifier) {
@@ -277,14 +279,6 @@
}
totalQuestions += 1;
}
#else
if ([step isKindOfClass:[ORKQuestionStep class]]) {
if (step.identifier == currentStep.identifier) {
currentStepStartingProgressNumber = (totalQuestions + 1);
}
totalQuestions += 1;
}
#endif
}
totalProgress.currentStepStartingProgressPosition = currentStepStartingProgressNumber;
@@ -293,7 +287,6 @@
return totalProgress;
}
#if TARGET_OS_IOS
- (NSMutableArray *)calculateSectionsForFormItems:(NSArray *)formItems {
NSMutableArray<NSMutableArray *> *_sections = [NSMutableArray new];
NSMutableArray *section = nil;
@@ -374,6 +367,7 @@
return NO;
}
#if TARGET_OS_IOS
- (BOOL)providesBackgroundAudioPrompts {
BOOL providesAudioPrompts = NO;
for (ORKStep *step in self.steps) {
-4
View File
@@ -28,12 +28,8 @@
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#if TARGET_OS_IOS
#import <ResearchKit/ORKStep.h>
#import <ResearchKit/ORKOrderedTask.h>
#endif
NS_ASSUME_NONNULL_BEGIN
+1 -1
View File
@@ -32,6 +32,7 @@
#import <UIKit/UIKit.h>
#import <ResearchKit/ORKDefines.h>
#import <UserNotifications/UserNotifications.h>
NS_ASSUME_NONNULL_BEGIN
@@ -49,7 +50,6 @@ typedef NS_ENUM(NSInteger, ORKRequestPermissionsState) {
ORKRequestPermissionsStateError,
};
typedef NS_OPTIONS(NSUInteger, UNAuthorizationOptions);
typedef NSString * SRSensor NS_TYPED_ENUM API_AVAILABLE(ios(14.0));
/**
+7 -10
View File
@@ -28,17 +28,13 @@
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#if TARGET_OS_IOS
#import <Contacts/Contacts.h>
#import <ResearchKit/ORKResult.h>
#endif
#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION
#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && TARGET_OS_IOS
#import <CoreLocation/CLLocation.h>
#endif
#import <Contacts/Contacts.h>
NS_ASSUME_NONNULL_BEGIN
@class CLCircularRegion;
@@ -153,7 +149,7 @@ ORK_CLASS_AVAILABLE
@end
#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION
#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && TARGET_OS_IOS
/**
The `ORKLocation` class represents the location addess obtained from a location question.
*/
@@ -184,7 +180,7 @@ ORK_CLASS_AVAILABLE
@property (nonatomic, copy, readonly, nullable) CNPostalAddress *postalAddress;
@end
#endif
#endif
/**
A result object from a location answer format.
@@ -196,7 +192,8 @@ ORK_CLASS_AVAILABLE
completes, it may be appropriate to serialize it for transmission to a server,
or to immediately perform analysis on it.
*/
#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION
#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && TARGET_OS_IOS
ORK_CLASS_AVAILABLE
@interface ORKLocationQuestionResult : ORKQuestionResult
@@ -206,7 +203,7 @@ ORK_CLASS_AVAILABLE
@property (nonatomic, copy, nullable) ORKLocation *locationAnswer;
@end
#endif
#endif
/**
A result object from a multiple-component picker-style choice-based answer format.
+12 -10
View File
@@ -28,15 +28,18 @@
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import <ResearchKit/ORKAnswerFormat_Internal.h>
#import <ResearchKit/ORKAnswerFormat_Private.h>
#import <ResearchKit/ORKHelpers_Internal.h>
#import <ResearchKit/ORKQuestionResult.h>
#import <ResearchKit/ORKQuestionStep.h>
#import <ResearchKit/ORKQuestionStep_Private.h>
#import <ResearchKit/ORKResult_Private.h>
#import <ResearchKit/ORKStep_Private.h>
#import "ORKQuestionResult_Private.h"
#import "ORKResult_Private.h"
#if TARGET_OS_IOS
#import "ORKQuestionStep.h"
#import "ORKHelpers_Internal.h"
#import "ORKAnswerFormat_Internal.h"
#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && TARGET_OS_IOS
#import <CoreLocation/CoreLocation.h>
#import <MapKit/MapKit.h>
#endif
@implementation ORKQuestionResult {
@@ -326,7 +329,7 @@
@end
#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION
#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && TARGET_OS_IOS
#pragma mark - ORKLocationQuestionResult
@implementation ORKLocation
@@ -468,7 +471,6 @@ static NSString *const RegionIdentifierKey = @"region.identifier";
}
@end
#endif
#pragma mark - ORKSESQuestionResult
@@ -28,15 +28,10 @@
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#if TARGET_OS_IOS
#import <ResearchKit/ORKQuestionResult.h>
#endif
#import <MapKit/MapKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface ORKQuestionResult ()
@@ -49,9 +44,10 @@ NS_ASSUME_NONNULL_BEGIN
@end
#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION
#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && TARGET_OS_IOS
@interface ORKLocation ()
- (instancetype)initWithCoordinate:(CLLocationCoordinate2D)coordinate
region:(nullable CLCircularRegion *)region
userInput:(nullable NSString *)userInput
@@ -60,5 +56,6 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)initWithPlacemark:(CLPlacemark *)placemark userInput:(NSString *)userInput;
@end
#endif
#endif
NS_ASSUME_NONNULL_END
-4
View File
@@ -29,11 +29,7 @@
*/
#import <Foundation/Foundation.h>
#if TARGET_OS_IOS
#import <ResearchKit/ORKTypes.h>
#endif
NS_ASSUME_NONNULL_BEGIN
-3
View File
@@ -28,10 +28,7 @@
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#if TARGET_OS_IOS
#import <ResearchKit/ORKResult.h>
#endif
NS_ASSUME_NONNULL_BEGIN
-4
View File
@@ -28,12 +28,8 @@
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import <UIKit/UIKit.h>
#if TARGET_OS_IOS
#import <ResearchKit/ORKDefines.h>
#endif
NS_ASSUME_NONNULL_BEGIN
-4
View File
@@ -30,11 +30,7 @@
*/
#import <UIKit/UIKit.h>
#if TARGET_OS_IOS
#import <ResearchKit/ORKSkin.h>
#endif
#if TARGET_OS_IOS
+5 -6
View File
@@ -28,13 +28,9 @@
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#if TARGET_OS_IOS
#import <ResearchKit/ORKTypes.h>
#endif
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <ResearchKit/ORKTypes.h>
@class HKObjectType;
@class ORKResult;
@@ -252,7 +248,7 @@ ORK_CLASS_AVAILABLE API_AVAILABLE(ios(11.0), watchos(6.0))
#pragma mark - iOS
#if TARGET_OS_IOS
#if TARGET_OS_IOS || TARGET_OS_VISION
@class ORKBodyItem;
@@ -313,4 +309,7 @@ API_AVAILABLE(ios(11))
#endif
#pragma mark - watchOS / VisionOS
NS_ASSUME_NONNULL_END
-3
View File
@@ -28,10 +28,7 @@
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#if TARGET_OS_IOS
#import <ResearchKit/ORKStep.h>
#endif
NS_ASSUME_NONNULL_BEGIN
-4
View File
@@ -35,11 +35,7 @@
#import <HealthKit/HealthKit.h>
#endif
#if TARGET_OS_IOS
#import <ResearchKit/ORKTypes.h>
#endif
NS_ASSUME_NONNULL_BEGIN
-4
View File
@@ -30,11 +30,7 @@
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#if TARGET_OS_IOS
#import <ResearchKit/ORKDefines.h>
#endif
NS_ASSUME_NONNULL_BEGIN
+2
View File
@@ -87,12 +87,14 @@ ORK_CLASS_AVAILABLE
*/
@property (nonatomic, getter=isAudioMute) BOOL audioMute;
#if !TARGET_OS_VISION
/**
Constants indicating the desired torch mode to use
The default value is `AVCaptureTorchModeAuto` (see `AVCaptureTorchMode`).
*/
@property (nonatomic) AVCaptureTorchMode torchMode;
#endif
/**
Constants indicating the physical position of an AVCaptureDevice's hardware on the system.
+2
View File
@@ -63,8 +63,10 @@ NS_ASSUME_NONNULL_BEGIN
@optional
- (UIView<ORKCustomSignatureAccessoryViewProtocol> * _Nullable)customHeaderViewForSignatureContent;
#if !TARGET_OS_VISION
@optional
- (UIScrollViewKeyboardDismissMode)keyboardDismissModeForCustomView;
#endif
@end
+27 -26
View File
@@ -29,26 +29,42 @@
*/
#import <ResearchKit/ORKDefines.h>
#import <ResearchKit/ORKTypes.h>
#import <ResearchKit/ORKStep.h>
#import <ResearchKit/ORKActiveStep.h>
#import <ResearchKit/ORKAnswerFormat.h>
#import <ResearchKit/ORKCollectionResult.h>
#import <ResearchKit/ORKCompletionStep.h>
#import <ResearchKit/ORKFormItemVisibilityRule.h>
#import <ResearchKit/ORKFormStep.h>
#import <ResearchKit/ORKInstructionStep.h>
#import <ResearchKit/ORKLearnMoreInstructionStep.h>
#import <ResearchKit/ORKLearnMoreItem.h>
#import <ResearchKit/ORKOrderedTask.h>
#import <ResearchKit/ORKQuestionResult.h>
#import <ResearchKit/ORKQuestionStep.h>
#import <ResearchKit/ORKResult.h>
#import <ResearchKit/ORKSkin.h>
#import <ResearchKit/ORKStep.h>
#import <ResearchKit/ORKTask.h>
#import <ResearchKit/ORKTypes.h>
#import <ResearchKit/ORKResultPredicate.h>
#import <ResearchKit/ORKBodyItem.h>
#import <ResearchKit/ORKEarlyTerminationConfiguration.h>
#import <ResearchKit/ORKNavigableOrderedTask.h>
#import <ResearchKit/ORKStepNavigationRule.h>
// Import these files for iOS only.
#if !TARGET_OS_WATCH && !TARGET_OS_VISION
#import <ResearchKit/ORKDefines.h>
#import <ResearchKit/ORKActiveStep.h>
#import <ResearchKit/ORKConsentReviewStep.h>
#import <ResearchKit/ORKConsentSharingStep.h>
#import <ResearchKit/ORKFormStep.h>
#import <ResearchKit/ORKFormItemVisibilityRule.h>
#import <ResearchKit/ORKHTMLPDFWriter.h>
#import <ResearchKit/ORKImageCaptureStep.h>
#import <ResearchKit/ORKInstructionStep.h>
#import <ResearchKit/ORKLoginStep.h>
#import <ResearchKit/ORKNavigablePageStep.h>
#import <ResearchKit/ORKPageStep.h>
#import <ResearchKit/ORKPasscodeStep.h>
#import <ResearchKit/ORKPDFViewerStep.h>
#import <ResearchKit/ORKPredicateFormItemVisibilityRule.h>
#import <ResearchKit/ORKQuestionStep.h>
#import <ResearchKit/ORKRegistrationStep.h>
#import <ResearchKit/ORKReviewStep.h>
#import <ResearchKit/ORKSignatureFormatter.h>
@@ -61,24 +77,15 @@
#import <ResearchKit/ORKFrontFacingCameraStep.h>
#import <ResearchKit/ORKWebViewStep.h>
#import <ResearchKit/ORKCustomStep.h>
#import <ResearchKit/ORKLearnMoreInstructionStep.h>
#import <ResearchKit/ORKSecondaryTaskStep.h>
#import <ResearchKit/ORKRequestPermissionsStep.h>
#import <ResearchKit/ORKTask.h>
#import <ResearchKit/ORKOrderedTask.h>
#import <ResearchKit/ORKNavigableOrderedTask.h>
#import <ResearchKit/ORKStepNavigationRule.h>
#import <ResearchKit/ORKAnswerFormat.h>
#import <ResearchKit/ORKHealthAnswerFormat.h>
#import <ResearchKit/ORKResult.h>
#import <ResearchKit/ORKCollectionResult.h>
#import <ResearchKit/ORKConsentSignatureResult.h>
#import <ResearchKit/ORKFrontFacingCameraStepResult.h>
#import <ResearchKit/ORKPasscodeResult.h>
#import <ResearchKit/ORKQuestionResult.h>
#import <ResearchKit/ORKSignatureResult.h>
#import <ResearchKit/ORKVideoInstructionStepResult.h>
#import <ResearchKit/ORKWebViewStepResult.h>
@@ -92,10 +99,6 @@
#import <ResearchKit/ORKConsentSignature.h>
#import <ResearchKit/ORKKeychainWrapper.h>
#import <ResearchKit/ORKBodyItem.h>
#import <ResearchKit/ORKLearnMoreItem.h>
#import <ResearchKit/ORKDataCollectionManager.h>
#import <ResearchKit/ORKCollector.h>
@@ -107,11 +110,7 @@
#import <ResearchKit/ORKLocationPermissionType.h>
#import <ResearchKit/ORKDeprecated.h>
#import <ResearchKit/ORKEarlyTerminationConfiguration.h>
#import <ResearchKit/ORKBundleAsset.h>
#import <ResearchKit/ORKSkin.h>
#import <ResearchKit/HKSample+ORKJSONDictionary.h>
#import <ResearchKit/CMMotionActivity+ORKJSONDictionary.h>
@@ -122,3 +121,5 @@
#import <ResearchKit/ORKHealthCondition.h>
#import <ResearchKit/ORKRelatedPerson.h>
#import <ResearchKit/ORKRelativeGroup.h>
#endif
+15 -10
View File
@@ -28,30 +28,35 @@
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import <ResearchKit/CLLocationManager+ResearchKit.h>
#import <ResearchKit/ORKActiveStep_Internal.h>
#import <ResearchKit/ORKAnswerFormat_Internal.h>
#import <ResearchKit/ORKAnswerFormat_Private.h>
#import <ResearchKit/ORKBodyItem_Internal.h>
#import <ResearchKit/ORKChoiceAnswerFormatHelper.h>
#import <ResearchKit/ORKCollectionResult_Private.h>
#import <ResearchKit/ORKErrors.h>
#import <ResearchKit/ORKHelpers_Internal.h>
#import <ResearchKit/ORKHelpers_Private.h>
#import <ResearchKit/ORKQuestionResult_Private.h>
#import <ResearchKit/ORKQuestionStep_Private.h>
#import <ResearchKit/ORKResult_Private.h>
#import <ResearchKit/ORKStep_Private.h>
#import <ResearchKit/ORKDevice.h>
#import <ResearchKit/ORKStepNavigationRule_Private.h>
#if !TARGET_OS_WATCH && !TARGET_OS_VISION
#import <ResearchKit/CLLocationManager+ResearchKit.h>
#import <ResearchKit/ORKActiveStep_Internal.h>
#import <ResearchKit/ORKConsentDocument_Private.h>
#import <ResearchKit/ORKConsentSection_Private.h>
#import <ResearchKit/ORKDataLogger.h>
#import <ResearchKit/ORKDevice_Private.h>
#import <ResearchKit/ORKErrors.h>
#import <ResearchKit/ORKHelpers_Internal.h>
#import <ResearchKit/ORKHelpers_Private.h>
#import <ResearchKit/ORKOrderedTask_Private.h>
#import <ResearchKit/ORKPageStep_Private.h>
#import <ResearchKit/ORKPredicateFormItemVisibilityRule_Private.h>
#import <ResearchKit/ORKQuestionResult_Private.h>
#import <ResearchKit/ORKQuestionStep_Private.h>
#import <ResearchKit/ORKRecorder_Private.h>
#import <ResearchKit/ORKResult_Private.h>
#import <ResearchKit/ORKSignatureResult_Private.h>
#import <ResearchKit/ORKSkin_Private.h>
#import <ResearchKit/ORKStepNavigationRule_Private.h>
#import <ResearchKit/ORKStep_Private.h>
#import <ResearchKit/ORKTypes_Private.h>
#import <ResearchKit/ORKWebViewStepResult_Private.h>
#endif
-4
View File
@@ -30,11 +30,7 @@
#import <Foundation/Foundation.h>
#if TARGET_OS_IOS
#import <ResearchKit/ORKStep.h>
#endif
NS_ASSUME_NONNULL_BEGIN
+6 -6
View File
@@ -28,15 +28,15 @@
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import <ResearchKit/ORKAnswerFormat_Internal.h>
#import <ResearchKit/ORKAnswerFormat_Private.h>
#import <ResearchKit/ORKHelpers_Internal.h>
#import <ResearchKit/ORKQuestionStep_Private.h>
#import <ResearchKit/ORKQuestionStep.h>
#import <ResearchKit/ORKStep_Private.h>
#if TARGET_OS_IOS
#import "ORKAnswerFormat_Internal.h"
#import "ORKAnswerFormat_Private.h"
#import "ORKHelpers_Internal.h"
#import "ORKLearnMoreItem.h"
#import "ORKQuestionStep_Private.h"
#import "ORKStep_Private.h"
#import "ORKQuestionStep.h"
#endif
#if TARGET_OS_IOS
+1 -5
View File
@@ -28,12 +28,8 @@
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#if TARGET_OS_IOS
#import <ResearchKit/ORKQuestionStep.h>
#import <ResearchKit/ORKAnswerFormat_Private.h>
#endif
#import <ResearchKit/ORKQuestionStep.h>
NS_ASSUME_NONNULL_BEGIN
@@ -0,0 +1,851 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 70;
objects = {
/* Begin PBXBuildFile section */
0BF86B222B4F360000E3790E /* ResearchKitSwiftUI.docc in Sources */ = {isa = PBXBuildFile; fileRef = 0BF86B212B4F360000E3790E /* ResearchKitSwiftUI.docc */; };
380E14B02C3350A900085D2B /* ResearchForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 380E14AF2C3350A900085D2B /* ResearchForm.swift */; };
3815C0142C2081CE00A29CD3 /* DateTimeQuestion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3815C0132C2081CE00A29CD3 /* DateTimeQuestion.swift */; };
381CAC0A2C35E06500FCE89A /* ResearchFormStepContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 381CAC092C35E05A00FCE89A /* ResearchFormStepContentView.swift */; };
381CAC0C2C35F4CE00FCE89A /* ResearchFormCompletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 381CAC0B2C35F4C300FCE89A /* ResearchFormCompletion.swift */; };
381CAC102C3C759200FCE89A /* HeightQuestion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 381CAC0F2C3C759200FCE89A /* HeightQuestion.swift */; };
3820BE212C6574C00012CB2C /* ResearchFormResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3820BE1F2C6574350012CB2C /* ResearchFormResult.swift */; };
3820BE222C6574C50012CB2C /* StepResultKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3820BE202C6574640012CB2C /* StepResultKey.swift */; };
382CF7852C3F2632000F3433 /* WeightQuestion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 382CF7842C3F262F000F3433 /* WeightQuestion.swift */; };
3858D2662C90EF1700F334EB /* ResultValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3858D2652C90EF1500F334EB /* ResultValue.swift */; };
389694182C6E7622007BA17D /* MeasurementUtility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 389694172C6E7621007BA17D /* MeasurementUtility.swift */; };
38CD26102C1909CC000FDBB0 /* Question.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38CD260F2C1909CC000FDBB0 /* Question.swift */; };
38CD263B2C1BA6EA000FDBB0 /* TextQuestion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38CD263A2C1BA6EA000FDBB0 /* TextQuestion.swift */; };
38DB82E32C753A8B0074B95D /* ResearchKitSwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0BF86B1D2B4F360000E3790E /* ResearchKitSwiftUI.framework */; platformFilters = (ios, xros, ); };
38F4F6C62C4AD35C004E7A8D /* ImageChoiceQuestion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38F4F6C52C4AD35C004E7A8D /* ImageChoiceQuestion.swift */; };
5E05B53D2C2E135D00A0A83B /* DoneKeyboardToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E05B53C2C2E135D00A0A83B /* DoneKeyboardToolbar.swift */; };
5E44D4482C24A6FF0045007E /* NumericQuestion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E44D4472C24A6FF0045007E /* NumericQuestion.swift */; };
5E507F812C1141F2002958E2 /* SliderValueForegroundStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E507F802C1141F2002958E2 /* SliderValueForegroundStyle.swift */; };
5EA939252CD18B01001060CD /* ResearchFormStep.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EA939242CD18AFB001060CD /* ResearchFormStep.swift */; };
5EAFC71C2C7549BD008560D7 /* NavigationalLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EAFC71B2C7549B9008560D7 /* NavigationalLayout.swift */; };
5EBAA28D2C40634800F193C6 /* BodyItemIconForegroundStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EBAA28C2C40634800F193C6 /* BodyItemIconForegroundStyle.swift */; };
5EBAA28F2C4063C100F193C6 /* StepIconForegroundStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EBAA28E2C4063C100F193C6 /* StepIconForegroundStyle.swift */; };
5ED16E272C5AF7E50072016D /* InstructionBodyItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ED16E262C5AF7DF0072016D /* InstructionBodyItem.swift */; };
5ED33A372CAE1EB900926DA3 /* TextChoice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ED33A362CAE1EB600926DA3 /* TextChoice.swift */; };
5EDFA9D72BFFFA2B0044C156 /* StepHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EDFA9D62BFFFA2B0044C156 /* StepHeader.swift */; };
5EE5FD312C6A8486004BBABF /* StateManagementType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EE5FD302C6A8482004BBABF /* StateManagementType.swift */; };
5EFC55352C9B9C4D002C9587 /* QuestionCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EFC55342C9B9C4D002C9587 /* QuestionCard.swift */; };
5EFC55742C9DEFC0002C9587 /* KeyboardUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EFC55732C9DEF9E002C9587 /* KeyboardUtilities.swift */; };
69EE51532C77F1B8008B2AD6 /* QuestionRequired.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69EE51522C77F1B8008B2AD6 /* QuestionRequired.swift */; };
E46F44CF2C66BF25009E5762 /* MultipleChoiceQuestion.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4DBF1B52B97C2AC0085D6A1 /* MultipleChoiceQuestion.swift */; };
E473AC712CB0AFC700530CB1 /* ResearchKitSwiftUI.strings in Resources */ = {isa = PBXBuildFile; fileRef = E473AC6F2CB0AFBE00530CB1 /* ResearchKitSwiftUI.strings */; };
E4DBF1B92B97C3060085D6A1 /* TextChoiceOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4DBF1B82B97C3030085D6A1 /* TextChoiceOption.swift */; };
E4DBF1BF2BA24AA40085D6A1 /* StickyScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4DBF1BE2BA24AA10085D6A1 /* StickyScrollView.swift */; };
E4DBF1C12BA24B190085D6A1 /* StickyFooterLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4DBF1C02BA24B150085D6A1 /* StickyFooterLayout.swift */; };
E4DBF1C32BA24B8B0085D6A1 /* StickyScrollView+StickyLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4DBF1C22BA24B860085D6A1 /* StickyScrollView+StickyLayout.swift */; };
E4E0AFCA2C5B12B40082ADF8 /* Color+ResearchFormColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E0AFC92C5B12B40082ADF8 /* Color+ResearchFormColors.swift */; };
E4FBFEDD2C6C0D0500F50946 /* SliderQuestion.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA32DE9E2BCF34440041A937 /* SliderQuestion.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
38DB82E42C753A8B0074B95D /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 0BF86B142B4F360000E3790E /* Project object */;
proxyType = 1;
remoteGlobalIDString = 0BF86B1C2B4F360000E3790E;
remoteInfo = "";
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
0B72132F2B4F36B900463E69 /* ResearchKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ResearchKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
0B7213392B4F379500463E69 /* ResearchKitUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ResearchKitUI.framework; sourceTree = BUILT_PRODUCTS_DIR; };
0B72133C2B4F37B300463E69 /* ResearchKitActiveTask.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ResearchKitActiveTask.framework; sourceTree = BUILT_PRODUCTS_DIR; };
0B7213542B4F3A8400463E69 /* ResearchKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ResearchKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
0B7213572B4F3ABD00463E69 /* ResearchKitActiveTask.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ResearchKitActiveTask.framework; sourceTree = BUILT_PRODUCTS_DIR; };
0B7213612B4F3C7F00463E69 /* ResearchKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ResearchKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
0B7213672B4F3CB200463E69 /* ResearchKitUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ResearchKitUI.framework; sourceTree = BUILT_PRODUCTS_DIR; };
0BF86B1D2B4F360000E3790E /* ResearchKitSwiftUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ResearchKitSwiftUI.framework; sourceTree = BUILT_PRODUCTS_DIR; };
0BF86B212B4F360000E3790E /* ResearchKitSwiftUI.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = ResearchKitSwiftUI.docc; sourceTree = "<group>"; };
380E14AF2C3350A900085D2B /* ResearchForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResearchForm.swift; sourceTree = "<group>"; };
3815C0132C2081CE00A29CD3 /* DateTimeQuestion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateTimeQuestion.swift; sourceTree = "<group>"; };
381CAC092C35E05A00FCE89A /* ResearchFormStepContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResearchFormStepContentView.swift; sourceTree = "<group>"; };
381CAC0B2C35F4C300FCE89A /* ResearchFormCompletion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResearchFormCompletion.swift; sourceTree = "<group>"; };
381CAC0F2C3C759200FCE89A /* HeightQuestion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeightQuestion.swift; sourceTree = "<group>"; };
3820BE1F2C6574350012CB2C /* ResearchFormResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResearchFormResult.swift; sourceTree = "<group>"; };
3820BE202C6574640012CB2C /* StepResultKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepResultKey.swift; sourceTree = "<group>"; };
382CF7842C3F262F000F3433 /* WeightQuestion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeightQuestion.swift; sourceTree = "<group>"; };
3858D2652C90EF1500F334EB /* ResultValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultValue.swift; sourceTree = "<group>"; };
389694172C6E7621007BA17D /* MeasurementUtility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeasurementUtility.swift; sourceTree = "<group>"; };
38CD260F2C1909CC000FDBB0 /* Question.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Question.swift; sourceTree = "<group>"; };
38CD263A2C1BA6EA000FDBB0 /* TextQuestion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextQuestion.swift; sourceTree = "<group>"; };
38DB82DF2C753A8B0074B95D /* ResearchKitSwiftUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ResearchKitSwiftUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
38F4F6C52C4AD35C004E7A8D /* ImageChoiceQuestion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageChoiceQuestion.swift; sourceTree = "<group>"; };
5E05B53C2C2E135D00A0A83B /* DoneKeyboardToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoneKeyboardToolbar.swift; sourceTree = "<group>"; };
5E44D4472C24A6FF0045007E /* NumericQuestion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NumericQuestion.swift; sourceTree = "<group>"; };
5E507F802C1141F2002958E2 /* SliderValueForegroundStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SliderValueForegroundStyle.swift; sourceTree = "<group>"; };
5E67B9712CA765540062A0F6 /* ResearchKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ResearchKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
5E7596BC2BE049B90088F7B7 /* ResearchKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ResearchKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
5EA939242CD18AFB001060CD /* ResearchFormStep.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResearchFormStep.swift; sourceTree = "<group>"; };
5EAFC71B2C7549B9008560D7 /* NavigationalLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationalLayout.swift; sourceTree = "<group>"; };
5EBAA28C2C40634800F193C6 /* BodyItemIconForegroundStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BodyItemIconForegroundStyle.swift; sourceTree = "<group>"; };
5EBAA28E2C4063C100F193C6 /* StepIconForegroundStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepIconForegroundStyle.swift; sourceTree = "<group>"; };
5ED16E262C5AF7DF0072016D /* InstructionBodyItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstructionBodyItem.swift; sourceTree = "<group>"; };
5ED33A362CAE1EB600926DA3 /* TextChoice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextChoice.swift; sourceTree = "<group>"; };
5EDFA9D62BFFFA2B0044C156 /* StepHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepHeader.swift; sourceTree = "<group>"; };
5EE5FD302C6A8482004BBABF /* StateManagementType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateManagementType.swift; sourceTree = "<group>"; };
5EFC55342C9B9C4D002C9587 /* QuestionCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuestionCard.swift; sourceTree = "<group>"; };
5EFC55732C9DEF9E002C9587 /* KeyboardUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardUtilities.swift; sourceTree = "<group>"; };
69EE51522C77F1B8008B2AD6 /* QuestionRequired.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuestionRequired.swift; sourceTree = "<group>"; };
BA32DE9E2BCF34440041A937 /* SliderQuestion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SliderQuestion.swift; sourceTree = "<group>"; };
E473AC6E2CB0AFBE00530CB1 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = ResearchKitSwiftUI.strings; sourceTree = "<group>"; };
E49B08D92C73EDD000669012 /* ResearchKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ResearchKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
E4B087862C668EA500EFCE35 /* ResearchKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ResearchKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
E4B0878A2C668EB100EFCE35 /* ResearchKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ResearchKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
E4B0878B2C668EBB00EFCE35 /* ResearchKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ResearchKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
E4DBF1B52B97C2AC0085D6A1 /* MultipleChoiceQuestion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultipleChoiceQuestion.swift; sourceTree = "<group>"; };
E4DBF1B82B97C3030085D6A1 /* TextChoiceOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextChoiceOption.swift; sourceTree = "<group>"; };
E4DBF1BE2BA24AA10085D6A1 /* StickyScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickyScrollView.swift; sourceTree = "<group>"; };
E4DBF1C02BA24B150085D6A1 /* StickyFooterLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickyFooterLayout.swift; sourceTree = "<group>"; };
E4DBF1C22BA24B860085D6A1 /* StickyScrollView+StickyLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StickyScrollView+StickyLayout.swift"; sourceTree = "<group>"; };
E4E0AFC92C5B12B40082ADF8 /* Color+ResearchFormColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+ResearchFormColors.swift"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
38DB82E02C753A8B0074B95D /* ResearchKitSwiftUITests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = ResearchKitSwiftUITests; sourceTree = "<group>"; };
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */
0BF86B1A2B4F360000E3790E /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
38DB82DC2C753A8B0074B95D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
38DB82E32C753A8B0074B95D /* ResearchKitSwiftUI.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
0B53DA5F2BD054EE00227126 /* Localization */ = {
isa = PBXGroup;
children = (
E473AC702CB0AFBE00530CB1 /* en.lproj */,
);
path = Localization;
sourceTree = "<group>";
};
0B72132E2B4F36B900463E69 /* Frameworks */ = {
isa = PBXGroup;
children = (
5E67B9712CA765540062A0F6 /* ResearchKit.framework */,
E49B08D92C73EDD000669012 /* ResearchKit.framework */,
E4B0878B2C668EBB00EFCE35 /* ResearchKit.framework */,
E4B0878A2C668EB100EFCE35 /* ResearchKit.framework */,
E4B087862C668EA500EFCE35 /* ResearchKit.framework */,
5E7596BC2BE049B90088F7B7 /* ResearchKit.framework */,
0B7213612B4F3C7F00463E69 /* ResearchKit.framework */,
0B7213672B4F3CB200463E69 /* ResearchKitUI.framework */,
0B7213542B4F3A8400463E69 /* ResearchKit.framework */,
0B7213572B4F3ABD00463E69 /* ResearchKitActiveTask.framework */,
0B72132F2B4F36B900463E69 /* ResearchKit.framework */,
0B7213392B4F379500463E69 /* ResearchKitUI.framework */,
0B72133C2B4F37B300463E69 /* ResearchKitActiveTask.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
0BF86B132B4F360000E3790E = {
isa = PBXGroup;
children = (
0BF86B1F2B4F360000E3790E /* ResearchKitSwiftUI */,
38DB82E02C753A8B0074B95D /* ResearchKitSwiftUITests */,
0BF86B1E2B4F360000E3790E /* Products */,
0B72132E2B4F36B900463E69 /* Frameworks */,
);
sourceTree = "<group>";
};
0BF86B1E2B4F360000E3790E /* Products */ = {
isa = PBXGroup;
children = (
0BF86B1D2B4F360000E3790E /* ResearchKitSwiftUI.framework */,
38DB82DF2C753A8B0074B95D /* ResearchKitSwiftUITests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
0BF86B1F2B4F360000E3790E /* ResearchKitSwiftUI */ = {
isa = PBXGroup;
children = (
5EA939202CD18A11001060CD /* Form */,
5EA9391F2CD18A05001060CD /* Step */,
E4DBF1B22B97C27E0085D6A1 /* Questions */,
5EA939212CD18A40001060CD /* Instructions */,
5EA9391C2CD18997001060CD /* Scrolling */,
3820BE1E2C65742D0012CB2C /* Results */,
5EA939232CD18AA8001060CD /* Measurement */,
5EFC55722C9DEF8F002C9587 /* Keyboard */,
5EA939272CD18C58001060CD /* Color */,
0B53DA5F2BD054EE00227126 /* Localization */,
0BF86B212B4F360000E3790E /* ResearchKitSwiftUI.docc */,
);
path = ResearchKitSwiftUI;
sourceTree = "<group>";
};
3820BE1E2C65742D0012CB2C /* Results */ = {
isa = PBXGroup;
children = (
3858D2652C90EF1500F334EB /* ResultValue.swift */,
3820BE202C6574640012CB2C /* StepResultKey.swift */,
3820BE1F2C6574350012CB2C /* ResearchFormResult.swift */,
);
path = Results;
sourceTree = "<group>";
};
5EA9391C2CD18997001060CD /* Scrolling */ = {
isa = PBXGroup;
children = (
E4DBF1C22BA24B860085D6A1 /* StickyScrollView+StickyLayout.swift */,
E4DBF1C02BA24B150085D6A1 /* StickyFooterLayout.swift */,
E4DBF1BE2BA24AA10085D6A1 /* StickyScrollView.swift */,
);
path = Scrolling;
sourceTree = "<group>";
};
5EA9391D2CD189CF001060CD /* Types */ = {
isa = PBXGroup;
children = (
5EA9392B2CD18DFD001060CD /* Multiple Choice */,
381CAC0F2C3C759200FCE89A /* HeightQuestion.swift */,
382CF7842C3F262F000F3433 /* WeightQuestion.swift */,
5EA939222CD18A89001060CD /* Slider */,
38CD263A2C1BA6EA000FDBB0 /* TextQuestion.swift */,
3815C0132C2081CE00A29CD3 /* DateTimeQuestion.swift */,
5E44D4472C24A6FF0045007E /* NumericQuestion.swift */,
38F4F6C52C4AD35C004E7A8D /* ImageChoiceQuestion.swift */,
);
path = Types;
sourceTree = "<group>";
};
5EA9391E2CD189DF001060CD /* Optionality */ = {
isa = PBXGroup;
children = (
69EE51522C77F1B8008B2AD6 /* QuestionRequired.swift */,
);
path = Optionality;
sourceTree = "<group>";
};
5EA9391F2CD18A05001060CD /* Step */ = {
isa = PBXGroup;
children = (
381CAC092C35E05A00FCE89A /* ResearchFormStepContentView.swift */,
5EA939242CD18AFB001060CD /* ResearchFormStep.swift */,
5EDFA9D62BFFFA2B0044C156 /* StepHeader.swift */,
5EBAA28E2C4063C100F193C6 /* StepIconForegroundStyle.swift */,
);
path = Step;
sourceTree = "<group>";
};
5EA939202CD18A11001060CD /* Form */ = {
isa = PBXGroup;
children = (
380E14AF2C3350A900085D2B /* ResearchForm.swift */,
381CAC0B2C35F4C300FCE89A /* ResearchFormCompletion.swift */,
5EAFC71B2C7549B9008560D7 /* NavigationalLayout.swift */,
);
path = Form;
sourceTree = "<group>";
};
5EA939212CD18A40001060CD /* Instructions */ = {
isa = PBXGroup;
children = (
5ED16E262C5AF7DF0072016D /* InstructionBodyItem.swift */,
5EBAA28C2C40634800F193C6 /* BodyItemIconForegroundStyle.swift */,
);
path = Instructions;
sourceTree = "<group>";
};
5EA939222CD18A89001060CD /* Slider */ = {
isa = PBXGroup;
children = (
BA32DE9E2BCF34440041A937 /* SliderQuestion.swift */,
5E507F802C1141F2002958E2 /* SliderValueForegroundStyle.swift */,
);
path = Slider;
sourceTree = "<group>";
};
5EA939232CD18AA8001060CD /* Measurement */ = {
isa = PBXGroup;
children = (
389694172C6E7621007BA17D /* MeasurementUtility.swift */,
);
path = Measurement;
sourceTree = "<group>";
};
5EA939272CD18C58001060CD /* Color */ = {
isa = PBXGroup;
children = (
E4E0AFC92C5B12B40082ADF8 /* Color+ResearchFormColors.swift */,
);
path = Color;
sourceTree = "<group>";
};
5EA939282CD18D68001060CD /* Response Options */ = {
isa = PBXGroup;
children = (
5ED33A362CAE1EB600926DA3 /* TextChoice.swift */,
);
path = "Response Options";
sourceTree = "<group>";
};
5EA939292CD18D8F001060CD /* Basic UI Structure */ = {
isa = PBXGroup;
children = (
38CD260F2C1909CC000FDBB0 /* Question.swift */,
5EFC55342C9B9C4D002C9587 /* QuestionCard.swift */,
);
path = "Basic UI Structure";
sourceTree = "<group>";
};
5EA9392A2CD18DB7001060CD /* Binding Management */ = {
isa = PBXGroup;
children = (
5EE5FD302C6A8482004BBABF /* StateManagementType.swift */,
);
path = "Binding Management";
sourceTree = "<group>";
};
5EA9392B2CD18DFD001060CD /* Multiple Choice */ = {
isa = PBXGroup;
children = (
E4DBF1B52B97C2AC0085D6A1 /* MultipleChoiceQuestion.swift */,
E4DBF1B82B97C3030085D6A1 /* TextChoiceOption.swift */,
);
path = "Multiple Choice";
sourceTree = "<group>";
};
5EFC55722C9DEF8F002C9587 /* Keyboard */ = {
isa = PBXGroup;
children = (
5EFC55732C9DEF9E002C9587 /* KeyboardUtilities.swift */,
5E05B53C2C2E135D00A0A83B /* DoneKeyboardToolbar.swift */,
);
path = Keyboard;
sourceTree = "<group>";
};
E473AC702CB0AFBE00530CB1 /* en.lproj */ = {
isa = PBXGroup;
children = (
E473AC6F2CB0AFBE00530CB1 /* ResearchKitSwiftUI.strings */,
);
path = en.lproj;
sourceTree = "<group>";
};
E4DBF1B22B97C27E0085D6A1 /* Questions */ = {
isa = PBXGroup;
children = (
5EA939292CD18D8F001060CD /* Basic UI Structure */,
5EA9391D2CD189CF001060CD /* Types */,
5EA939282CD18D68001060CD /* Response Options */,
5EA9392A2CD18DB7001060CD /* Binding Management */,
5EA9391E2CD189DF001060CD /* Optionality */,
);
path = Questions;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
3810A4032C7D3BA000535B38 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
0BF86B1C2B4F360000E3790E /* ResearchKitSwiftUI */ = {
isa = PBXNativeTarget;
buildConfigurationList = 0BF86B262B4F360000E3790E /* Build configuration list for PBXNativeTarget "ResearchKitSwiftUI" */;
buildPhases = (
3810A4032C7D3BA000535B38 /* Headers */,
0BF86B192B4F360000E3790E /* Sources */,
0BF86B1A2B4F360000E3790E /* Frameworks */,
0BF86B1B2B4F360000E3790E /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = ResearchKitSwiftUI;
productName = ResearchKitSwiftUI;
productReference = 0BF86B1D2B4F360000E3790E /* ResearchKitSwiftUI.framework */;
productType = "com.apple.product-type.framework";
};
38DB82DE2C753A8B0074B95D /* ResearchKitSwiftUITests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 38DB82E82C753A8B0074B95D /* Build configuration list for PBXNativeTarget "ResearchKitSwiftUITests" */;
buildPhases = (
38DB82DB2C753A8B0074B95D /* Sources */,
38DB82DC2C753A8B0074B95D /* Frameworks */,
38DB82DD2C753A8B0074B95D /* Resources */,
);
buildRules = (
);
dependencies = (
38DB82E52C753A8B0074B95D /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
38DB82E02C753A8B0074B95D /* ResearchKitSwiftUITests */,
);
name = ResearchKitSwiftUITests;
packageProductDependencies = (
);
productName = ResearchKitSwiftUITests;
productReference = 38DB82DF2C753A8B0074B95D /* ResearchKitSwiftUITests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
0BF86B142B4F360000E3790E /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1600;
LastUpgradeCheck = 1510;
TargetAttributes = {
0BF86B1C2B4F360000E3790E = {
CreatedOnToolsVersion = 15.1;
};
38DB82DE2C753A8B0074B95D = {
CreatedOnToolsVersion = 16.0;
};
};
};
buildConfigurationList = 0BF86B172B4F360000E3790E /* Build configuration list for PBXProject "ResearchKitSwiftUI" */;
compatibilityVersion = "Xcode 15.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 0BF86B132B4F360000E3790E;
productRefGroup = 0BF86B1E2B4F360000E3790E /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
0BF86B1C2B4F360000E3790E /* ResearchKitSwiftUI */,
38DB82DE2C753A8B0074B95D /* ResearchKitSwiftUITests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
0BF86B1B2B4F360000E3790E /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
E473AC712CB0AFC700530CB1 /* ResearchKitSwiftUI.strings in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
38DB82DD2C753A8B0074B95D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
0BF86B192B4F360000E3790E /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
E46F44CF2C66BF25009E5762 /* MultipleChoiceQuestion.swift in Sources */,
69EE51532C77F1B8008B2AD6 /* QuestionRequired.swift in Sources */,
381CAC0C2C35F4CE00FCE89A /* ResearchFormCompletion.swift in Sources */,
E4DBF1C32BA24B8B0085D6A1 /* StickyScrollView+StickyLayout.swift in Sources */,
E4DBF1C12BA24B190085D6A1 /* StickyFooterLayout.swift in Sources */,
5E44D4482C24A6FF0045007E /* NumericQuestion.swift in Sources */,
3820BE222C6574C50012CB2C /* StepResultKey.swift in Sources */,
E4DBF1BF2BA24AA40085D6A1 /* StickyScrollView.swift in Sources */,
E4FBFEDD2C6C0D0500F50946 /* SliderQuestion.swift in Sources */,
5EFC55352C9B9C4D002C9587 /* QuestionCard.swift in Sources */,
5EBAA28F2C4063C100F193C6 /* StepIconForegroundStyle.swift in Sources */,
5EAFC71C2C7549BD008560D7 /* NavigationalLayout.swift in Sources */,
5EBAA28D2C40634800F193C6 /* BodyItemIconForegroundStyle.swift in Sources */,
5EDFA9D72BFFFA2B0044C156 /* StepHeader.swift in Sources */,
5E507F812C1141F2002958E2 /* SliderValueForegroundStyle.swift in Sources */,
5EA939252CD18B01001060CD /* ResearchFormStep.swift in Sources */,
5EFC55742C9DEFC0002C9587 /* KeyboardUtilities.swift in Sources */,
E4E0AFCA2C5B12B40082ADF8 /* Color+ResearchFormColors.swift in Sources */,
38CD26102C1909CC000FDBB0 /* Question.swift in Sources */,
380E14B02C3350A900085D2B /* ResearchForm.swift in Sources */,
E4DBF1B92B97C3060085D6A1 /* TextChoiceOption.swift in Sources */,
5E05B53D2C2E135D00A0A83B /* DoneKeyboardToolbar.swift in Sources */,
3815C0142C2081CE00A29CD3 /* DateTimeQuestion.swift in Sources */,
382CF7852C3F2632000F3433 /* WeightQuestion.swift in Sources */,
38F4F6C62C4AD35C004E7A8D /* ImageChoiceQuestion.swift in Sources */,
38CD263B2C1BA6EA000FDBB0 /* TextQuestion.swift in Sources */,
3820BE212C6574C00012CB2C /* ResearchFormResult.swift in Sources */,
381CAC102C3C759200FCE89A /* HeightQuestion.swift in Sources */,
5ED33A372CAE1EB900926DA3 /* TextChoice.swift in Sources */,
381CAC0A2C35E06500FCE89A /* ResearchFormStepContentView.swift in Sources */,
0BF86B222B4F360000E3790E /* ResearchKitSwiftUI.docc in Sources */,
5EE5FD312C6A8486004BBABF /* StateManagementType.swift in Sources */,
3858D2662C90EF1700F334EB /* ResultValue.swift in Sources */,
389694182C6E7622007BA17D /* MeasurementUtility.swift in Sources */,
5ED16E272C5AF7E50072016D /* InstructionBodyItem.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
38DB82DB2C753A8B0074B95D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
38DB82E52C753A8B0074B95D /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
platformFilters = (
ios,
xros,
);
target = 0BF86B1C2B4F360000E3790E /* ResearchKitSwiftUI */;
targetProxy = 38DB82E42C753A8B0074B95D /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
E473AC6F2CB0AFBE00530CB1 /* ResearchKitSwiftUI.strings */ = {
isa = PBXVariantGroup;
children = (
E473AC6E2CB0AFBE00530CB1 /* en */,
);
name = ResearchKitSwiftUI.strings;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
0BF86B242B4F360000E3790E /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 17.2;
LOCALIZATION_PREFERS_STRING_CATALOGS = NO;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = xros;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Debug;
};
0BF86B252B4F360000E3790E /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 17.2;
LOCALIZATION_PREFERS_STRING_CATALOGS = NO;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = xros;
SWIFT_COMPILATION_MODE = wholemodule;
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
0BF86B272B4F360000E3790E /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES;
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
DRIVERKIT_DEPLOYMENT_TARGET = 20.0;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_MODULE_VERIFIER = YES;
FRAMEWORK_SEARCH_PATHS = "";
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 1.0;
MODULEMAP_FILE = "";
MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
MODULE_VERIFIER_VERBOSE = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.researchkit.ResearchKitSwiftUI;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator watchos watchsimulator xros xrsimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_OBJC_BRIDGING_HEADER = "";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,4,7";
WATCHOS_DEPLOYMENT_TARGET = 11.0;
XROS_DEPLOYMENT_TARGET = 2.0;
};
name = Debug;
};
0BF86B282B4F360000E3790E /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES;
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
DRIVERKIT_DEPLOYMENT_TARGET = 20.0;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_MODULE_VERIFIER = YES;
FRAMEWORK_SEARCH_PATHS = "";
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 1.0;
MODULEMAP_FILE = "";
MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
MODULE_VERIFIER_VERBOSE = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.researchkit.ResearchKitSwiftUI;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator watchos watchsimulator xros xrsimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_OBJC_BRIDGING_HEADER = "";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,4,7";
WATCHOS_DEPLOYMENT_TARGET = 11.0;
XROS_DEPLOYMENT_TARGET = 2.0;
};
name = Release;
};
38DB82E62C753A8B0074B95D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "";
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = come.appleinternal.ResearchKitSwiftUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = auto;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator";
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,7";
XROS_DEPLOYMENT_TARGET = 2.0;
};
name = Debug;
};
38DB82E72C753A8B0074B95D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "";
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = come.appleinternal.ResearchKitSwiftUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = auto;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator";
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,7";
XROS_DEPLOYMENT_TARGET = 2.0;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
0BF86B172B4F360000E3790E /* Build configuration list for PBXProject "ResearchKitSwiftUI" */ = {
isa = XCConfigurationList;
buildConfigurations = (
0BF86B242B4F360000E3790E /* Debug */,
0BF86B252B4F360000E3790E /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
0BF86B262B4F360000E3790E /* Build configuration list for PBXNativeTarget "ResearchKitSwiftUI" */ = {
isa = XCConfigurationList;
buildConfigurations = (
0BF86B272B4F360000E3790E /* Debug */,
0BF86B282B4F360000E3790E /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
38DB82E82C753A8B0074B95D /* Build configuration list for PBXNativeTarget "ResearchKitSwiftUITests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
38DB82E62C753A8B0074B95D /* Debug */,
38DB82E72C753A8B0074B95D /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 0BF86B142B4F360000E3790E /* Project object */;
}
@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1600"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0BF86B1C2B4F360000E3790E"
BuildableName = "ResearchKitSwiftUI.framework"
BlueprintName = "ResearchKitSwiftUI"
ReferencedContainer = "container:ResearchKitSwiftUI.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0BF86B1C2B4F360000E3790E"
BuildableName = "ResearchKitSwiftUI.framework"
BlueprintName = "ResearchKitSwiftUI"
ReferencedContainer = "container:ResearchKitSwiftUI.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1600"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "38DB82DE2C753A8B0074B95D"
BuildableName = "ResearchKitSwiftUITests.xctest"
BlueprintName = "ResearchKitSwiftUITests"
ReferencedContainer = "container:ResearchKitSwiftUI.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
@@ -0,0 +1,80 @@
/*
Copyright (c) 2024, 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 Foundation
import SwiftUI
extension Color {
public enum ColorChoice {
case background
case secondaryBackground
case label
case systemGray4
case systemGray5
}
public static func choice(for choice: ColorChoice) -> Color {
switch choice {
case .background:
#if os(watchOS)
return Color.primary
#else
return Color(uiColor: UIColor.systemBackground)
#endif
case .secondaryBackground:
#if os(watchOS)
return Color.secondary
#else
return Color(uiColor: UIColor.secondarySystemBackground)
#endif
case .label:
#if os(watchOS)
return Color.primary
#else
return Color(uiColor: UIColor.label)
#endif
case .systemGray4:
#if os(watchOS)
return Color.secondary.opacity(0.4)
#else
return Color(uiColor: UIColor.systemGray4)
#endif
case .systemGray5:
#if os(watchOS)
return Color.secondary.opacity(0.5)
#else
return Color(uiColor: UIColor.systemGray5)
#endif
}
}
}
@@ -0,0 +1,170 @@
/*
Copyright (c) 2024, 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 SwiftUI
struct NavigationalLayout: View {
@Environment(\.dismiss) var dismiss
@State
private var stepIdentifiers: [Subview.ID] = []
@State
private var researchFormCompletion: ResearchFormCompletion?
private let steps: SubviewsCollection
private let onResearchFormCompletion: ((ResearchFormCompletion) -> Void)?
init(
_ steps: SubviewsCollection,
onResearchFormCompletion: ((ResearchFormCompletion) -> Void)?
) {
self.steps = steps
self.onResearchFormCompletion = onResearchFormCompletion
}
var body: some View {
NavigationStack(path: $stepIdentifiers) {
if let firstStep = steps.first {
ResearchFormStepContentView(
isLastStep: isLastStep(for: firstStep)
) { completion in
switch completion {
case .failed, .discarded, .terminated:
dismiss()
case .completed(let result):
handle(formCompletion: completion, with: result)
case .saved:
if let currentStepIndex = index(for: firstStep) {
moveToNextStep(
relativeToCurrentIndex: currentStepIndex)
}
}
} content: {
firstStep
}
.navigationTitle("1 of \(steps.count)")
.navigationDestination(for: Subview.ID.self) { subviewID in
ResearchFormStepContentView(
isLastStep: isLastStep(for: subviewID)
) { completion in
switch completion {
case .failed, .discarded, .terminated:
dismiss()
case .completed(let result):
handle(formCompletion: completion, with: result)
case .saved:
if let currentStepIndex = index(for: subviewID) {
moveToNextStep(
relativeToCurrentIndex: currentStepIndex)
}
}
} content: {
if let currentStepIndex = index(for: subviewID) {
steps[currentStepIndex]
}
}
.navigationTitle(navigationTitle(for: subviewID))
}
}
}
#if os(watchOS)
.preference(
key: ResearchFormCompletionKey.self,
value: researchFormCompletion)
#endif
}
private func handle(
formCompletion completion: ResearchFormCompletion,
with result: ResearchFormResult
) {
if let onResearchFormCompletion {
#if os(watchOS)
researchFormCompletion = .completed(result)
#endif
onResearchFormCompletion(completion)
} else {
dismiss()
}
}
private func moveToNextStep(relativeToCurrentIndex currentIndex: Int) {
let nextStepIndex = currentIndex + 1
if nextStepIndex < steps.count {
stepIdentifiers.append(steps[nextStepIndex].id)
}
}
private func isLastStep(for subview: Subview) -> Bool {
isLastStep(for: subview.id)
}
private func isLastStep(for id: Subview.ID) -> Bool {
steps.firstIndex(where: { $0.id == id }) == steps.count - 1
}
private func index(for subview: Subview) -> Int? {
index(for: subview.id)
}
private func index(for id: Subview.ID) -> Int? {
steps.firstIndex { step in
step.id == id
}
}
private func navigationTitle(for id: Subview.ID) -> String {
let navigationTitle: String
if let index = index(for: id) {
navigationTitle = "\(index + 1) of \(steps.count)"
} else {
navigationTitle = ""
}
return navigationTitle
}
}
#if os(watchOS)
struct ResearchFormCompletionKey: PreferenceKey {
static var defaultValue: ResearchFormCompletion? = nil
static func reduce(
value: inout ResearchFormCompletion?,
nextValue: () -> ResearchFormCompletion?
) {
value = nextValue()
}
}
#endif
@@ -0,0 +1,98 @@
/*
Copyright (c) 2024, 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 SwiftUI
/// Manages the navigation between steps in a survey.
///
/// To add steps to a `ResearchForm`, create instances of ``ResearchFormStep`` and pass them into a `ResearchForm`. To display questions in each step, ResearchKit provides various question formats that can be used within a ``ResearchFormStep`` such as ``MultipleChoiceQuestion``, ``DateTimeQuestion``, and more. These questions can be marked as optional or required, which a `ResearchForm` tracks as part of navigation management.
///
/// Additionally, a `ResearchForm` manages survey results for questions that manage their own bindings internally. Results are passed through a ``ResearchFormResult`` once a survey is completed. For instance, a text question for which no binding is provided has its result stored in ``ResearchFormResult``. However, a text question that has been provided a binding does not have its result stored in ``ResearchFormResult``, and in this case, you are expected to manage the result for the text question.
public struct ResearchForm<Content: View>: View {
@State
private var managedFormResult: ResearchFormResult
#if os(watchOS)
@State
private var researchFormCompletion: ResearchFormCompletion?
#endif
private let taskKey: StepResultKey<String?>
private let steps: Content
private var onResearchFormCompletion: ((ResearchFormCompletion) -> Void)?
/// Initializes an instance of ``ResearchForm`` with the provided configuration.
/// - Parameters:
/// - taskIdentifier: An identifier that uniquely identifies this research form.
/// - restorationResult: A result used to pre-populate questions that had previously been answered.
/// - steps: The steps in a survey, each of which can contain a set of questions.
/// - onResearchFormCompletion: A completion that is triggered when the survey is dismissed.
public init(
id: String,
restorationResult: ResearchFormResult? = nil,
@ViewBuilder steps: () -> Content,
onResearchFormCompletion: ((ResearchFormCompletion) -> Void)? = nil
) {
self.taskKey = .text(id: id)
self.steps = steps()
self.onResearchFormCompletion = onResearchFormCompletion
self.managedFormResult = restorationResult ?? ResearchFormResult()
}
public var body: some View {
Group(subviews: steps) { steps in
NavigationalLayout(
steps, onResearchFormCompletion: onResearchFormCompletion)
}
.environmentObject(managedFormResult)
.environment(\.questionContext, .formEmbedded)
#if os(watchOS)
// On the watch, an x button is automatically added to the top left of the screen when presenting content, so we have
// to remove the cancel button, which had invoked `onResearchFormCompletion` with a completion of `discarded`.
//
// Here, we track the completions that come in, and in `onDisappear`, we invoke the completion with the `discarded`
// state if no completion was ever set. This helps with passing through the discarded state even when there is
// no cancel button on the watch.
.onPreferenceChange(
ResearchFormCompletionKey.self,
perform: { researchFormCompletion in
self.researchFormCompletion = researchFormCompletion
}
)
.onDisappear {
if researchFormCompletion == nil {
onResearchFormCompletion?(.discarded)
}
}
#endif
}
}
@@ -0,0 +1,70 @@
/*
Copyright (c) 2024, 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.
*/
/// Represents the context for a survey's completion.
public enum ResearchFormCompletion {
/// The survey was dismissed with the intent to save question responses.
case saved(ResearchFormResult)
/// The survey was dismissed with the intent to discard question responses.
case discarded
/// The survey was completed, and question responses are provided.
case completed(ResearchFormResult)
/// The survey was dismissed in a failed manner.
case failed
/// The survey was terminated.
case terminated
}
extension ResearchFormCompletion: Equatable {
public static func == (
lhs: ResearchFormCompletion, rhs: ResearchFormCompletion
) -> Bool {
let isEqual: Bool
switch (lhs, rhs) {
case let (.saved(lhsValue), .saved(rhsValue)):
isEqual = lhsValue == rhsValue
case (.discarded, .discarded):
isEqual = true
case let (.completed(lhsValue), .completed(rhsValue)):
isEqual = lhsValue == rhsValue
case (.failed, .failed):
isEqual = true
case (.terminated, .terminated):
isEqual = true
default:
isEqual = false
}
return isEqual
}
}
@@ -0,0 +1,51 @@
/*
Copyright (c) 2024, 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 SwiftUI
extension ShapeStyle where Self == BodyItemIconForegroundStyle {
/// This foreground style is used for labels that display values associated with sliders.
static var bodyItemIconForegroundStyle: BodyItemIconForegroundStyle {
BodyItemIconForegroundStyle()
}
}
struct BodyItemIconForegroundStyle: ShapeStyle {
func resolve(in environment: EnvironmentValues) -> some ShapeStyle {
#if os(visionOS)
.white
#else
Color.accentColor
#endif
}
}
@@ -0,0 +1,115 @@
/*
Copyright (c) 2024, 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 SwiftUI
/// Displays an image and text side by side.
public struct InstructionBodyItem: View {
private let image: Image?
private let text: Text?
/// Initializes an instance of ``InstructionBodyItem`` with the provided configuration.
/// - Parameters:
/// - image: The image to display for this instruction.
/// - text: The text to display for this instruction.
public init(image: Image? = nil, text: Text? = nil) {
self.image = image
self.text = text
}
public var body: some View {
#if os(watchOS)
VStack(alignment: .leading) {
image?
.frame(width: 20, height: 20)
.foregroundStyle(.bodyItemIconForegroundStyle)
text?
.font(.subheadline)
}
.preference(key: QuestionCardPreferenceKey.self, value: false)
#else
HStack {
image?
.frame(width: 40, height: 40)
.foregroundStyle(.bodyItemIconForegroundStyle)
text?
.font(.subheadline)
}
.preference(key: QuestionCardPreferenceKey.self, value: false)
#endif
}
}
struct QuestionCardPreferenceKey: PreferenceKey {
static var defaultValue = true
static func reduce(value: inout Bool, nextValue: () -> Bool) {
value = nextValue()
}
}
#Preview {
ScrollView {
ResearchFormStep(
image: Image(systemName: "doc.text.magnifyingglass"),
title: "Before You Join join"
) {
InstructionBodyItem(
image: Image(systemName: "heart.fill"),
text: Text(
"The study will ask you to share some of your Health data.")
)
InstructionBodyItem(
image: Image(systemName: "checkmark.circle.fill"),
text: Text(
"You will be asked to complete various tasks over the duration of the study."
)
)
InstructionBodyItem(
image: Image(systemName: "signature"),
text: Text(
"Before joining, we will ask you to sign an informed consent document."
)
)
InstructionBodyItem(
image: Image(systemName: "lock.fill"),
text: Text("Your data is kept private and secure.")
)
}
}
}
@@ -0,0 +1,78 @@
/*
Copyright (c) 2024, 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 SwiftUI
extension View {
/// Adds a toolbar above the keyboard that contains a done button, which invokes the action passed to this modifier.
/// - Parameter condition: The condition under which the keyboard toolbar is shown.
/// - Parameter action: The action triggered when the done button in the toolbar is tapped.
/// - Returns: The modified view that includes the toolbar when the keyboard is invoked.
func doneKeyboardToolbar(
condition: @escaping () -> Bool, action: @escaping () -> Void
) -> some View {
modifier(
DoneKeyboardToolbar(condition: condition, action: action)
)
}
}
struct DoneKeyboardToolbar: ViewModifier {
private let condition: () -> Bool
private let action: () -> Void
init(condition: @escaping () -> Bool, action: @escaping () -> Void) {
self.condition = condition
self.action = action
}
func body(content: Content) -> some View {
#if os(iOS)
content
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
if condition() {
Spacer()
Button("Done") {
action()
}
}
}
}
#elseif os(visionOS)
content
#endif
}
}
@@ -0,0 +1,43 @@
/*
Copyright (c) 2024, 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
#if !os(watchOS)
extension UIApplication {
func endEditing() {
sendAction(
#selector(UIResponder.resignFirstResponder), to: nil, from: nil,
for: nil)
}
}
#endif
@@ -0,0 +1,34 @@
/*
Copyright (c) 2020, 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.
*/
"BUTTON_CANCEL" = "Cancel";
"BUTTON_NEXT" = "Next";
"BUTTON_DONE" = "Done";
"QUESTION_PROGRESS" = "%D OF %D";
@@ -0,0 +1,81 @@
/*
Copyright (c) 2024, 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 SwiftUI
import math_h
func convertCentimetersToFeetAndInches(_ centimeters: Double) -> (
feet: Int, inches: Int
) {
var feet = 0
var inches = 0
let centimetersToInches = centimeters * 0.393701
inches = Int(centimetersToInches)
feet = inches / 12
inches = inches % 12
return (feet, inches)
}
func convertKilogramsToPoundsAndOunces(_ kilograms: Double) -> (
pounds: Double, ounces: Double
) {
let poundPerKilogram = 2.20462262
let fractionalPounds = kilograms * poundPerKilogram
var pounds = floor(fractionalPounds)
var ounces = round((fractionalPounds - pounds) * 16)
if ounces == 16 {
pounds += 1
ounces = 0
}
return (pounds, ounces)
}
func convertPoundsAndOuncesToKilograms(pounds: Double, ounces: Double) -> Double
{
let kilogramsPerPound = 0.45359237
let kg = (pounds + (ounces / 16)) * kilogramsPerPound
return round(kg * 100) / 100
}
func convertKilogramsToWholeAndFraction(_ kilograms: Double) -> (
whole: Double, fraction: Double
) {
let whole = floor(kilograms)
let fraction = round((kilograms - floor(kilograms)) * 100)
return (whole, fraction)
}
extension Binding {
func unwrapped<T>(defaultValue: T) -> Binding<T> where Value == T? {
Binding<T>(
get: { self.wrappedValue ?? defaultValue },
set: { self.wrappedValue = $0 })
}
}
@@ -0,0 +1,149 @@
/*
Copyright (c) 2024, 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 SwiftUI
struct Question<Header: View, Content: View>: View {
@Environment(\.colorScheme) var colorScheme
private let header: Header
private let content: Content
init(
@ViewBuilder header: () -> Header,
@ViewBuilder content: () -> Content
) {
self.header = header()
self.content = content()
}
var body: some View {
VStack(alignment: .leading, spacing: .zero) {
header
Divider()
.padding(.horizontal)
content
}
}
}
extension Question where Header == QuestionHeader {
init(
title: String,
detail: String? = nil,
content: () -> Content
) {
self.header = QuestionHeader(title: title, detail: detail)
self.content = content()
}
}
/// A question header containing a title and detail.
public struct QuestionHeader: View {
private let title: String
private let detail: String?
init(title: String, detail: String? = nil) {
self.title = title
self.detail = detail
}
public var body: some View {
VStack(alignment: .leading, spacing: 4) {
titleText(title)
if let detail {
Text(detail)
.font(detailFont)
}
}
#if os(watchOS)
.padding(.horizontal)
.padding(.bottom, 4)
.padding(.top, 4)
#else
.padding()
#endif
}
@ViewBuilder
private func titleText(_ text: String) -> some View {
Text(title)
.foregroundStyle(Color.choice(for: .label))
.fontWeight(.bold)
#if os(watchOS)
.font(.footnote)
#else
.font(.body)
#endif
}
private var detailFont: Font {
#if os(watchOS)
.system(size: 12)
#else
.subheadline
#endif
}
}
#Preview("Detail and Title") {
VStack {
Spacer()
Question(title: "What is your name?") {
Text("Specific component content will show up here")
}
Spacer()
}
.padding()
.background {
Color.choice(for: .secondaryBackground)
}
.ignoresSafeArea()
}
#Preview("Just title") {
VStack {
Spacer()
Question(title: "What is your name?") {
Text("Specific component content will show up here")
}
Spacer()
}
.padding()
.background {
Color.choice(for: .secondaryBackground)
}
.ignoresSafeArea()
}
@@ -0,0 +1,131 @@
/*
Copyright (c) 2024, 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 SwiftUI
struct QuestionCard<Content: View>: View {
@Environment(\.isQuestionCardEnabled)
private var isQuestionCardEnabled
private let content: Content
init(content: () -> Content) {
self.content = content()
}
var body: some View {
if isQuestionCardEnabled {
content
.modifier(
QuestionCardModifier()
)
.questionCardEnabled(false)
} else {
content
}
}
}
extension EnvironmentValues {
@Entry var isQuestionCardEnabled = true
}
struct QuestionCardModifier: ViewModifier {
func body(content: Content) -> some View {
content
.background(.cardColor)
.clipShape(
RoundedRectangle(cornerRadius: 12)
)
}
}
extension ShapeStyle where Self == CardColor {
fileprivate static var cardColor: CardColor {
CardColor()
}
}
private struct CardColor: ShapeStyle {
func resolve(in environment: EnvironmentValues) -> some ShapeStyle {
#if os(visionOS)
.regularMaterial
#else
environment.colorScheme == .dark
? Color.choice(for: .systemGray4) : .white
#endif
}
}
extension View {
func questionCardEnabled(_ isEnabled: Bool) -> some View {
environment(\.isQuestionCardEnabled, isEnabled)
}
}
#Preview {
ScrollView {
VStack(spacing: 20) {
QuestionCard {
VStack(alignment: .leading) {
Text("Title")
Text("Detail")
}
}
QuestionCard {
VStack(alignment: .leading, spacing: 0) {
QuestionCard {
VStack(alignment: .leading) {
Text("Title")
Text("Detail")
}
}
Rectangle()
.fill(Color(white: 0.975))
.frame(height: 44)
}
}
}
.padding()
}
}
@@ -0,0 +1,36 @@
/*
Copyright (c) 2024, 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 SwiftUI
enum StateManagementType<T> {
case automatic(key: StepResultKey<T>)
case manual(Binding<T>)
}
@@ -0,0 +1,68 @@
/*
Copyright (c) 2024, 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 SwiftUI
extension EnvironmentValues {
@Entry var questionRequired: Bool = false
}
extension View {
/// Designates a question as required or optional.
public func questionRequired(_ value: Bool) -> some View {
self
.environment(\.questionRequired, value)
}
}
struct QuestionRequiredPreferenceKey: PreferenceKey {
static var defaultValue: Bool = false
static func reduce(value: inout Bool, nextValue: () -> Bool) {
value = nextValue()
}
}
struct QuestionAnsweredPreferenceKey: PreferenceKey {
static var defaultValue = false
static func reduce(value: inout Bool, nextValue: () -> Bool) {
value = nextValue()
}
}
struct StepCompletedPreferenceKey: PreferenceKey {
static var defaultValue: Bool = false
static func reduce(value: inout Bool, nextValue: () -> Bool) {
value = nextValue()
}
}
@@ -0,0 +1,90 @@
/*
Copyright (c) 2024, 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 Foundation
/// Represents a text choice.
public struct TextChoice: Identifiable {
/// The id for this multiple choice option.
public let id: String
let choiceText: String
let value: ResultValue
/// Initializes an instance of ``TextChoice`` with the provided configuration.
/// - Parameters:
/// - id: The unique identifier for this multiple choice option.
/// - choiceText: The text for this multiple choice option.
/// - value: The integer value associated with this multiple choice option.
public init(
id: String,
choiceText: String,
value: Int
) {
self.init(id: id, choiceText: choiceText, value: .int(value))
}
/// Initializes an instance of ``TextChoice`` with the provided configuration.
/// - Parameters:
/// - id: The unique identifier for this multiple choice option.
/// - choiceText: The text for this multiple choice option.
/// - value: The string value associated with this multiple choice option.
public init(
id: String,
choiceText: String,
value: String
) {
self.init(id: id, choiceText: choiceText, value: .string(value))
}
/// Initializes an instance of ``TextChoice`` with the provided configuration.
/// - Parameters:
/// - id: The unique identifier for this multiple choice option.
/// - choiceText: The text for this multiple choice option.
/// - value: The date value associated with this multiple choice option.
public init(
id: String,
choiceText: String,
value: Date
) {
self.init(id: id, choiceText: choiceText, value: .date(value))
}
private init(
id: String,
choiceText: String,
value: ResultValue
) {
self.id = id
self.choiceText = choiceText
self.value = value
}
}
@@ -0,0 +1,341 @@
/*
Copyright (c) 2024, 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 SwiftUI
/// A question that allows for date and time input.
public struct DateTimeQuestion<Header: View>: View {
@EnvironmentObject
private var managedFormResult: ResearchFormResult
@Environment(\.questionContext)
private var questionContext: QuestionContext
@Environment(\.questionRequired)
private var isRequired: Bool
@State
private var managedDate: Date?
@State
private var showDatePickerModal = false
@State
private var showTimePickerModal = false
private let id: String
private let header: Header
private let pickerPrompt: String
private let displayedComponents: DatePicker.Components
private let range: ClosedRange<Date>
private let selection: StateManagementType<Date?>
private var resolvedResult: Binding<Date?> {
switch selection {
case let .automatic(key: key):
return Binding(
get: {
let date: Date?
if let managedDate {
date = managedDate
} else {
switch questionContext {
case .formEmbedded:
date = managedFormResult.resultForStep(key: key) ?? Date()
case .standalone:
date = Date()
}
}
return date
},
set: {
managedDate = $0
if case .formEmbedded = questionContext {
managedFormResult.setResultForStep(.date($0), key: key)
}
}
)
case let .manual(value):
return value
}
}
/// Initializes an instance of ``DateTimeQuestion`` with the provided configuration.
/// - Parameters:
/// - id: The unique identifier for this question.
/// - header: The header for this question.
/// - date: The selected date.
/// - pickerPrompt: The prompt that informs the user.
/// - displayedComponents: The date-time components that are displayed for this question.
/// - range: The range of selectable dates.
public init(
id: String,
@ViewBuilder header: () -> Header,
date: Date = Date(),
pickerPrompt: String,
displayedComponents: DatePicker.Components,
range: ClosedRange<Date>
) {
self.id = id
self.header = header()
self.pickerPrompt = pickerPrompt
self.displayedComponents = displayedComponents
self.range = range
self.selection = .automatic(key: .date(id: id))
}
/// Initializes an instance of ``DateTimeQuestion`` with the provided configuration.
/// - Parameters:
/// - id: The unique identifier for this question.
/// - header: The header for this question.
/// - date: The selected date.
/// - pickerPrompt: The prompt that informs the user.
/// - displayedComponents: The date-time components that are displayed for this question.
/// - range: The range of selectable dates.
public init(
id: String,
@ViewBuilder header: () -> Header,
date: Binding<Date?>,
pickerPrompt: String,
displayedComponents: DatePicker.Components,
range: ClosedRange<Date>
) {
self.id = id
self.header = header()
self.pickerPrompt = pickerPrompt
self.displayedComponents = displayedComponents
self.range = range
self.selection = .manual(date)
}
public var body: some View {
QuestionCard {
Question {
header
} content: {
#if os(watchOS)
VStack {
if displayedComponents.contains(.date) {
Button {
showDatePickerModal.toggle()
} label: {
Text(
resolvedResult.wrappedValue ?? Date(),
format: .dateTime.day().month().year()
)
}
}
if displayedComponents.contains(.hourMinuteAndSecond) {
Button {
showTimePickerModal.toggle()
} label: {
Text(
resolvedResult.wrappedValue ?? Date(),
format: .dateTime.hour().minute().second()
)
}
} else if displayedComponents.contains(.hourAndMinute) {
Button {
showTimePickerModal.toggle()
} label: {
Text(
resolvedResult.wrappedValue ?? Date(),
format: .dateTime.hour().minute()
)
}
}
}
.buttonBorderShape(.roundedRectangle)
.buttonStyle(.bordered)
.padding()
.navigationDestination(isPresented: $showDatePickerModal) {
watchDatePicker(displayedComponents: .date)
}
.navigationDestination(isPresented: $showTimePickerModal) {
watchDatePicker(
displayedComponents: displayedComponents.contains(
.hourMinuteAndSecond)
? .hourMinuteAndSecond : .hourAndMinute)
}
#else
DatePicker(
pickerPrompt,
selection: resolvedResult.unwrapped(
defaultValue: Date()),
in: range,
displayedComponents: displayedComponents
)
.datePickerStyle(.compact)
.foregroundStyle(.primary)
.padding()
#endif
}
.preference(
key: QuestionRequiredPreferenceKey.self, value: isRequired
)
.preference(
key: QuestionAnsweredPreferenceKey.self, value: isAnswered)
}
}
@ViewBuilder
private func watchDatePicker(displayedComponents: DatePicker.Components)
-> some View
{
VStack(alignment: .leading) {
header
DatePicker(
pickerPrompt,
selection: resolvedResult.unwrapped(defaultValue: Date()),
in: range,
displayedComponents: displayedComponents
)
#if !os(watchOS)
.padding(.horizontal)
#endif
}
}
private var isAnswered: Bool {
return resolvedResult.wrappedValue != nil
}
}
extension DateTimeQuestion where Header == QuestionHeader {
/// Initializes an instance of ``DateTimeQuestion`` with the provided configuration.
/// - Parameters:
/// - id: The unique identifier for this question.
/// - title: The title for this question.
/// - detail: The details for this question.
/// - date: The selected date.
/// - pickerPrompt: The prompt that informs the user.
/// - displayedComponents: The date-time components that are displayed for this question.
/// - range: The range of selectable dates.
public init(
id: String,
title: String,
detail: String? = nil,
date: Date = Date(),
pickerPrompt: String,
displayedComponents: DatePicker.Components,
range: ClosedRange<Date>
) {
self.id = id
self.header = QuestionHeader(title: title, detail: detail)
self.pickerPrompt = pickerPrompt
self.displayedComponents = displayedComponents
self.range = range
self.selection = .automatic(key: .date(id: id))
}
/// Initializes an instance of ``DateTimeQuestion`` with the provided configuration.
/// - Parameters:
/// - id: The unique identifier for this question.
/// - title: The title for this question.
/// - detail: The details for this question.
/// - date: The selected date.
/// - pickerPrompt: The prompt that informs the user.
/// - displayedComponents: The date-time components that are displayed for this question.
/// - range: The range of selectable dates.
public init(
id: String,
title: String,
detail: String? = nil,
date: Binding<Date?>,
pickerPrompt: String,
displayedComponents: DatePicker.Components,
range: ClosedRange<Date>
) {
self.id = id
self.header = QuestionHeader(title: title, detail: detail)
self.pickerPrompt = pickerPrompt
self.displayedComponents = displayedComponents
self.range = range
self.selection = .manual(date)
}
}
#Preview("Date Only") {
@Previewable @State var date: Date? = Date()
NavigationStack {
ScrollView {
DateTimeQuestion(
id: UUID().uuidString,
title: "What is your birthday?",
detail: "Question 1 of 4",
date: $date,
pickerPrompt: "Select Date",
displayedComponents: [.date],
range: Date.distantPast...Date.distantFuture
)
.padding(.horizontal)
}
}
}
#Preview("Time Only") {
@Previewable @State var date: Date? = Date()
NavigationStack {
ScrollView {
DateTimeQuestion(
id: UUID().uuidString,
title: "What time is it?",
detail: "Question 2 of 4",
date: $date,
pickerPrompt: "Select Time",
displayedComponents: [.hourAndMinute],
range: Date.distantPast...Date.distantFuture
)
.padding(.horizontal)
}
}
}
#Preview("Time and Date") {
@Previewable @State var date: Date? = Date()
NavigationStack {
ScrollView {
DateTimeQuestion(
id: UUID().uuidString,
title: "What is the time and date?",
detail: "Question 2 of 4",
date: $date,
pickerPrompt: "Select Time and Date",
displayedComponents: [.date, .hourAndMinute],
range: Date.distantPast...Date.distantFuture
)
.padding(.horizontal)
}
}
}
@@ -0,0 +1,387 @@
/*
Copyright (c) 2024, 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 SwiftUI
/// Represents the different measurement systems that can be used.
public enum MeasurementSystem {
/// The US Customary measurement system.
case USC
/// The measurement system defined by the system.
case local
/// The metric measurement system.
case metric
}
/// A question that allows for height input.
public struct HeightQuestion: View {
@EnvironmentObject
private var managedFormResult: ResearchFormResult
@Environment(\.questionContext)
private var questionContext: QuestionContext
@Environment(\.questionRequired)
private var isRequired: Bool
@State
private var managedHeight: Double?
@State private var isInputActive = false
@State private var hasChanges: Bool
private let id: String
private let title: String
private let detail: String?
private let measurementSystem: MeasurementSystem
private let selection: StateManagementType<Double?>
private var initialPrimaryValue: Double = 162 // Denotes height in cm, which is ~5'4", a good average height.
private var resolvedResult: Binding<Double?> {
switch selection {
case let .automatic(key: key):
return Binding(
get: {
let height: Double?
if let managedHeight {
height = managedHeight
} else {
switch questionContext {
case .formEmbedded:
height = managedFormResult.resultForStep(key: key) ?? initialPrimaryValue
case .standalone:
height = initialPrimaryValue
}
}
return height
},
set: {
managedHeight = $0
if case .formEmbedded = questionContext {
managedFormResult.setResultForStep(.height($0), key: key)
}
}
)
case let .manual(value):
return value
}
}
/// Initializes an instance of ``HeightQuestion`` with the provided configuration.
/// - Parameters:
/// - id: The unique identifier for this question.
/// - title: The title for this question.
/// - detail: The details for this question.
/// - measurementSystem: The measurement system for this question.
public init(
id: String,
title: String,
detail: String? = nil,
measurementSystem: MeasurementSystem
) {
self.id = id
self.hasChanges = false
self.title = title
self.detail = detail
let system: MeasurementSystem = {
switch measurementSystem {
case .USC:
return .USC
case .local:
if Locale.current.measurementSystem == .us {
return .USC
} else {
return .metric
}
case .metric:
return .metric
}
}()
self.measurementSystem = system
self.selection = .automatic(key: .height(id: id))
}
/// Initializes an instance of ``HeightQuestion`` with the provided configuration.
/// - Parameters:
/// - id: The unique identifier question.
/// - title: The title for this question.
/// - detail: The details for this question.
/// - measurementSystem: The measurement system for this question.
/// - height: The selected height.
public init(
id: String,
title: String,
detail: String? = nil,
measurementSystem: MeasurementSystem,
height: Binding<Double?>
) {
self.id = id
self.hasChanges = false
self.title = title
self.detail = detail
let system: MeasurementSystem = {
switch measurementSystem {
case .USC:
return .USC
case .local:
if Locale.current.measurementSystem == .us {
return .USC
} else {
return .metric
}
case .metric:
return .metric
}
}()
self.measurementSystem = system
self.selection = .manual(height)
}
private var selectionString: String {
guard let result = resolvedResult.wrappedValue else {
return "\(Int(initialPrimaryValue)) cm"
}
if measurementSystem == .USC {
let (feet, inches) = convertCentimetersToFeetAndInches(result)
return "\(feet)' \(inches)\""
} else {
return "\(Int(result)) cm"
}
}
public var body: some View {
QuestionCard {
Question(title: title, detail: detail) {
HStack {
Text("Select Height")
.foregroundStyle(Color.primary)
.frame(maxWidth: .infinity, alignment: .leading)
Button {
isInputActive = true
#if !os(watchOS)
UIApplication.shared.endEditing()
#endif
} label: {
Text(selectionString)
.foregroundStyle(Color.primary)
}
.buttonStyle(.bordered)
.buttonBorderShape(.roundedRectangle)
#if !os(watchOS)
.popover(
isPresented: $isInputActive,
attachmentAnchor: .point(.bottom),
arrowEdge: .top
) {
HeightPickerView(
measurementSystem: measurementSystem,
selection: resolvedResult,
hasChanges: $hasChanges
)
.frame(width: 300)
.presentationCompactAdaptation((.popover))
}
#else
.navigationDestination(
isPresented: $isInputActive
) {
HeightPickerView(
measurementSystem: measurementSystem,
selection: resolvedResult,
hasChanges: $hasChanges
)
}
#endif
}
.padding()
}
.preference(
key: QuestionRequiredPreferenceKey.self, value: isRequired
)
.preference(
key: QuestionAnsweredPreferenceKey.self, value: isAnswered)
}
}
private var isAnswered: Bool {
resolvedResult.wrappedValue != nil
}
}
struct HeightPickerView: View {
@Environment(\.dismiss) private var dismiss
@State private var firstSelection: Int
@State private var secondSelection: Int
private let footToCentimetersMultiplier: Double = 30.48
private let inchToCentimetersMultiplier: Double = 2.54
private let measurementSystem: MeasurementSystem
@Binding private var selection: Double?
@Binding private var hasChanges: Bool
init(
measurementSystem: MeasurementSystem,
selection: Binding<Double?>,
hasChanges: Binding<Bool>
) {
self.measurementSystem = measurementSystem
self._selection = selection
self._hasChanges = hasChanges
guard let centimeters = selection.wrappedValue else {
firstSelection = 0
secondSelection = 0
return
}
if Self.usesMetricSystem(measurementSystem: measurementSystem) == false
{
let (feet, inches) = convertCentimetersToFeetAndInches(centimeters)
firstSelection = feet
secondSelection = inches
} else {
firstSelection = Int(centimeters)
secondSelection = 0
}
}
private var upperValue: Int {
if measurementSystem == .USC {
return 10
} else {
return 300
}
}
private var secondaryUpperValue: Int {
// Numbers up to 1 foot or 12 inches
return 12
}
private var primaryUnit: String {
if measurementSystem == .USC {
return "ft"
} else {
return "cm"
}
}
private var secondaryUnit: String {
return "in"
}
var body: some View {
HStack(spacing: .zero) {
Picker(selection: $firstSelection) {
ForEach(0..<upperValue, id: \.self) { i in
Text("\(i) \(primaryUnit)")
.tag(i)
}
} label: {
Text(primaryUnit)
}
.pickerStyle(.wheel)
.onChange(of: firstSelection) { _, _ in
hasChanges = true
selection = standardizedHeight(
(firstSelection, secondSelection))
}
if measurementSystem == .USC {
Picker(selection: $secondSelection) {
ForEach(0..<secondaryUpperValue, id: \.self) { i in
Text("\(i) \(secondaryUnit)")
.tag(i)
}
} label: {
Text(secondaryUnit)
}
.pickerStyle(.wheel)
.onChange(of: secondSelection) { _, _ in
hasChanges = true
selection = standardizedHeight(
(firstSelection, secondSelection))
}
}
}
}
private func standardizedHeight(_ height: (Int, Int)) -> Double {
if Self.usesMetricSystem(measurementSystem: measurementSystem) == false
{
let centimeters =
(Double(height.0) * footToCentimetersMultiplier)
+ (Double(height.1) * inchToCentimetersMultiplier)
return centimeters
} else {
return Double(height.0)
}
}
private static func usesMetricSystem(measurementSystem: MeasurementSystem)
-> Bool
{
switch measurementSystem {
case .USC:
return false
case .local:
if Locale.current.measurementSystem == .us {
return false
} else {
return true
}
case .metric:
return true
}
}
}
@available(iOS 18.0, *)
#Preview {
@Previewable @State var selection: Double? = 162
NavigationStack {
HeightQuestion(
id: UUID().uuidString,
title: "Height question here",
detail: nil,
measurementSystem: .USC,
height: $selection
)
}
}
@@ -0,0 +1,567 @@
/*
Copyright (c) 2024, 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 SwiftUI
/// An image choice.
public struct ImageChoice: Identifiable, Equatable {
/// The unique identifier for this image choice.
public let id: String
/// The image for the unselected state.
public let normalImage: UIImage
/// The image for the selected state.
public let selectedImage: UIImage?
/// The text that describes the image.
public let text: String
let value: ResultValue
/// Initializes an instance of ``ImageChoice`` with the provided configuration.
/// - Parameters:
/// - normalImage: The image for the unselected state.
/// - selectedImage: The image for the selected state.
/// - text: The text that describes the image.
/// - value: The selection value for this image.
public init(
normalImage: UIImage,
selectedImage: UIImage?,
text: String,
value: Int
) {
self.init(
normalImage: normalImage,
selectedImage: selectedImage,
text: text,
value: .int(value)
)
}
/// Initializes an instance of ``ImageChoice`` with the provided configuration.
/// - Parameters:
/// - normalImage: The image for the unselected state.
/// - selectedImage: The image for the selected state.
/// - text: The text that describes the image.
/// - value: The selection value for this image.
public init(
normalImage: UIImage,
selectedImage: UIImage?,
text: String,
value: String
) {
self.init(
normalImage: normalImage,
selectedImage: selectedImage,
text: text,
value: .string(value)
)
}
/// Initializes an instance of ``ImageChoice`` with the provided configuration.
/// - Parameters:
/// - normalImage: The image for the unselected state.
/// - selectedImage: The image for the selected state.
/// - text: The text that describes the image.
/// - value: The selection value for this image.
public init(
normalImage: UIImage,
selectedImage: UIImage?,
text: String,
value: Date
) {
self.init(
normalImage: normalImage,
selectedImage: selectedImage,
text: text,
value: .date(value)
)
}
private init(
normalImage: UIImage,
selectedImage: UIImage?,
text: String,
value: ResultValue
) {
self.id = String(describing: value)
self.normalImage = normalImage
self.selectedImage = selectedImage
self.text = text
self.value = value
}
public static func == (lhs: ImageChoice, rhs: ImageChoice) -> Bool {
return lhs.id == rhs.id && lhs.text == rhs.text
}
}
/// Represents the number of of choices that can be selected.
public enum ChoiceSelectionLimit {
/// Allows for the selection of only one choice.
case single
/// Allows for the selection of more than one choice.
case multiple
}
/// A question that allows for image input.
public struct ImageChoiceQuestion: View {
@EnvironmentObject
private var managedFormResult: ResearchFormResult
@Environment(\.questionContext)
private var questionContext: QuestionContext
@Environment(\.questionRequired)
private var isRequired: Bool
@State
private var managedResults: [ResultValue]?
private let id: String
private let title: String
private let detail: String?
private let choices: [ImageChoice]
private let choiceSelectionLimit: ChoiceSelectionLimit
private let vertical: Bool
private let selection: StateManagementType<[ResultValue]?>
private var resolvedResult: Binding<[ResultValue]?> {
switch selection {
case .automatic(let key):
return Binding(
get: {
let result: [ResultValue]?
if let managedResults {
result = managedResults
} else {
switch questionContext {
case .formEmbedded:
result = managedFormResult.resultForStep(key: key) ?? []
case .standalone:
result = []
}
}
return result
},
set: {
managedResults = $0
if case .formEmbedded = questionContext {
managedFormResult.setResultForStep(.image($0), key: key)
}
}
)
case .manual(let value):
return value
}
}
/// Initializes an instance of ``ImageChoiceQuestion`` with the provided configuration.
/// - Parameters:
/// - id: The unique identifier for this question.
/// - title: The title for this question.
/// - detail: The details for this question.
/// - choices: The image choices for this question.
/// - choiceSelectionLimit: The choice selection limit for this image choice question.
/// - vertical: Whether or not the images should be displayed horizontally or vertically.
/// - imageValue: The selected value associated with one of the image choices.
public init(
id: String,
title: String,
detail: String?,
choices: [ImageChoice],
choiceSelectionLimit: ChoiceSelectionLimit,
vertical: Bool,
imageValue: Binding<[Int]?>
) {
self.init(
id: id,
title: title,
detail: detail,
choices: choices,
choiceSelectionLimit: choiceSelectionLimit,
vertical: vertical,
selection: .init(
get: {
guard let integers = imageValue.wrappedValue else {
return nil
}
return integers.map { .int($0) }
},
set: { newValues in
guard let newValues else {
imageValue.wrappedValue = nil
return
}
imageValue.wrappedValue = newValues.compactMap {
resultValue in
guard case let .int(value) = resultValue else {
return nil
}
return value
}
}
)
)
}
/// Initializes an instance of ``ImageChoiceQuestion`` with the provided configuration.
/// - Parameters:
/// - id: The unique identifier for this question.
/// - title: The title for this question.
/// - detail: The details for this question.
/// - choices: The image choices for this question.
/// - choiceSelectionLimit: The choice selection limit for this image choice question.
/// - vertical: Whether or not the images should be displayed horizontally or vertically.
/// - imageValue: The selected value associated with one of the image choices.
public init(
id: String,
title: String,
detail: String?,
choices: [ImageChoice],
choiceSelectionLimit: ChoiceSelectionLimit,
vertical: Bool,
imageValue: Binding<[String]?>
) {
self.init(
id: id,
title: title,
detail: detail,
choices: choices,
choiceSelectionLimit: choiceSelectionLimit,
vertical: vertical,
selection: .init(
get: {
guard let strings = imageValue.wrappedValue else {
return nil
}
return strings.map { .string($0) }
},
set: { newValues in
guard let newValues else {
imageValue.wrappedValue = nil
return
}
imageValue.wrappedValue = newValues.compactMap {
resultValue in
guard case let .string(value) = resultValue else {
return nil
}
return value
}
}
)
)
}
/// Initializes an instance of ``ImageChoiceQuestion`` with the provided configuration.
/// - Parameters:
/// - id: The unique identifier for this question.
/// - title: The title for this question.
/// - detail: The details for this question.
/// - choices: The image choices for this question.
/// - choiceSelectionLimit: The choice selection limit for this image choice question.
/// - vertical: Whether or not the images should be displayed horizontally or vertically.
/// - imageValue: The selected value associated with one of the image choices.
public init(
id: String,
title: String,
detail: String?,
choices: [ImageChoice],
choiceSelectionLimit: ChoiceSelectionLimit,
vertical: Bool,
imageValue: Binding<[Date]?>
) {
self.init(
id: id,
title: title,
detail: detail,
choices: choices,
choiceSelectionLimit: choiceSelectionLimit,
vertical: vertical,
selection: .init(
get: {
guard let dates = imageValue.wrappedValue else {
return nil
}
return dates.map { .date($0) }
},
set: { newValues in
guard let newValues else {
imageValue.wrappedValue = nil
return
}
imageValue.wrappedValue = newValues.compactMap {
resultValue in
guard case let .date(value) = resultValue else {
return nil
}
return value
}
}
)
)
}
private init(
id: String,
title: String,
detail: String?,
choices: [ImageChoice],
choiceSelectionLimit: ChoiceSelectionLimit,
vertical: Bool,
selection: Binding<[ResultValue]?>
) {
self.id = id
self.title = title
self.detail = detail
self.choices = choices
self.choiceSelectionLimit = choiceSelectionLimit
self.vertical = vertical
self.selection = .manual(selection)
}
/// Initializes an instance of ``ImageChoiceQuestion`` with the provided configuration.
/// - Parameters:
/// - id: The unique identifier for this question.
/// - title: The title for this question.
/// - detail: The details for this question.
/// - choices: The image choices for this question.
/// - choiceSelectionLimit: The choice selection limit for this image choice question.
/// - vertical: Whether or not the images should be displayed horizontally or vertically.
public init(
id: String,
title: String,
detail: String?,
choices: [ImageChoice],
choiceSelectionLimit: ChoiceSelectionLimit,
vertical: Bool
) {
self.id = id
self.title = title
self.detail = detail
self.choices = choices
self.choiceSelectionLimit = choiceSelectionLimit
self.vertical = vertical
self.selection = .automatic(key: .imageChoice(id: id))
}
public var body: some View {
QuestionCard {
Question(title: title, detail: detail) {
VStack {
if choiceSelectionLimit == .multiple {
multipleSelectionHeader()
}
if vertical {
VStack {
imageChoices()
}
.padding()
} else {
HStack {
imageChoices()
}
.padding()
}
selectionText()
.frame(maxWidth: .infinity, alignment: .center)
.padding()
}
}
.preference(
key: QuestionRequiredPreferenceKey.self, value: isRequired
)
.preference(
key: QuestionAnsweredPreferenceKey.self, value: isAnswered)
}
}
private var isAnswered: Bool {
if let resultArray = resolvedResult.wrappedValue, !resultArray.isEmpty {
return true
}
return false
}
@ViewBuilder
private func selectionText() -> some View {
if let result = resolvedResult.wrappedValue,
result.isEmpty == false
{
let strings: [String] = {
var strings: [String] = []
for i in result {
if let choice = choices.first(where: { $0.value == i }) {
strings.append(choice.text)
}
}
return strings
}()
Text(strings.joined(separator: ", "))
.foregroundStyle(.primary)
} else {
Text("Tap to select")
.foregroundStyle(.secondary)
}
}
@ViewBuilder
private func imageChoices() -> some View {
ForEach(choices, id: \.id) { choice in
Button {
if let index = resolvedResult.wrappedValue?.firstIndex(where: {
$0 == choice.value
}) {
resolvedResult.wrappedValue?.remove(at: index)
} else {
resolvedResult.wrappedValue?.append(choice.value)
}
} label: {
if let result = resolvedResult.wrappedValue,
result.contains(where: { $0 == choice.value })
{
Image(uiImage: choice.selectedImage ?? choice.normalImage)
.resizable()
.imageSizeConstraints()
.scaledToFit()
.padding()
.frame(maxWidth: .infinity)
.background(
choice.selectedImage == nil
? Color.choice(for: .systemGray5) : Color.clear
)
.cornerRadius(24)
.imageChoiceHoverEffect()
} else {
Image(uiImage: choice.normalImage)
.resizable()
.imageSizeConstraints()
.scaledToFit()
.padding()
.frame(maxWidth: .infinity)
.cornerRadius(24)
.imageChoiceHoverEffect()
.contentShape(Capsule())
}
}
.buttonStyle(.plain)
}
}
@ViewBuilder
private func multipleSelectionHeader() -> some View {
#if !os(watchOS)
Text("SELECT ALL THAT APPLY")
.font(.caption)
.fontWeight(.semibold)
.foregroundStyle(.secondary)
.frame(maxWidth: .infinity, alignment: .leading)
.padding([.horizontal, .top])
Divider()
#else
Text("SELECT ALL THAT APPLY")
.lineLimit(1)
.minimumScaleFactor(0.5)
.font(.caption2)
.fontWeight(.semibold)
.foregroundStyle(.secondary)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal)
#endif
}
}
extension View {
fileprivate func imageChoiceHoverEffect() -> some View {
self
#if !os(watchOS)
.contentShape(.hoverEffect, RoundedRectangle(cornerRadius: 24))
.hoverEffect()
#endif
}
fileprivate func imageSizeConstraints() -> some View {
self
#if !os(watchOS)
.frame(
minWidth: 10, maxWidth: 50,
minHeight: 10, maxHeight: 50
)
#else
.frame(
minWidth: 10, maxWidth: 30,
minHeight: 10, maxHeight: 30
)
#endif
}
}
#Preview {
@Previewable @State var selection: [Int]? = []
ScrollView {
ImageChoiceQuestion(
id: UUID().uuidString,
title: "Which do you prefer?",
detail: nil,
choices: [
ImageChoice(
normalImage: UIImage(systemName: "carrot")!,
selectedImage: nil,
text: "carrot",
value: 0
),
ImageChoice(
normalImage: UIImage(systemName: "birthday.cake")!,
selectedImage: nil,
text: "cake",
value: 1
),
],
choiceSelectionLimit: .multiple,
vertical: false,
imageValue: $selection
)
}
}
@@ -0,0 +1,352 @@
/*
Copyright (c) 2024, 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 SwiftUI
/// A question that allows for multiple choice input.
public struct MultipleChoiceQuestion: View {
@EnvironmentObject
private var managedFormResult: ResearchFormResult
@Environment(\.questionContext)
private var questionContext: QuestionContext
@Environment(\.questionRequired)
private var isRequired: Bool
@State
private var managedResults: [ResultValue]?
private var resolvedResult: Binding<[ResultValue]?> {
switch selection {
case let .automatic(key: key):
return Binding(
get: {
let result: [ResultValue]?
if let managedResults {
result = managedResults
} else {
switch questionContext {
case .formEmbedded:
result = managedFormResult.resultForStep(key: key) ?? nil
case .standalone:
result = nil
}
}
return result
},
set: {
managedResults = $0
if case .formEmbedded = questionContext {
managedFormResult.setResultForStep(.multipleChoice($0), key: key)
}
}
)
case let .manual(value):
return value
}
}
private let id: String
private let title: String
private let detail: String?
private let choices: [TextChoice]
private let choiceSelectionLimit: ChoiceSelectionLimit
private let selection: StateManagementType<[ResultValue]?>
/// Initializes an instance of ``MultipleChoiceQuestion`` with the provided configuration for an integer result.
/// - Parameters:
/// - id: The unique identifier associated with this question.
/// - title: The title for this question.
/// - detail: The details for this question.
/// - choices: The choices that can be selected for this question.
/// - choiceSelectionLimit: The selection type for this question.
/// - selection: The selected choices, where the choice values are represented as integers.
public init(
id: String,
title: String,
detail: String? = nil,
choices: [TextChoice],
choiceSelectionLimit: ChoiceSelectionLimit,
selection: Binding<[Int]?>
) {
self.init(
id: id,
title: title,
detail: detail,
choices: choices,
choiceSelectionLimit: choiceSelectionLimit,
selection: .init(
get: {
guard let integers = selection.wrappedValue else {
return nil
}
return integers.map { .int($0) }
},
set: { newValues in
guard let newValues else {
selection.wrappedValue = nil
return
}
selection.wrappedValue = newValues.compactMap {
resultValue in
guard case let .int(value) = resultValue else {
return nil
}
return value
}
}
)
)
}
/// Initializes an instance of ``MultipleChoiceQuestion`` with the provided configuration for a string result.
/// - Parameters:
/// - id: The unique identifier associated with this question.
/// - title: The title for this question.
/// - detail: The details for this question.
/// - choices: The choices that can be selected for this question.
/// - choiceSelectionLimit: The selection type for this question.
/// - selection: The selected choices, where the choice values are represented as strings.
public init(
id: String,
title: String,
detail: String? = nil,
choices: [TextChoice],
choiceSelectionLimit: ChoiceSelectionLimit,
selection: Binding<[String]?>
) {
self.init(
id: id,
title: title,
detail: detail,
choices: choices,
choiceSelectionLimit: choiceSelectionLimit,
selection: .init(
get: {
guard let strings = selection.wrappedValue else {
return nil
}
return strings.map { .string($0) }
},
set: { newValues in
guard let newValues else {
selection.wrappedValue = nil
return
}
selection.wrappedValue = newValues.compactMap {
resultValue in
guard case let .string(value) = resultValue else {
return nil
}
return value
}
}
)
)
}
/// Initializes an instance of ``MultipleChoiceQuestion`` with the provided configuration for a date result.
/// - Parameters:
/// - id: The unique identifier associated with this question.
/// - title: The title for this question.
/// - detail: The details for this question.
/// - choices: The choices that can be selected for this question.
/// - choiceSelectionLimit: The selection type for this question.
/// - selection: The selected choices, where the choice values are represented as dates.
public init(
id: String,
title: String,
detail: String? = nil,
choices: [TextChoice],
choiceSelectionLimit: ChoiceSelectionLimit,
selection: Binding<[Date]?>
) {
self.init(
id: id,
title: title,
detail: detail,
choices: choices,
choiceSelectionLimit: choiceSelectionLimit,
selection: .init(
get: {
guard let dates = selection.wrappedValue else {
return nil
}
return dates.map { .date($0) }
},
set: { newValues in
guard let newValues else {
selection.wrappedValue = nil
return
}
selection.wrappedValue = newValues.compactMap {
resultValue in
guard case let .date(value) = resultValue else {
return nil
}
return value
}
}
)
)
}
private init(
id: String,
title: String,
detail: String? = nil,
choices: [TextChoice],
choiceSelectionLimit: ChoiceSelectionLimit,
selection: Binding<[ResultValue]?>
) {
self.id = id
self.title = title
self.detail = detail
self.choices = choices
self.choiceSelectionLimit = choiceSelectionLimit
self.selection = .manual(selection)
}
/// Initializes an instance of ``MultipleChoiceQuestion`` with the provided configuration and manages a binding internally.
/// - Parameters:
/// - id: The unique identifier associated with this question.
/// - title: The title for this question.
/// - detail: The details for this question.
/// - choices: The choices that can be selected for this question.
/// - choiceSelectionLimit: The selection type for this question.
public init(
id: String,
title: String,
detail: String? = nil,
choices: [TextChoice],
choiceSelectionLimit: ChoiceSelectionLimit
) {
self.id = id
self.title = title
self.detail = detail
self.choices = choices
self.choiceSelectionLimit = choiceSelectionLimit
self.selection = .automatic(key: .multipleChoice(id: id))
}
public var body: some View {
QuestionCard {
Question(title: title, detail: detail) {
ForEach(Array(choices.enumerated()), id: \.offset) {
index, option in
VStack(spacing: .zero) {
if index != 0 {
Divider()
}
TextChoiceOption(
title: Text(option.choiceText),
isSelected: isSelected(option)
) {
choiceSelected(option)
}
.padding(.horizontal, 8)
#if !os(watchOS)
.contentShape(
.hoverEffect, RoundedRectangle(cornerRadius: 12)
)
.hoverEffect()
#endif
.padding(.horizontal, -8)
}
}
}
.preference(
key: QuestionRequiredPreferenceKey.self, value: isRequired
)
.preference(
key: QuestionAnsweredPreferenceKey.self, value: isAnswered)
}
}
private var isAnswered: Bool {
if let resultArray = resolvedResult.wrappedValue, !resultArray.isEmpty {
return true
}
return false
}
private func isSelected(_ option: TextChoice) -> Bool {
resolvedResult.wrappedValue?.contains(where: { choice in
choice == option.value
}) ?? false
}
private func choiceSelected(_ option: TextChoice) {
if let resultArray = resolvedResult.wrappedValue,
let index = resultArray.firstIndex(where: { $0 == option.value })
{
resolvedResult.wrappedValue?.remove(at: index)
} else {
switch choiceSelectionLimit {
case .single:
resolvedResult.wrappedValue = [option.value]
case .multiple:
resolvedResult.wrappedValue =
(resolvedResult.wrappedValue ?? []) + [option.value]
}
}
}
}
struct MultipleChoiceQuestionView_Previews: PreviewProvider {
static var previews: some View {
ZStack {
Color.choice(for: .secondaryBackground)
.ignoresSafeArea()
MultipleChoiceQuestion(
id: UUID().uuidString,
title: "Which do you prefer?",
choices: [
TextChoice(id: "a", choiceText: "Option A", value: 0),
TextChoice(id: "b", choiceText: "Option B", value: 1),
TextChoice(id: "c", choiceText: "Option C", value: 2),
],
choiceSelectionLimit: .multiple,
selection: .constant([0])
)
.padding(.horizontal)
}
}
}
@@ -0,0 +1,77 @@
/*
Copyright (c) 2024, 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 SwiftUI
struct TextChoiceOption: View {
var isSelected: Bool
var title: Text
var selection: () -> Void
public init(title: Text, isSelected: Bool, selection: @escaping () -> Void)
{
self.title = title
self.selection = selection
self.isSelected = isSelected
}
@ViewBuilder
public var body: some View {
HStack {
title
.font(.subheadline)
.foregroundStyle(Color.choice(for: .label))
.frame(maxWidth: .infinity, alignment: .leading)
Image(systemName: isSelected ? "checkmark.circle.fill" : "circle")
.imageScale(.large)
.foregroundColor(isSelected ? .accentColor : deselectedCheckmarkColor)
.font(.body)
}
.padding(.vertical, 8)
.padding(.horizontal, 16)
.contentShape(Rectangle())
.onTapGesture {
selection()
}
}
private var deselectedCheckmarkColor: Color {
#if os(iOS)
Color(.systemGray3)
#else
Color(.lightGray)
#endif
}
}
@@ -0,0 +1,205 @@
/*
Copyright (c) 2024, 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 Foundation
import SwiftUI
/// A question that allows for numeric input.
@available(watchOS, unavailable)
public struct NumericQuestion<Header: View>: View {
@EnvironmentObject
private var managedFormResult: ResearchFormResult
private enum FocusTarget {
case numericQuestion
}
private let id: String
private let header: Header
private let prompt: String?
@FocusState private var focusTarget: FocusTarget?
private let selection: StateManagementType<Double?>
@Environment(\.questionContext)
private var questionContext: QuestionContext
@Environment(\.questionRequired)
private var isRequired: Bool
@State
private var managedNumber: Double?
private var resolvedResult: Binding<Double?> {
switch selection {
case let .automatic(key: key):
return Binding(
get: {
let number: Double?
if let managedNumber {
number = managedNumber
} else {
switch questionContext {
case .formEmbedded:
number = managedFormResult.resultForStep(key: key) ?? nil
case .standalone:
number = nil
}
}
return number
},
set: {
managedNumber = $0
if case .formEmbedded = questionContext {
managedFormResult.setResultForStep(.numeric($0), key: key)
}
}
)
case let .manual(value):
return value
}
}
public var body: some View {
QuestionCard {
Question(
header: {
header
},
content: {
TextField(
"", value: resolvedResult, format: .number,
prompt: placeholder
)
#if !os(watchOS) && !os(macOS)
.keyboardType(.decimalPad)
.focused($focusTarget, equals: .numericQuestion)
#endif
.doneKeyboardToolbar(
condition: {
focusTarget == .numericQuestion
},
action: {
focusTarget = nil
}
)
.padding()
}
)
.preference(
key: QuestionRequiredPreferenceKey.self, value: isRequired
)
.preference(
key: QuestionAnsweredPreferenceKey.self, value: isAnswered)
}
}
private var placeholder: Text? {
if let prompt {
return Text(prompt)
}
return nil
}
private var isAnswered: Bool {
resolvedResult.wrappedValue != nil
}
}
@available(watchOS, unavailable)
extension NumericQuestion where Header == QuestionHeader {
/// Initializes an instance of ``NumericQuestion`` with the provided configuration.
/// - Parameters:
/// - id: The unique identifier for this numeric question.
/// - number: The entered number.
/// - title: The title for this question.
/// - detail: The details for this question.
/// - prompt: The prompt that informs the user.
public init(
id: String,
number: Binding<Double?>,
title: String,
detail: String? = nil,
prompt: String?
) {
self.id = id
header = QuestionHeader(title: title, detail: detail)
self.prompt = prompt
self.selection = .manual(number)
}
/// Initializes an instance of ``NumericQuestion`` with the provided configuration.
/// - Parameters:
/// - id: The unique identifier for this question.
/// - number: The entered number.
/// - title: The title for this question.
/// - detail: The details for this question.
/// - prompt: The prompt that informs the user.
public init(
id: String,
number: Decimal? = nil,
title: String,
detail: String? = nil,
prompt: String?
) {
self.id = id
header = QuestionHeader(title: title, detail: detail)
self.prompt = prompt
self.selection = .automatic(key: .numeric(id: id))
}
}
@available(watchOS, unavailable)
struct NumericQuestionView_Previews: PreviewProvider {
static var previews: some View {
ZStack {
Color.choice(for: .secondaryBackground)
.ignoresSafeArea()
ScrollView {
NumericQuestion(
id: UUID().uuidString,
number: .constant(22.0),
title: "How old are you?",
detail: nil,
prompt: "Tap to enter age"
)
.padding(.horizontal)
}
}
}
}
@@ -0,0 +1,797 @@
/*
Copyright (c) 2024, 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 Foundation
import SwiftUI
/// A configuration representing the kind of `SliderQuestion` to be used.
enum SliderQuestionConfiguration {
@available(watchOS, unavailable)
case textChoice([TextChoice])
case integerRange(ClosedRange<Int>)
case doubleRange(ClosedRange<Double>)
}
/// A question that allows for integer, double, or text input by means of a slider.
public struct SliderQuestion: View {
private let id: String
private let title: String
private let detail: String?
private let sliderQuestionConfiguration: SliderQuestionConfiguration
private let step: Double
private enum ScaleSelectionBindingValue: Equatable {
static func == (
lhs: SliderQuestion.ScaleSelectionBindingValue,
rhs: SliderQuestion.ScaleSelectionBindingValue
) -> Bool {
switch lhs {
case .textChoice(let binding):
guard case .textChoice(let rhsBinding) = rhs else {
return false
}
return rhsBinding.wrappedValue.id == binding.wrappedValue.id
case .int(let binding):
guard case .int(let rhsBinding) = rhs else {
return false
}
return rhsBinding.wrappedValue == binding.wrappedValue
case .double(let binding):
guard case .double(let rhsBinding) = rhs else {
return false
}
return rhsBinding.wrappedValue == binding.wrappedValue
}
}
case int(Binding<Int?>)
case double(Binding<Double?>)
@available(watchOS, unavailable)
case textChoice(Binding<TextChoice>)
}
private enum ScaleSelectionPrimitiveValue: Equatable {
static func == (
lhs: SliderQuestion.ScaleSelectionPrimitiveValue,
rhs: SliderQuestion.ScaleSelectionPrimitiveValue
) -> Bool {
switch lhs {
case .textChoice(let lhsValue):
guard case .textChoice(let rhsValue) = rhs else {
return false
}
return lhsValue.id == rhsValue.id
case .int(let lhsValue):
guard case .int(let rhsValue) = rhs else {
return false
}
return lhsValue == rhsValue
case .double(let lhsValue):
guard case .double(let rhsValue) = rhs else {
return false
}
return lhsValue == rhsValue
}
}
case int(Int)
case double(Double)
@available(watchOS, unavailable)
case textChoice(TextChoice)
}
@EnvironmentObject
private var managedFormResult: ResearchFormResult
@Environment(\.questionContext)
private var questionContext: QuestionContext
@Environment(\.questionRequired)
private var isRequired: Bool
@State
private var managedSelectedValue: Double?
@State
private var isWaitingForUserFeedback: Bool = true
private let stateManagementType: StateManagementType<Double>
private var resolvedBinding: Binding<ScaleSelectionBindingValue> {
let resolvedBinding: Binding<ScaleSelectionBindingValue>
switch stateManagementType {
case .automatic:
resolvedBinding = resolvedManagedResult
case .manual:
resolvedBinding = .init(
get: {
clientManagedSelection
},
// This binding isn't invoked with respect to `set` because another binding is returned in `get`.
set: { _ in }
)
}
return resolvedBinding
}
// Actual underlying value of the slider
@State
private var sliderUIValue: Double
private var resolvedManagedResult: Binding<ScaleSelectionBindingValue> {
.init(
get: {
// `resolvedManagedResult` is only applicable in the `automatic` case,
// as implemented in `resolvedBinding`.
guard case .automatic(let key) = stateManagementType else {
// Return dummy binding since this should never be invoked.
return .int(
.init(
get: {
0
},
set: { _ in }
)
)
}
let scaleSelectionBinding: ScaleSelectionBindingValue
switch sliderQuestionConfiguration {
#if !os(watchOS)
case .textChoice(let multipleChoiceOptions):
scaleSelectionBinding = .textChoice(
.init(
get: {
let textChoice: TextChoice
if let managedSelectedValue {
textChoice = multipleChoiceOptions[Int(managedSelectedValue)]
} else {
switch questionContext {
case .formEmbedded:
if let selectedIndex = managedFormResult.resultForStep(key: key) {
textChoice = multipleChoiceOptions[Int(selectedIndex)]
} else {
textChoice = TextChoice(id: "", choiceText: "", value: 0)
}
case .standalone:
textChoice = TextChoice(id: "", choiceText: "", value: 0)
}
}
return textChoice
},
set: { multipleChoiceOption in
guard
let index =
multipleChoiceOptions.firstIndex(
where: {
$0.id
== multipleChoiceOption
.id
})
else {
return
}
managedSelectedValue = Double(index)
if case .formEmbedded = questionContext {
managedFormResult.setResultForStep(
.scale(Double(index)), key: key)
}
}
)
)
#endif
case .integerRange:
scaleSelectionBinding = .int(
.init(
get: {
let integerValue: Int?
if let managedSelectedValue {
integerValue = Int(managedSelectedValue)
} else {
switch questionContext {
case .formEmbedded:
if let value = managedFormResult.resultForStep(key: key) {
integerValue = Int(value)
} else {
integerValue = 0
}
case .standalone:
integerValue = 0
}
}
return integerValue
},
set: {
guard let result = $0 else { return }
managedSelectedValue = Double(result)
if case .formEmbedded = questionContext {
managedFormResult.setResultForStep(
.scale(Double(result)), key: key)
}
}
)
)
case .doubleRange:
scaleSelectionBinding = .double(
.init(
get: {
let doubleValue: Double
if let managedSelectedValue {
doubleValue = managedSelectedValue
} else {
switch questionContext {
case .formEmbedded:
if let value = managedFormResult.resultForStep(key: key) {
doubleValue = value
} else {
doubleValue = 0
}
case .standalone:
doubleValue = 0
}
}
return doubleValue
},
set: {
guard let result = $0 else { return }
managedSelectedValue = result
if case .formEmbedded = questionContext {
managedFormResult.setResultForStep(
.scale(result), key: key)
}
}
)
)
}
return scaleSelectionBinding
},
set: { newValue in
// `resolvedManagedResult` is only applicable in the `automatic` case,
// as implemented in `resolvedBinding`.
guard case .automatic(let key) = stateManagementType else {
return
}
switch (newValue, sliderQuestionConfiguration) {
#if !os(watchOS)
case let (
.textChoice(binding), .textChoice(multipleChoiceOptions)
):
let index =
multipleChoiceOptions.firstIndex(
where: { binding.wrappedValue.id == $0.id }
) ?? 0
managedSelectedValue = Double(index)
if case .formEmbedded = questionContext {
managedFormResult.setResultForStep(
.scale(Double(index)), key: key)
}
#endif
case let (.int(binding), .integerRange):
guard let result = binding.wrappedValue else { return }
managedSelectedValue = Double(result)
if case .formEmbedded = questionContext {
managedFormResult.setResultForStep(
.scale(Double(result)), key: key)
}
case let (.double(binding), .doubleRange):
guard let result = binding.wrappedValue else { return }
managedSelectedValue = result
if case .formEmbedded = questionContext {
managedFormResult.setResultForStep(.scale(result), key: key)
}
default:
break
}
}
)
}
private var clientManagedSelection: ScaleSelectionBindingValue
/// Initializes an instance of ``SliderQuestion`` with the provided configuration and manages the binding for double values internally.
/// - Parameters:
/// - id: The unique identifier for this question.
/// - title: The title for this question.
/// - detail: The details for this question.
/// - range: The range of selectable values for this question.
/// - step: The amount of change between each increment or decrement.
/// - selection: The selected value.
public init(
id: String,
title: String,
detail: String? = nil,
range: ClosedRange<Double>,
step: Double = 1.0,
selection: Double
) {
self.id = id
self.title = title
self.detail = detail
self.sliderQuestionConfiguration = .doubleRange(range)
self.step = step
self.clientManagedSelection = .double(
.init(get: { selection }, set: { _ in }))
self.stateManagementType = .automatic(key: StepResultKey(id: id))
self._sliderUIValue = State(wrappedValue: selection)
}
/// Initializes an instance of ``SliderQuestion`` with the provided configuration for double values.
/// - Parameters:
/// - id: The unique identifier for this question.
/// - title: The title for this question.
/// - detail: The details for this question.
/// - range: The range of selectable values for this question.
/// - step: The amount of change between each increment or decrement.
/// - selection: The selected value.
public init(
id: String,
title: String,
detail: String? = nil,
range: ClosedRange<Double>,
step: Double = 1.0,
selection: Binding<Double?>
) {
self.id = id
self.title = title
self.detail = detail
self.sliderQuestionConfiguration = .doubleRange(range)
self.step = step
self.clientManagedSelection = .double(selection)
self.stateManagementType = .manual(
.init(
get: {
Double(selection.wrappedValue ?? range.lowerBound)
},
set: { newValue in
selection.wrappedValue = newValue
}
)
)
self._sliderUIValue = State(
wrappedValue: selection.wrappedValue ?? range.lowerBound)
}
/// Initializes an instance of ``SliderQuestion`` with the provided configuration for integer values.
/// - Parameters:
/// - id: The unique identifier for this question.
/// - title: The title for this question.
/// - detail: The details for this question.
/// - range: The range of selectable values for this slider question.
/// - step: The amount of change between each increment or decrement.
/// - selection: The selected value.
public init(
id: String,
title: String,
detail: String? = nil,
range: ClosedRange<Int>,
step: Double = 1.0,
selection: Int?
) {
self.id = id
self.title = title
self.detail = detail
self.sliderQuestionConfiguration = .integerRange(range)
self.step = step
self.clientManagedSelection = .int(
.init(get: { selection }, set: { _ in }))
self.stateManagementType = .automatic(key: StepResultKey(id: id))
self._sliderUIValue = State(
wrappedValue: Double(selection ?? range.lowerBound))
}
/// Initializes an instance of ``SliderQuestion`` with the provided configuration for integer values.
/// - Parameters:
/// - id: The unique identifier for this question.
/// - title: The title for this question.
/// - detail: The details for this question.
/// - range: The range of selectable values for this slider question.
/// - step: The amount of change between each increment or decrement.
/// - selection: The selected value.
public init(
id: String,
title: String,
detail: String? = nil,
range: ClosedRange<Int>,
step: Double = 1.0,
selection: Binding<Int?>
) {
self.id = id
self.title = title
self.detail = detail
self.sliderQuestionConfiguration = .integerRange(range)
self.step = step
self.clientManagedSelection = .int(selection)
self.stateManagementType = .manual(
.init(
get: {
Double(selection.wrappedValue ?? range.lowerBound)
},
set: { newValue in
selection.wrappedValue = Int(newValue)
}
)
)
self._sliderUIValue = State(
wrappedValue: Double(selection.wrappedValue ?? range.lowerBound))
}
/// Initializes an instance of ``SliderQuestion`` with the provided configuration for text values.
/// - Parameters:
/// - id: The unique identifier for this question.
/// - title: The title for this question.
/// - detail: The details for this question.
/// - multipleChoiceOptions: The text options that this slider can represent.
/// - selection: The selected value.
@available(watchOS, unavailable)
public init(
id: String,
title: String,
detail: String? = nil,
multipleChoiceOptions: [TextChoice],
selection: TextChoice
) {
self.id = id
self.title = title
self.detail = detail
self.sliderQuestionConfiguration = .textChoice(multipleChoiceOptions)
self.step = 1.0
self.clientManagedSelection = .textChoice(
.init(get: { selection }, set: { _ in }))
self.stateManagementType = .automatic(key: StepResultKey(id: id))
let index = multipleChoiceOptions.firstIndex(where: {
selection.id == $0.id
})
let sliderValue = index ?? Array<TextChoice>.Index(0.0)
self._sliderUIValue = State(wrappedValue: Double(sliderValue))
}
/// Initializes an instance of ``SliderQuestion`` with the provided configuration for text values.
/// - Parameters:
/// - id: The unique identifier for this question.
/// - title: The title for this question.
/// - detail: The details for this question.
/// - multipleChoiceOptions: The text options that this slider can represent.
/// - selection: The selected value.
@available(watchOS, unavailable)
public init(
id: String,
title: String,
detail: String? = nil,
multipleChoiceOptions: [TextChoice],
selection: Binding<TextChoice>
) {
self.id = id
self.title = title
self.detail = detail
self.sliderQuestionConfiguration = .textChoice(multipleChoiceOptions)
self.step = 1.0
self.clientManagedSelection = .textChoice(selection)
self.stateManagementType = .manual(
.init(
get: {
guard
let index = multipleChoiceOptions.firstIndex(
where: { selection.wrappedValue.id == $0.id }
)
else {
return 0
}
return Double(index)
},
set: { newValue in
let index = Int(newValue)
guard index >= 0, index < multipleChoiceOptions.count else {
return
}
let newSelection = multipleChoiceOptions[index]
selection.wrappedValue = newSelection
}
)
)
let index = multipleChoiceOptions.firstIndex(where: {
selection.id == $0.id
})
let sliderValue = index ?? Array<TextChoice>.Index(0.0)
self._sliderUIValue = State(wrappedValue: Double(sliderValue))
}
public var body: some View {
QuestionCard {
Question(title: title, detail: detail) {
scaleView(selectionConfiguration: sliderQuestionConfiguration)
.onChange(of: sliderUIValue) { oldValue, newValue in
isWaitingForUserFeedback = false
switch resolvedBinding.wrappedValue {
case .double(let doubleBinding):
doubleBinding.wrappedValue = newValue
case .int(let intBinding):
intBinding.wrappedValue = Int(newValue)
case .textChoice(let textChoiceBinding):
guard
case let .textChoice(array) =
sliderQuestionConfiguration
else {
return
}
let index = Int(newValue)
textChoiceBinding.wrappedValue = array[index]
}
}
.onChange(of: clientManagedSelection) {
oldValue, newValue in
switch newValue {
case .textChoice(let binding):
guard
case let .textChoice(array) =
sliderQuestionConfiguration
else {
return
}
let selectedIndex =
array.firstIndex(where: {
$0.id == binding.wrappedValue.id
}) ?? 0
sliderUIValue = Double(selectedIndex)
case .int(let binding):
if let value = binding.wrappedValue {
sliderUIValue = Double(value)
}
case .double(let binding):
if let value = binding.wrappedValue {
sliderUIValue = value
}
}
}
.padding()
.onAppear {
guard case .automatic(let key) = stateManagementType,
case .formEmbedded = questionContext,
let sliderValue = managedFormResult.resultForStep(
key: key)
else {
return
}
sliderUIValue = sliderValue
}
}
.preference(
key: QuestionRequiredPreferenceKey.self, value: isRequired
)
.preference(
key: QuestionAnsweredPreferenceKey.self,
value: !isWaitingForUserFeedback)
}
}
@ViewBuilder
private func scaleView(selectionConfiguration: SliderQuestionConfiguration)
-> some View
{
VStack {
Text("\(value(for: selectionConfiguration))")
.font(.title2)
.fontWeight(.bold)
.foregroundStyle(.sliderValueForegroundStyle)
slider(selectionConfiguration: selectionConfiguration)
}
}
@ViewBuilder
private func sliderLabel(_ value: String) -> some View {
Text(value)
.fixedSize()
.foregroundStyle(Color.choice(for: .label))
.font(.subheadline)
.fontWeight(.bold)
}
@ViewBuilder
private func slider(selectionConfiguration: SliderQuestionConfiguration)
-> some View
{
Slider(
value: $sliderUIValue,
in: sliderBounds(for: selectionConfiguration),
step: sliderStep(for: selectionConfiguration)
) {
Text("Slider for \(selectionConfiguration)")
} minimumValueLabel: {
sliderLabel(
"\(minimumValueDescription(for: selectionConfiguration))")
} maximumValueLabel: {
sliderLabel(
"\(maximumValueDescription(for: selectionConfiguration))")
}
}
private func value(for selectionConfiguration: SliderQuestionConfiguration)
-> any CustomStringConvertible
{
let value: any CustomStringConvertible
switch selectionConfiguration {
case .integerRange:
value = Int(sliderUIValue)
case .doubleRange:
value = String(format: "%.1f", sliderUIValue)
case .textChoice(let choices):
value = choices[Int(sliderUIValue)].choiceText
}
return value
}
private func sliderBounds(
for selectionConfiguration: SliderQuestionConfiguration
) -> ClosedRange<Double> {
let sliderBounds: ClosedRange<Double>
switch selectionConfiguration {
case .textChoice(let choices):
sliderBounds = 0...Double(choices.count - 1)
case .integerRange(let range):
sliderBounds = Double(range.lowerBound)...Double(range.upperBound)
case .doubleRange(let range):
sliderBounds = range.lowerBound...range.upperBound
}
return sliderBounds
}
private func sliderStep(
for selectionConfiguration: SliderQuestionConfiguration
) -> Double.Stride {
let sliderStep: Double.Stride
switch selectionConfiguration {
case .textChoice:
sliderStep = 1
case .integerRange, .doubleRange:
sliderStep = step
}
return sliderStep
}
private func minimumValueDescription(
for selectionConfiguration: SliderQuestionConfiguration
) -> any CustomStringConvertible {
let minimumValueLabel: any CustomStringConvertible
switch selectionConfiguration {
case .textChoice(let choices):
minimumValueLabel = choices.first?.choiceText ?? ""
case .integerRange(let range):
minimumValueLabel = range.lowerBound
case .doubleRange(let range):
minimumValueLabel = range.lowerBound
}
return minimumValueLabel
}
private func maximumValueDescription(
for selectionConfiguration: SliderQuestionConfiguration
) -> any CustomStringConvertible {
let maximumValueDescription: any CustomStringConvertible
switch selectionConfiguration {
case .textChoice(let choices):
maximumValueDescription = choices.last?.choiceText ?? ""
case .integerRange(let range):
maximumValueDescription = range.upperBound
case .doubleRange(let range):
maximumValueDescription = range.upperBound
}
return maximumValueDescription
}
}
struct ScaleSliderQuestionView_Previews: PreviewProvider {
static var previews: some View {
ZStack {
(Color.choice(for: .secondaryBackground))
.ignoresSafeArea()
SliderQuestion(
id: UUID().uuidString,
title: "On a scale of 1-10, how would you rate today?",
range: 1...10,
selection: .constant(7)
)
.padding(.horizontal)
}
}
}
#Preview("Int") {
ScrollView {
SliderQuestion(
id: UUID().uuidString,
title: "On a scale of 1-10, how would you rate today?",
range: 1...10,
selection: .constant(7)
)
}
}
#Preview("Double") {
@Previewable @State var selection: Double? = 0.0
ScrollView {
SliderQuestion(
id: UUID().uuidString,
title: "Double Slider Question Example",
range: 0.0...10.0,
step: 0.1,
selection: $selection
)
}
}
#if !os(watchOS)
#Preview("Text") {
ScrollView {
SliderQuestion(
id: UUID().uuidString,
title: "On a scale of Pun - Poem, how would rate today?",
multipleChoiceOptions: [
.init(id: "1", choiceText: "Pun", value: 1),
.init(id: "2", choiceText: "Dad Joke", value: 2),
.init(id: "3", choiceText: "Knock-Knock Joke", value: 3),
.init(id: "4", choiceText: "One-Liner", value: 4),
.init(id: "5", choiceText: "Parody", value: 5),
.init(id: "5", choiceText: "Poem", value: 6),
],
selection: .constant(
.init(id: "2", choiceText: "Dad Joke", value: 2)))
}
}
#endif
@@ -0,0 +1,51 @@
/*
Copyright (c) 2024, 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 SwiftUI
extension ShapeStyle where Self == SliderValueForegroundStyle {
/// This foreground style is used for labels that display values associated with sliders.
static var sliderValueForegroundStyle: SliderValueForegroundStyle {
SliderValueForegroundStyle()
}
}
struct SliderValueForegroundStyle: ShapeStyle {
func resolve(in environment: EnvironmentValues) -> some ShapeStyle {
#if os(visionOS)
Color(.label)
#else
Color.accentColor
#endif
}
}
@@ -0,0 +1,345 @@
/*
Copyright (c) 2024, 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 SwiftUI
/// A question that allows for text input.
public struct TextQuestion<Header: View>: View {
/// Represents the number of lines a text question can contain.
public enum LineLimit {
/// A single line text question.
case singleLine
/// A multiline text question.
case multiline
}
@EnvironmentObject
private var managedFormResult: ResearchFormResult
@Environment(\.questionContext)
private var questionContext: QuestionContext
@Environment(\.questionRequired)
private var isRequired: Bool
@State
private var managedText: String?
private enum FocusTarget {
case textQuestion
}
private let id: String
private let header: Header
private let multilineTextFieldPadding: Double = 54
@FocusState private var focusTarget: FocusTarget?
private let prompt: String?
private let lineLimit: LineLimit
private let characterLimit: Int
private let hideCharacterCountLabel: Bool
private let hideClearButton: Bool
private let selection: StateManagementType<String?>
private var resolvedResult: Binding<String?> {
switch selection {
case let .automatic(key: key):
return Binding(
get: {
let text: String?
if let managedText {
text = managedText
} else {
switch questionContext {
case .formEmbedded:
text = managedFormResult.resultForStep(key: key) ?? nil
case .standalone:
text = nil
}
}
return text
},
set: {
managedText = $0
if case .formEmbedded = questionContext {
managedFormResult.setResultForStep(.text($0), key: key)
}
}
)
case let .manual(value):
return value
}
}
/// Initializes an instance of ``TextQuestion`` with the provided configuration.
/// - Parameters:
/// - id: The unique identifier for this question.
/// - header: The header for this question.
/// - prompt: The placeholder for this question.
/// - lineLimit: Specifies whether this text question is single line or multiline.
/// - characterLimit: The number of characters that can be used for this question.
/// - hideCharacterCountLabel: Whether or not the character count is displayed.
/// - hideClearButton: Whether or not the clear button is displayed.
/// - text: The entered text.
public init(
id: String,
@ViewBuilder header: () -> Header,
prompt: String?,
lineLimit: LineLimit,
characterLimit: Int,
hideCharacterCountLabel: Bool = false,
hideClearButton: Bool = false,
text: Binding<String?>
) {
self.id = id
self.header = header()
self.prompt = prompt
self.lineLimit = lineLimit
self.characterLimit = characterLimit
self.hideCharacterCountLabel = hideCharacterCountLabel
self.hideClearButton = hideClearButton
self.selection = .manual(text)
}
/// Initializes an instance of ``TextQuestion`` with the provided configuration.
/// - Parameters:
/// - id: The unique identifier for this question.
/// - header: The header for this question.
/// - prompt: The placeholder for this question.
/// - lineLimit: Specifies whether this text question is single line or multiline.
/// - characterLimit: The number of characters that can be used for this text question.
/// - hideCharacterCountLabel: Whether or not the character count is displayed.
/// - hideClearButton: Whether or not the clear button is displayed.
public init(
id: String,
@ViewBuilder header: () -> Header,
prompt: String?,
lineLimit: LineLimit,
characterLimit: Int,
hideCharacterCountLabel: Bool = false,
hideClearButton: Bool = false
) {
self.id = id
self.header = header()
self.prompt = prompt
self.lineLimit = lineLimit
self.characterLimit = characterLimit > 0 ? characterLimit : .max
self.hideCharacterCountLabel = hideCharacterCountLabel
self.hideClearButton = hideClearButton
self.selection = .automatic(key: .text(id: id))
}
private var axis: Axis {
switch lineLimit {
case .singleLine:
return .horizontal
case .multiline:
return .vertical
}
}
private var placeholder: Text? {
if let prompt {
return Text(prompt)
}
return nil
}
public var body: some View {
QuestionCard {
Question {
header
} content: {
VStack {
TextField(
id, text: resolvedResult.unwrapped(defaultValue: ""),
prompt: placeholder, axis: axis
)
.textFieldStyle(.plain) // Text binding's `didSet` called twice if this is not set.
.focused($focusTarget, equals: .textQuestion)
.padding(
.bottom,
axis == .vertical ? multilineTextFieldPadding : .zero
)
.contentShape(Rectangle())
.onAppear(perform: {
#if !os(watchOS)
if lineLimit == .singleLine {
UITextField.appearance().clearButtonMode =
.whileEditing
}
#endif
})
if lineLimit == .multiline {
HStack {
if hideCharacterCountLabel == false {
Text(
"\(resolvedResult.wrappedValue?.count ?? 0)/\(characterLimit)"
)
}
Spacer()
if !hideClearButton {
Button {
resolvedResult.wrappedValue = .none
} label: {
Text("Clear")
}
}
}
.onChange(of: resolvedResult.wrappedValue) {
oldValue, newValue in
if resolvedResult.wrappedValue?.count ?? 0
> characterLimit
{
resolvedResult.wrappedValue = oldValue
}
}
}
}
.padding()
#if os(iOS)
.doneKeyboardToolbar(
condition: {
focusTarget == .textQuestion
},
action: {
focusTarget = nil
}
)
#endif
}
.preference(
key: QuestionRequiredPreferenceKey.self, value: isRequired
)
.preference(
key: QuestionAnsweredPreferenceKey.self, value: isAnswered)
}
}
private var isAnswered: Bool {
if let result = resolvedResult.wrappedValue {
return !result.isEmpty
}
return false
}
}
extension TextQuestion where Header == QuestionHeader {
/// Initializes an instance of ``TextQuestion`` with the provided configuration.
/// - Parameters:
/// - id: The unique identifier for this text question.
/// - title: The title for this text question.
/// - detail: The details for this text question.
/// - prompt: The placeholder for this text question.
/// - lineLimit: Specifies whether this text question is single line or multiline.
/// - characterLimit: The number of characters that can be used for this text question.
/// - hideCharacterCountLabel: Whether or not the character count is displayed.
/// - hideClearButton: Whether or not the clear button is displayed.
/// - text: The entered text.
public init(
id: String,
title: String,
detail: String?,
prompt: String?,
lineLimit: LineLimit,
characterLimit: Int,
hideCharacterCountLabel: Bool = false,
hideClearButton: Bool = false,
text: Binding<String?>
) {
self.id = id
self.header = QuestionHeader(title: title, detail: detail)
self.prompt = prompt
self.lineLimit = lineLimit
self.characterLimit = characterLimit
self.hideCharacterCountLabel = hideCharacterCountLabel
self.hideClearButton = hideClearButton
self.selection = .manual(text)
}
/// Initializes an instance of ``TextQuestion`` with the provided configuration.
/// - Parameters:
/// - id: The unique identifier for this text question.
/// - title: The title for this text question.
/// - detail: The details for this text question.
/// - prompt: The placeholder for this text question.
/// - lineLimit: Specifies whether this text question is single line or multiline.
/// - characterLimit: The number of characters that can be used for this text question.
/// - hideCharacterCountLabel: Whether or not the character count is displayed.
/// - hideClearButton: Whether or not the clear button is displayed.
/// - defaultTextAnswer: The initial text to display.
public init(
id: String,
title: String,
detail: String? = nil,
prompt: String?,
lineLimit: LineLimit,
characterLimit: Int,
hideCharacterCountLabel: Bool = false,
hideClearButton: Bool = false,
defaultTextAnswer: String? = nil
) {
self.id = id
self.header = QuestionHeader(title: title, detail: detail)
self.prompt = prompt
self.lineLimit = lineLimit
self.characterLimit = characterLimit
self.hideCharacterCountLabel = hideCharacterCountLabel
self.hideClearButton = hideClearButton
self.selection = .automatic(key: StepResultKey(id: id))
if let defaultTextAnswer {
self.resolvedResult.wrappedValue = defaultTextAnswer
}
}
}
#Preview {
@Previewable @State var value: String? = "Tom Riddle"
ScrollView {
TextQuestion(
id: UUID().uuidString,
title: "What is your name?",
detail: nil,
prompt: "Tap to write",
lineLimit: .singleLine,
characterLimit: 10,
hideCharacterCountLabel: true,
text: $value
)
}
}
@@ -0,0 +1,647 @@
/*
Copyright (c) 2024, 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 SwiftUI
/// The precision representing the granularity of a measurement's value. (e.g. 68 kg vs. 68.04 kg, or 150 lbs vs. 150 lbs 5 oz)
public enum NumericPrecision {
case `default`, low, high
}
private class WeightFormatter {
static let shared = WeightFormatter()
private let weightFormatter: NumberFormatter
private init() {
let weightFormatter = NumberFormatter()
weightFormatter.numberStyle = .decimal
weightFormatter.roundingMode = .halfUp
self.weightFormatter = weightFormatter
}
func string(for number: Double, precision: NumericPrecision) -> String? {
switch precision {
case .default:
weightFormatter.minimumFractionDigits = 1
weightFormatter.maximumFractionDigits = 1
case .low:
weightFormatter.minimumFractionDigits = 0
weightFormatter.maximumFractionDigits = 0
case .high:
weightFormatter.minimumFractionDigits = 2
weightFormatter.maximumFractionDigits = 2
}
return weightFormatter.string(for: number)
}
}
/// A question that allows for weight input.
public struct WeightQuestion: View {
@EnvironmentObject
private var managedFormResult: ResearchFormResult
@Environment(\.questionContext)
private var questionContext: QuestionContext
@State private var isInputActive = false
@State private var hasChanges: Bool
@Environment(\.questionRequired)
private var isRequired: Bool
@State
private var managedWeight: Double?
private let defaultWeightInKilograms = 68.039
private let id: String
private let title: String
private let detail: String?
private let measurementSystem: MeasurementSystem
private let precision: NumericPrecision
private let defaultValue: Double?
private let minimumValue: Double?
private let maximumValue: Double?
private let selection: StateManagementType<Double?>
private var resolvedResult: Binding<Double?> {
switch selection {
case let .automatic(key: key):
return Binding(
get: {
let weight: Double?
if let managedWeight {
weight = managedWeight
} else {
switch questionContext {
case .formEmbedded:
weight = managedFormResult.resultForStep(key: key) ?? nil
case .standalone:
weight = nil
}
}
return weight
},
set: {
managedWeight = $0
if case .formEmbedded = questionContext {
managedFormResult.setResultForStep(.numeric($0), key: key)
}
}
)
case let .manual(value):
return value
}
}
/// Initializes an instance of ``WeightQuestion`` with the provided configuration.
/// - Parameters:
/// - id: The unique identifier for this question.
/// - title: The title for this question.
/// - detail: The details for this question.
/// - measurementSystem: The measurement system for this question.
/// - precision: The precision for this question.
/// - defaultValue: The default weight.
/// - minimumValue: The minimum selectable weight.
/// - maximumValue: The maximum selectable weight.
public init(
id: String,
title: String,
detail: String? = nil,
measurementSystem: MeasurementSystem,
precision: NumericPrecision = .default,
defaultValue: Double?,
minimumValue: Double?,
maximumValue: Double?
) {
self.id = id
self.hasChanges = false
self.title = title
self.detail = detail
let system: MeasurementSystem = {
switch measurementSystem {
case .USC:
return .USC
case .local:
if Locale.current.measurementSystem == .us {
return .USC
} else {
return .metric
}
case .metric:
return .metric
}
}()
self.measurementSystem = system
self.precision = precision
self.defaultValue = defaultValue
self.minimumValue = minimumValue
self.maximumValue = maximumValue
self.selection = .automatic(key: .weight(id: id))
}
/// Initializes an instance of ``WeightQuestion`` with the provided configuration.
/// - Parameters:
/// - id: The unique identifier for this question.
/// - title: The title for this question.
/// - detail: The details for this question.
/// - measurementSystem: The measurement system for this question.
/// - precision: The precision for this question.
/// - defaultValue: The default weight.
/// - minimumValue: The minimum selectable weight.
/// - maximumValue: The maximum selectable weight.
/// - weight: The selected weight.
public init(
id: String,
title: String,
detail: String? = nil,
measurementSystem: MeasurementSystem,
precision: NumericPrecision = .default,
defaultValue: Double?,
minimumValue: Double?,
maximumValue: Double?,
weight: Binding<Double?>
) {
self.id = id
self.hasChanges = false
self.title = title
self.detail = detail
let system: MeasurementSystem = {
switch measurementSystem {
case .USC:
return .USC
case .local:
if Locale.current.measurementSystem == .us {
return .USC
} else {
return .metric
}
case .metric:
return .metric
}
}()
self.measurementSystem = system
self.precision = precision
self.defaultValue = defaultValue
self.minimumValue = minimumValue
self.maximumValue = maximumValue
self.selection = .manual(weight)
}
private var selectionString: String {
let selectedResult: Double
if let result = resolvedResult.wrappedValue {
selectedResult = result
} else {
selectedResult = defaultValue ?? defaultWeightInKilograms
}
let (pounds, ounces) = convertKilogramsToPoundsAndOunces(selectedResult)
if measurementSystem == .USC {
switch precision {
case .default, .low:
return "\(Int(pounds)) lb"
case .high:
return "\(Int(pounds)) lb \(Int(ounces)) oz"
}
} else {
if resolvedResult.wrappedValue == defaultWeightInKilograms {
// 68.039 isn't exactly the prettiest value, but it maps
// nice to 150 pounds, so if the user sticks with the default
// we'll round to the nearest result which in our case would be 60kg.
return "\(selectedResult.rounded()) kg"
}
return
"\(WeightFormatter.shared.string(for: selectedResult, precision: precision) ?? "") kg"
}
}
public var body: some View {
QuestionCard {
Question(title: title, detail: detail) {
HStack {
Text("Select Weight")
.foregroundStyle(Color.primary)
.frame(maxWidth: .infinity, alignment: .leading)
Button {
isInputActive = true
#if !os(watchOS)
UIApplication.shared.endEditing()
#endif
} label: {
Text(selectionString)
.foregroundStyle(Color.primary)
}
.buttonStyle(.bordered)
.buttonBorderShape(.roundedRectangle)
#if os(watchOS)
.navigationDestination(isPresented: $isInputActive) {
WeightPickerView(
measurementSystem: measurementSystem,
precision: precision,
defaultValue: defaultValue,
minimumValue: minimumValue,
maximumValue: maximumValue,
selection: .init(
get: {
resolvedResult.wrappedValue
?? defaultValue
?? defaultWeightInKilograms
},
set: {
resolvedResult.wrappedValue = $0
}
),
hasChanges: $hasChanges
)
}
#else
.popover(
isPresented: $isInputActive,
attachmentAnchor: .point(.bottom),
arrowEdge: .top
) {
WeightPickerView(
measurementSystem: measurementSystem,
precision: precision,
defaultValue: defaultValue
?? defaultWeightInKilograms,
minimumValue: minimumValue,
maximumValue: maximumValue,
selection: .init(
get: {
resolvedResult.wrappedValue
?? defaultValue
?? defaultWeightInKilograms
},
set: {
resolvedResult.wrappedValue = $0
}
),
hasChanges: $hasChanges
)
.frame(width: 300)
.presentationCompactAdaptation((.popover))
}
#endif
}
.padding()
}
.preference(
key: QuestionRequiredPreferenceKey.self, value: isRequired
)
.preference(
key: QuestionAnsweredPreferenceKey.self, value: isAnswered)
}
}
private var isAnswered: Bool {
resolvedResult.wrappedValue != nil
}
}
struct WeightPickerView: View {
@Environment(\.dismiss) private var dismiss
private let measurementSystem: MeasurementSystem
private let precision: NumericPrecision
private let defaultValue: Double?
private let minimumValue: Double?
private let maximumValue: Double?
@Binding private var selection: Double?
@Binding private var hasChanges: Bool
@State private var highPrecisionSelection: Int = 0
@State private var selectionOne: Double
@State private var selectionTwo: Double
private static let defaultValueInKilograms: Double = 68.0
private static let defaultValueInPounds: Double = 150.0
private var lowerValue: Double {
guard let minimumValue else { return 0 }
return minimumValue
}
private var upperValue: Double {
if measurementSystem == .USC {
guard let maximumValue else { return 1_450 }
return maximumValue
} else {
guard let maximumValue else {
switch precision {
case .low, .high:
return 657
case .default:
return 657.5
}
}
return maximumValue
}
}
private var primaryStep: Double {
if measurementSystem != .USC {
switch precision {
case .default:
return 0.5
case .low, .high:
return 1
}
} else {
return 1
}
}
private var secondaryStep: Double {
if measurementSystem == .USC {
return 1
} else {
return 0.01
}
}
private var primaryUnit: String {
if measurementSystem == .USC {
return "lb"
} else {
return "kg"
}
}
private var primaryRange: [Double] {
var range: [Double] = []
for i in stride(from: lowerValue, through: upperValue, by: primaryStep)
{
range.append(i)
}
return range
}
private var secondaryRange: [Double] {
let upperValue = measurementSystem == .USC ? 15 : 0.99
var range: [Double] = []
for i in stride(
from: lowerValue, through: upperValue, by: secondaryStep)
{
if case .USC = measurementSystem {
range.append(i)
} else {
let decimal = Decimal(i).rounded(2, .plain)
range.append(
NSDecimalNumber(decimal: decimal).doubleValue
)
}
}
return range
}
init(
measurementSystem: MeasurementSystem = .metric,
precision: NumericPrecision = .default,
defaultValue: Double? = nil,
minimumValue: Double? = nil,
maximumValue: Double? = nil,
selection: Binding<Double?>,
hasChanges: Binding<Bool>
) {
self.measurementSystem = measurementSystem
self.precision = precision
self.defaultValue = defaultValue
self.minimumValue = minimumValue
self.maximumValue = maximumValue
self._selection = selection
self._hasChanges = hasChanges
let displayedValue = selection.wrappedValue ?? defaultValue ?? 0
let selectionOneValue: Double = {
if measurementSystem == .USC {
return convertKilogramsToPoundsAndOunces(displayedValue).pounds
} else {
let roundedSelectionOne: Double
switch precision {
case .default:
roundedSelectionOne = (displayedValue * 10).rounded() / 10
case .low:
roundedSelectionOne = displayedValue.rounded()
case .high:
roundedSelectionOne = floor(displayedValue)
}
return roundedSelectionOne
}
}()
self.selectionOne = selectionOneValue
let selectionTwoValue: Double = {
if measurementSystem == .USC {
return convertKilogramsToPoundsAndOunces(displayedValue).ounces
} else {
if case .high = precision {
let selectionTwo = Decimal(displayedValue)
let selectionTwoFraction =
selectionTwo.rounded(2, .plain)
- selectionTwo.rounded(0, .down)
return NSDecimalNumber(decimal: selectionTwoFraction)
.doubleValue
} else {
return 0
}
}
}()
self.selectionTwo = selectionTwoValue
}
var body: some View {
HStack(spacing: .zero) {
Picker(selection: $selectionOne) {
ForEach(primaryRange, id: \.self) { i in
Text(primaryPickerString(for: i))
.tag(i)
}
} label: {
Text(primaryUnit)
}
.pickerStyle(.wheel)
.onChange(of: selectionOne) { _, _ in
selection = standardizedWeight((selectionOne, selectionTwo))
hasChanges = true
}
if precision == .high {
Picker(selection: $selectionTwo) {
ForEach(secondaryRange, id: \.self) { i in
Text(secondaryPickerString(for: i))
.tag(i)
}
} label: {
if measurementSystem == .USC {
Text("oz")
}
}
.pickerStyle(.wheel)
.onChange(of: selectionTwo) { _, _ in
selection = standardizedWeight((selectionOne, selectionTwo))
hasChanges = true
}
}
if measurementSystem != .USC,
precision == .high
{
Picker(selection: $highPrecisionSelection) {
ForEach(0..<1, id: \.self) { i in
Text("\(primaryUnit)")
.tag(i)
}
} label: {
Text("Tap Here")
}
.pickerStyle(.wheel)
}
}
}
private func primaryPickerString(
for value: Double
) -> String {
let formatter = NumberFormatter()
let fractionalDigits: Int = {
if measurementSystem != .USC && precision == .default {
return 1
}
return 0
}()
formatter.minimumFractionDigits = fractionalDigits
formatter.minimumIntegerDigits =
measurementSystem != .USC && self.precision == .high ? 0 : 1
let string = formatter.string(for: value) ?? "Unknown"
let includeUnit: Bool = {
if measurementSystem != .USC && precision == .high {
return false
}
return true
}()
let finalString = includeUnit ? "\(string) \(primaryUnit)" : string
return finalString
}
private func secondaryPickerString(
for value: Double
) -> String {
let formatter = NumberFormatter()
let fractionalDigits: Int = {
if measurementSystem != .USC && precision == .high {
return 2
}
return 0
}()
formatter.minimumFractionDigits = fractionalDigits
formatter.minimumIntegerDigits =
measurementSystem != .USC && self.precision == .high ? 0 : 1
let string = formatter.string(for: value) ?? "Unknown"
let includeUnit: Bool = {
if measurementSystem == .USC && precision == .high {
return true
}
return false
}()
let finalString = includeUnit ? "\(string) oz" : string
return finalString
}
private func standardizedWeight(_ weight: (Double, Double)) -> Double {
if measurementSystem == .USC {
return convertPoundsAndOuncesToKilograms(
pounds: weight.0, ounces: weight.1)
} else {
switch precision {
case .low, .default:
return weight.0
case .high:
return weight.0 + weight.1
}
}
}
}
@available(iOS 18.0, *)
#Preview {
@Previewable @State var selection: Double? = 133
NavigationStack {
WeightQuestion(
id: UUID().uuidString,
title: "Weight question here",
detail: nil,
measurementSystem: .USC,
precision: .high,
defaultValue: 150,
minimumValue: 0,
maximumValue: 1430,
weight: $selection
)
}
}
extension Decimal {
fileprivate func rounded(
_ scale: Int, _ roundingMode: NSDecimalNumber.RoundingMode
) -> Decimal {
var result = Decimal()
var localCopy = self
NSDecimalRound(&result, &localCopy, scale, roundingMode)
return result
}
}
@@ -0,0 +1,75 @@
# ``ResearchKitSwiftUI``
## Overview
ResearchKit SwiftUI marks the beginning of a gradual transition from UIKit to SwiftUI. The goal is to provide the same solutions that the UIKit implementation of ResearchKit offers today in a way that is aligned with SwiftUI's philosophies. We are starting this transition with new additions for a form, step, and some frequently used question formats.
## Topics
### Adding Questions to a Form
- ``ResearchForm``
- ``ResearchFormStep``
- ``ResearchFormCompletion``
### Headers
- ``StepHeader``
- ``QuestionHeader``
### Multiple Choice Question
- ``MultipleChoiceQuestion``
### Measurement Questions
- ``HeightQuestion``
- ``WeightQuestion``
### Slider Question
- ``SliderQuestion``
### Text Question
- ``TextQuestion``
### Date and Time Question
- ``DateTimeQuestion``
### Numeric Question
- ``NumericQuestion``
### Image Choice Question
- ``ImageChoiceQuestion``
- ``ImageChoice``
### Instructions
- ``InstructionBodyItem``
### Results
- ``ResearchFormResult``
- ``AnswerFormat``
- ``Result``
- ``ResultValue``
### Selection Type
- ``ChoiceSelectionLimit``
### Text Choice
- ``TextChoice``
### Measurement Type
- ``MeasurementSystem``
### Precision Type
- ``NumericPrecision``
@@ -0,0 +1,217 @@
/*
Copyright (c) 2024, 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 Combine
import SwiftUI
/// Represents responses for the different kinds of questions.
public enum AnswerFormat {
case text(String?)
case numeric(Double?)
case date(Date?)
case weight(Double?)
case height(Double?)
case multipleChoice([ResultValue]?)
case image([ResultValue]?)
case scale(Double?)
}
extension AnswerFormat: Codable {}
extension AnswerFormat: Equatable {
public static func == (lhs: AnswerFormat, rhs: AnswerFormat) -> Bool {
let isEqual: Bool
switch (lhs, rhs) {
case let (.text(lhsValue), .text(rhsValue)):
isEqual = lhsValue == rhsValue
case let (.numeric(lhsValue), .numeric(rhsValue)):
isEqual = lhsValue == rhsValue
case let (.date(lhsValue), .date(rhsValue)):
isEqual = lhsValue == rhsValue
case let (.weight(lhsValue), .weight(rhsValue)):
isEqual = lhsValue == rhsValue
case let (.height(lhsValue), .height(rhsValue)):
isEqual = lhsValue == rhsValue
case let (.multipleChoice(lhsValue), .multipleChoice(rhsValue)):
isEqual = lhsValue == rhsValue
case let (.image(lhsValue), .image(rhsValue)):
isEqual = lhsValue == rhsValue
case let (.scale(lhsValue), .scale(rhsValue)):
isEqual = lhsValue == rhsValue
default:
isEqual = false
}
return isEqual
}
}
/// Captures the responses to questions for which no binding has been provided. It is passed through ``ResearchForm``'s `onResearchFormCompletion` handler, which is passed through the initializer, upon survey completion.
public final class ResearchFormResult: ObservableObject {
private var stepResults: [String: AnswerFormat]
/// Initializes an instance of ``ResearchFormResult`` that contains no responses.
public convenience init() {
self.init(results: [])
}
/// Initializes an instance of ``ResearchFormResult`` with the provided configuration.
/// - Parameter results: The results from which an instance of `ResearchFormResult` is created.
public init(results: [Result]) {
stepResults = results.reduce(into: [String: AnswerFormat]()) {
partialResult, result in
partialResult[result.identifier] = result.answer
}
}
/// Initializes an instance of ``ResearchFormResult`` with the provided configuration.
/// - Parameter decoder: The decoder used to deserialize an instance of `ResearchFormResult`.
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
stepResults = try container.decode(
[String: AnswerFormat].self, forKey: .stepResults)
}
func resultForStep<Result>(key: StepResultKey<Result>) -> Result? {
let answerFormat = stepResults[key.id]
switch answerFormat {
case let .text(answer):
return answer as? Result
case .numeric(let decimal):
return decimal as? Result
case .date(let date):
return date as? Result
case .height(let height):
return height as? Result
case .weight(let weight):
return weight as? Result
case .image(let image):
return image as? Result
case .multipleChoice(let multipleChoice):
return multipleChoice as? Result
case .scale(let double):
return double as? Result
default:
return nil
}
}
func setResultForStep<Result>(
_ format: AnswerFormat, key: StepResultKey<Result>
) {
stepResults[key.id] = format
}
/// Maps the captured responses to a type of your choice.
/// - Parameter transform: The mapping function used to transform a response to a type of your choice.
/// - Returns: An array containing the type to which the responses were transformed.
public func compactMap<T>(_ transform: (Result) -> T?) -> [T] {
stepResults.compactMap { entry in
transform(
Result(identifier: entry.key, answer: entry.value)
)
}
}
}
/// The response context for a question.
public struct Result {
/// The question identifier associated with this result.
public let identifier: String
/// The response to the question associated with the `identifier`.
public let answer: AnswerFormat
/// Initializes an instance of ``Result`` with the provided configuration.
/// - Parameters:
/// - identifier: The question identifier associated with this response.
/// - answer: The response to the question associated with the `identifier`.
public init(identifier: String, answer: AnswerFormat) {
self.identifier = identifier
self.answer = answer
}
}
extension ResearchFormResult: Codable {
enum CodingKeys: CodingKey {
case stepResults
}
public func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(stepResults, forKey: .stepResults)
}
}
extension ResearchFormResult: Equatable {
public static func == (lhs: ResearchFormResult, rhs: ResearchFormResult)
-> Bool
{
lhs.stepResults == rhs.stepResults
}
}
extension EnvironmentValues {
var questionContext: QuestionContext {
get {
self[QuestionContextKey.self]
}
set {
self[QuestionContextKey.self] = newValue
}
}
}
enum QuestionContext {
case standalone, formEmbedded
}
private struct QuestionContextKey: EnvironmentKey {
static var defaultValue: QuestionContext = .standalone
}
@@ -0,0 +1,55 @@
/*
Copyright (c) 2024, 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 Foundation
/// `ResultValue` is limited to a few question types that have different representations for their selected values.
public enum ResultValue: Codable {
case int(Int)
case string(String)
case date(Date)
}
extension ResultValue: Equatable {
public static func == (lhs: ResultValue, rhs: ResultValue) -> Bool {
switch (lhs, rhs) {
case (.int(let a), .int(let b)):
return a == b
case (.string(let a), .string(let b)):
return a == b
case (.date(let a), .date(let b)):
return a == b
default:
return false
}
}
}
@@ -0,0 +1,64 @@
/*
Copyright (c) 2024, 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 Foundation
struct StepResultKey<Result> {
let id: String
static func text(id: String) -> StepResultKey<String?> {
return StepResultKey<String?>(id: id)
}
static func imageChoice(id: String) -> StepResultKey<[ResultValue]?> {
return StepResultKey<[ResultValue]?>(id: id)
}
static func multipleChoice(id: String) -> StepResultKey<[ResultValue]?> {
return StepResultKey<[ResultValue]?>(id: id)
}
static func numeric(id: String) -> StepResultKey<Double?> {
return StepResultKey<Double?>(id: id)
}
static func height(id: String) -> StepResultKey<Double?> {
return StepResultKey<Double?>(id: id)
}
static func weight(id: String) -> StepResultKey<Double?> {
return StepResultKey<Double?>(id: id)
}
static func date(id: String) -> StepResultKey<Date?> {
return StepResultKey<Date?>(id: id)
}
}
@@ -0,0 +1,75 @@
/*
Copyright (c) 2024, 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 SwiftUI
extension StickyScrollView {
struct StickyFooterLayout: Layout {
let safeAreaInsets: EdgeInsets
func sizeThatFits(
proposal: ProposedViewSize,
subviews: Subviews,
cache: inout Void
) -> CGSize {
let contentHeight = subviews[0].dimensions(in: proposal).height
return CGSize(
width: proposal.replacingUnspecifiedDimensions().width,
height: contentHeight + safeAreaInsets.bottom
)
}
func placeSubviews(
in bounds: CGRect,
proposal: ProposedViewSize,
subviews: Subviews,
cache: inout Void
) {
let content = subviews[0]
let contentPlacementProposal = ProposedViewSize(
width: bounds.width,
height: .infinity
)
content.place(
at: CGPoint(
x: bounds.origin.x,
y: bounds.origin.y
),
anchor: .topLeading,
proposal: contentPlacementProposal
)
}
}
}
@@ -0,0 +1,197 @@
/*
Copyright (c) 2024, 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 SwiftUI
extension StickyScrollView {
struct StickyLayout: Layout {
let allowsExtendedLayout: Bool
let size: CGSize
let bodySize: CGSize
var offset: CGFloat
let safeAreaInsets: EdgeInsets
let keyboardIgnoringSafeAreaInsets: EdgeInsets
let isContentCenteringEnabled: Bool
@Binding
var totalLayoutHeight: CGFloat
@Binding
var availableContentHeight: CGFloat
@Binding
var isFooterBackgroundVisible: Bool
func sizeThatFits(
proposal: ProposedViewSize,
subviews: Subviews,
cache: inout Void
) -> CGSize {
let bottomInset = safeAreaInsets.bottom
let contentHeight = subviews[0].dimensions(in: proposal).height
let stickyHeight = subviews[1].dimensions(in: proposal).height
if contentHeight + stickyHeight > size.height {
return CGSize(
width: proposal.replacingUnspecifiedDimensions().width,
height: contentHeight + stickyHeight - bottomInset
)
}
return CGSize(
width: proposal.replacingUnspecifiedDimensions().width,
height: contentHeight + stickyHeight + bottomInset
)
}
func placeSubviews(
in bounds: CGRect,
proposal: ProposedViewSize,
subviews: Subviews,
cache: inout Void
) {
let stickyContent = subviews[1]
let stickyContentHeight = stickyContent.dimensions(in: proposal)
.height
let content = subviews[0]
let contentHeight = content.dimensions(in: proposal).height
let totalHeight = contentHeight + stickyContentHeight
let bodyHeight = bodySize.height
let topWhiteSpace =
isContentCenteringEnabled == false
? 0
: (contentHeight - bodyHeight) / 2
// This is the bottom inset to the top of the keyboard (and attachments)
let bottomInset = safeAreaInsets.bottom
// This is the bottom inset with no keyboard
let keyboardIgnoringBottomInset = keyboardIgnoringSafeAreaInsets
.bottom
let shouldFooterFixPosition =
allowsExtendedLayout == false
|| contentHeight + (stickyContentHeight - safeAreaInsets.bottom)
< size.height
let shouldFooterBackgroundShow: Bool = {
let bottomOfBodyFromTop =
bodySize.height + topWhiteSpace + self.offset
let topOfStickyFooter =
size.height + bottomInset - stickyContentHeight
let isHeightGreaterThanAvailableSpace =
bottomOfBodyFromTop > topOfStickyFooter
return isHeightGreaterThanAvailableSpace
&& shouldFooterFixPosition
}()
DispatchQueue.main.async {
isFooterBackgroundVisible = shouldFooterBackgroundShow
// The following conditionals are a hack to deal with lack of keyboard management support in SwiftUI.
// The point of these conditionals is to allow bottom spacing for the sticky section, but not
// allow that space when the keyboard is visible.
// There are 3 cases for the layout:
if totalHeight > size.height {
if bottomInset > keyboardIgnoringBottomInset {
// 1. The content is greater than the view size, and the keyboard is visible.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
self.totalLayoutHeight =
totalHeight - bottomInset - stickyContentHeight
+ bottomInset
}
// Need to cancel these (for quick dismisses)
} else {
// 2. The content is greater than the view size, and the keyboard is not visible
self.totalLayoutHeight = totalHeight - bottomInset
}
} else {
// 3. The content is not greater than the view size, doesn't matter if the keyboard is visible
self.totalLayoutHeight = contentHeight
}
self.availableContentHeight =
size.height - stickyContentHeight + bottomInset
}
let contentPlacementProposal = ProposedViewSize(
width: bounds.width,
height: .infinity
)
let stickyPlacementProposal = ProposedViewSize(
width: bounds.width,
height: .infinity
)
content.place(
at: CGPoint(
x: bounds.origin.x,
y: bounds.origin.y
),
anchor: .topLeading,
proposal: contentPlacementProposal
)
let offset =
shouldFooterFixPosition
? size.height - stickyContentHeight - self.offset + bottomInset
: max(contentHeight, size.height - stickyContentHeight)
stickyContent.place(
at: CGPoint(
x: bounds.origin.x,
y: bounds.origin.y + offset + safeAreaInsets.bottom
- keyboardIgnoringSafeAreaInsets.bottom
),
anchor: .topLeading,
proposal: stickyPlacementProposal
)
}
}
}
@@ -0,0 +1,322 @@
/*
Copyright (c) 2024, 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 SwiftUI
/// A vertical ScrollView with a sticky footer, that returns to flow inline if
/// the content is longer than the available height.
/// Usage:
/// ```
/// StickyScrollView {
/// allowsExtendedLayout: true,
/// bodyContent: { /* Your Content */ },
/// footerContent: { /* Will stick to bottom if bodyContent is short */ }
/// }
/// ```
struct StickyScrollView<BodyContent: View, FooterContent: View>: View {
/// Create a StickyScrollView with footer content that can stick to the content bounds if the content is longer
/// than the scrollView frame.
/// - Parameters:
/// - allowsExtendedLayout: Allow the footer to stick to bottom of content if the content is longer
/// than the container height. On watchOS, the footer will never stick to the bottom.
/// - bodyContent: The body content for the ScrollView
/// - footerContent: The footer content.
init(
allowsExtendedLayout: Bool = false,
paddingAboveKeyboard: CGFloat = 0.0,
centerContentIfFits: Bool = false,
bodyContent: @escaping (CGSize) -> BodyContent,
footerContent: @escaping () -> FooterContent
) {
#if os(watchOS)
self.allowsExtendedLayout = true
#else
self.allowsExtendedLayout = allowsExtendedLayout
#endif
self.bodyContent = bodyContent
self.footerContent = footerContent
self.paddingAboveKeyboard = paddingAboveKeyboard
self.centerContentIfFits = centerContentIfFits
}
init(
allowsExtendedLayout: Bool = false,
paddingAboveKeyboard: CGFloat = 0.0,
centerContentIfFits: Bool = false,
bodyContent: @escaping () -> BodyContent,
footerContent: @escaping () -> FooterContent
) {
self.init(
allowsExtendedLayout: allowsExtendedLayout,
paddingAboveKeyboard: paddingAboveKeyboard,
centerContentIfFits: centerContentIfFits,
bodyContent: { _ in bodyContent() },
footerContent: footerContent
)
}
@ViewBuilder let bodyContent: (CGSize) -> BodyContent
@ViewBuilder let footerContent: () -> FooterContent
let paddingAboveKeyboard: CGFloat
let centerContentIfFits: Bool
private let allowsExtendedLayout: Bool
@Namespace
var scrollCoordinateSpace
@State
private var offset = CGFloat.zero
@State
private var frameSize = CGSize.zero
@State
private var totalLayoutHeight = CGFloat.zero
@State
private var availableContentHeight = CGFloat.zero
@State
private var bodySize = CGSize.zero
@State
private var safeAreaInsets = EdgeInsets(
top: 0, leading: 0, bottom: 0, trailing: 0)
@State
private var keyboardIgnoringSafeAreaInsets = EdgeInsets(
top: 0, leading: 0, bottom: 0, trailing: 0)
@State
private var isFooterBackgroundVisible = false
var body: some View {
GeometryReader { outerGeo in
ScrollView {
GeometryReader { geo in
let offset = geo.frame(in: .named(scrollCoordinateSpace))
.origin.y
StickyLayout(
allowsExtendedLayout: allowsExtendedLayout,
size: frameSize,
bodySize: bodySize,
offset: offset,
safeAreaInsets: safeAreaInsets,
keyboardIgnoringSafeAreaInsets:
keyboardIgnoringSafeAreaInsets,
isContentCenteringEnabled: centerContentIfFits,
totalLayoutHeight: $totalLayoutHeight,
availableContentHeight: $availableContentHeight,
isFooterBackgroundVisible: $isFooterBackgroundVisible
) {
let bodySize = CGSize(
width: geo.size.width - geo.safeAreaInsets.leading
- geo.safeAreaInsets.trailing,
height: availableContentHeight
)
VStack(spacing: 0) {
bodyContent(bodySize)
.fixedSize(horizontal: false, vertical: true)
.background(
GeometryReader {
Color.clear.preference(
key: BodySizeKey.self,
value: $0.size
)
})
}
.frame(
maxWidth: bodySize.width,
minHeight: centerContentIfFits
? outerGeo.size.height
- outerGeo.safeAreaInsets.top
- outerGeo.safeAreaInsets.bottom : nil
)
StickyFooterLayout(
safeAreaInsets: safeAreaInsets
) {
VStack(spacing: 0) {
Rectangle()
.frame(
maxWidth: .infinity,
minHeight: 1,
maxHeight: 1,
alignment: .topLeading
)
.background(Color.gray)
.opacity(
isFooterBackgroundVisible ? 0.1 : 0)
footerContent()
.padding()
}
}
.edgesIgnoringSafeArea(.bottom)
#if !os(watchOS)
.background(
Material.bar.opacity(
isFooterBackgroundVisible ? 100 : 0))
#endif
.frame(maxWidth: .infinity)
}
}
.frame(height: totalLayoutHeight, alignment: .topLeading)
}
.safeAreaInset(edge: .bottom) {
Color.clear.frame(
height: safeAreaInsets != keyboardIgnoringSafeAreaInsets
? paddingAboveKeyboard
: 0.0
)
}
.background(
GeometryReader {
Color.clear.preference(
key: FrameSizeKey.self,
value: $0.size
)
Color.clear.preference(
key: SafeAreaInsetsKey.self,
value: $0.safeAreaInsets
)
}
)
.overlay {
GeometryReader {
Color.clear.preference(
key: KeyboardIgnoringSafeAreaInsets.self,
value: $0.safeAreaInsets
)
}
.ignoresSafeArea(.keyboard, edges: .all)
}
.coordinateSpace(name: scrollCoordinateSpace)
.onPreferenceChange(FrameSizeKey.self) { value in
self.frameSize = value
}
.onPreferenceChange(SafeAreaInsetsKey.self) { value in
self.safeAreaInsets = value
}
.onPreferenceChange(KeyboardIgnoringSafeAreaInsets.self) { value in
self.keyboardIgnoringSafeAreaInsets = value
}
.onPreferenceChange(
BodySizeKey.self,
perform: { value in
self.bodySize = value
})
}
}
}
private struct FrameSizeKey: PreferenceKey {
typealias Value = CGSize
static var defaultValue = CGSize.zero
static func reduce(value: inout Value, nextValue: () -> Value) {
value = CGSize(
width: value.width + nextValue().width,
height: value.height + nextValue().height
)
}
}
private struct BodySizeKey: PreferenceKey {
typealias Value = CGSize
static var defaultValue = CGSize.zero
static func reduce(value: inout Value, nextValue: () -> Value) {
value = CGSize(
width: value.width + nextValue().width,
height: value.height + nextValue().height
)
}
}
private struct KeyboardIgnoringSafeAreaInsets: PreferenceKey {
typealias Value = EdgeInsets
static var defaultValue = EdgeInsets(
top: 0, leading: 0, bottom: 0, trailing: 0)
static func reduce(value: inout Value, nextValue: () -> Value) {
let nextValue = nextValue()
value = EdgeInsets(
top: value.top + nextValue.top,
leading: value.leading + nextValue.leading,
bottom: value.bottom + nextValue.bottom,
trailing: value.trailing + nextValue.trailing
)
}
}
private struct SafeAreaInsetsKey: PreferenceKey {
typealias Value = EdgeInsets
static var defaultValue = EdgeInsets(
top: 0, leading: 0, bottom: 0, trailing: 0)
static func reduce(value: inout Value, nextValue: () -> Value) {
let nextValue = nextValue()
value = EdgeInsets(
top: value.top + nextValue.top,
leading: value.leading + nextValue.leading,
bottom: value.bottom + nextValue.bottom,
trailing: value.trailing + nextValue.trailing
)
}
}
struct ToolbarButton: ButtonStyle {
let isDisabled: Bool
public init(isDisabled: Bool = false) {
self.isDisabled = isDisabled
}
var buttonColor: Color {
return isDisabled ? .gray : .accentColor
}
public func makeBody(configuration: Configuration) -> some View {
configuration.label
.fontWeight(.semibold)
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.padding(12)
.background(
configuration.isPressed
? buttonColor.opacity(0.8).cornerRadius(12)
: buttonColor.cornerRadius(12)
)
.disabled(isDisabled)
.scaleEffect(configuration.isPressed ? 1.05 : 1)
.animation(.easeOut(duration: 0.2), value: configuration.isPressed)
}
}
@@ -0,0 +1,230 @@
/*
Copyright (c) 2024, 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 SwiftUI
/// Represents a step in a survey and lays out the header and questions on one page. Question numbers (e.g. 1 of 3) are automatically added at the top of each question to denote progress in a step.
public struct ResearchFormStep<Header: View, Content: View>: View {
@State
private var shouldWrapInQuestionCard = true
private let header: Header
private let content: Content
/// Initializes an instance of ``ResearchFormStep`` with the provided configuration.
/// - Parameters:
/// - header: The header that is displayed at the top of the step.
/// - content: The questions that are displayed below the header.
public init(
@ViewBuilder header: () -> Header,
@ViewBuilder content: () -> Content
) {
self.header = header()
self.content = content()
}
@State
private var visibleQuestions = Set<Subview.ID>()
@State
private var requiredQuestions = Set<Subview.ID>()
@State
private var answeredQuestions = Set<Subview.ID>()
private var canMoveToNextStep: Bool {
requiredQuestions
.filter { visibleQuestions.contains($0) }
.subtracting(answeredQuestions).isEmpty
}
public var body: some View {
VStack(alignment: .leading, spacing: 16) {
header
Group(
subviews: cardConsideredContent()
) { questions in
ForEach(subviews: questions) { question in
Group {
if shouldWrapInQuestionCard {
QuestionCard {
VStack(alignment: .leading, spacing: 0) {
if let questionIndex = questions.firstIndex(
where: { $0.id == question.id })
{
let questionNumber = questionIndex + 1
Text(
"Question \(questionNumber) of \(questions.count)"
)
.foregroundColor(.secondary)
.font(.footnote)
#if os(watchOS)
.padding([.horizontal])
.padding(.top, 4)
#else
.fontWeight(.bold)
.padding([.horizontal, .top])
#endif
}
question
}
}
} else {
question
}
}
.onPreferenceChange(QuestionRequiredPreferenceKey.self) {
if $0 == true {
requiredQuestions.insert(question.id)
}
}
.onPreferenceChange(QuestionAnsweredPreferenceKey.self) {
if $0 == true {
answeredQuestions.insert(question.id)
} else {
answeredQuestions.remove(question.id)
}
}
.onAppear {
visibleQuestions.insert(question.id)
}
.onDisappear {
visibleQuestions.remove(question.id)
}
}
}
}
.preference(
key: StepCompletedPreferenceKey.self, value: canMoveToNextStep
)
#if os(iOS)
.frame(maxWidth: .infinity, alignment: .leading)
#endif
.onPreferenceChange(QuestionCardPreferenceKey.self) {
shouldWrapInQuestionCard in
self.shouldWrapInQuestionCard = shouldWrapInQuestionCard
}
}
@ViewBuilder
private func cardConsideredContent() -> some View {
if shouldWrapInQuestionCard {
content.environment(\.isQuestionCardEnabled, false)
} else {
content
}
}
}
extension ResearchFormStep where Header == EmptyView {
/// Initializes an instance of ``ResearchFormStep`` with just the questions and no header.
/// - Parameter content: The questions for the step.
public init(@ViewBuilder content: () -> Content) {
self.init(
header: {
EmptyView()
},
content: content
)
}
}
extension ResearchFormStep where Header == StepHeader, Content == EmptyView {
/// Initializes an instance of ``ResearchFormStep`` with the provided configuration. There are no questions displayed beneath the header.
/// - Parameters:
/// - image: The image displayed in the header.
/// - title: The title displayed in the header.
/// - subtitle: The subtitle displayed in the header.
public init(
image: Image? = nil,
title: String? = nil,
subtitle: String? = nil
) {
self.init(
image: image,
title: title,
subtitle: subtitle,
content: {
EmptyView()
}
)
}
}
extension ResearchFormStep where Header == StepHeader {
/// Initializes an instance of ``ResearchFormStep`` with the provided configuration. The questions are displayed beneath the header.
/// - Parameters:
/// - image: The image displayed in the header.
/// - title: The title displayed in the header.
/// - subtitle: The subtitle displayed in the header.
/// - content: The questions for the step that are displayed beneath the header.
public init(
image: Image? = nil,
title: String? = nil,
subtitle: String? = nil,
@ViewBuilder content: () -> Content
) {
let titleText: Text?
if let title, !title.isEmpty {
titleText = Text(title)
} else {
titleText = nil
}
let subtitleText: Text?
if let subtitle, !subtitle.isEmpty {
subtitleText = Text(subtitle)
} else {
subtitleText = nil
}
self.init(
header: {
StepHeader(
image: image,
title: titleText,
subtitle: subtitleText
)
},
content: content
)
}
}
@@ -0,0 +1,145 @@
/*
Copyright (c) 2024, 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 SwiftUI
struct ResearchFormStepContentView<Content: View>: View {
@EnvironmentObject
private var managedFormResult: ResearchFormResult
private let content: Content
private let isLastStep: Bool
private var onStepCompletion: ((ResearchFormCompletion) -> Void)?
@State
private var doneButtonEnabled: Bool = true
init(
isLastStep: Bool,
onStepCompletion: ((ResearchFormCompletion) -> Void)? = nil,
@ViewBuilder content: () -> Content
) {
self.isLastStep = isLastStep
self.onStepCompletion = onStepCompletion
self.content = content()
}
var body: some View {
StickyScrollView {
content
.onPreferenceChange(StepCompletedPreferenceKey.self) {
doneButtonEnabled = $0
}
.padding()
#if !os(watchOS)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button {
onStepCompletion?(.discarded)
} label: {
Text("Cancel")
}
}
}
#endif
} footerContent: {
Button {
if isLastStep {
onStepCompletion?(.completed(managedFormResult))
} else {
onStepCompletion?(.saved(managedFormResult))
}
} label: {
Text(isLastStep ? "Done" : "Next")
.fontWeight(.bold)
.frame(maxWidth: maxWidthForDoneButton)
.padding(.vertical, 8)
}
.buttonStyle(.researchFormStep)
.disabled(!doneButtonEnabled)
}
.background(Color.choice(for: .secondaryBackground))
.navigationBarTitleDisplayMode(.inline)
}
private var maxWidthForDoneButton: CGFloat {
#if os(visionOS)
300
#else
.infinity
#endif
}
}
private struct ResearchFormStepButtonStyle: ButtonStyle {
@Environment(\.isEnabled)
private var isEnabled
func makeBody(configuration: Configuration) -> some View {
#if os(iOS)
configuration.label
.foregroundStyle(.white)
.frame(height: 50)
.background(
isEnabled ? Color.accentColor : Color.accentColor.opacity(0.5),
in: RoundedRectangle(cornerRadius: 12)
)
#elseif os(visionOS)
configuration.label
.foregroundStyle(isEnabled ? .white : .gray)
.frame(height: 50)
.background(
.ultraThinMaterial,
in: RoundedRectangle(cornerRadius: 25)
)
.hoverEffect(.highlight)
#elseif os(watchOS)
configuration.label
.foregroundStyle(isEnabled ? .white : .gray)
.frame(height: 50)
.background(
Color.choice(for: .systemGray4),
in: RoundedRectangle(cornerRadius: 25)
)
#endif
}
}
extension ButtonStyle where Self == ResearchFormStepButtonStyle {
fileprivate static var researchFormStep: ResearchFormStepButtonStyle {
.init()
}
}
@@ -0,0 +1,105 @@
/*
Copyright (c) 2024, 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 SwiftUI
/// A step header containing an image, title, and subtitle.
public struct StepHeader: View {
private let image: Image?
private let title: Text?
private let subtitle: Text?
/// Initializes an instance of ``StepHeader`` with the provided configuration.
/// - Parameters:
/// - image: The image for this step header.
/// - title: The title for this step header.
/// - subtitle: The subtitle for this header.
init(image: Image? = nil, title: Text? = nil, subtitle: Text? = nil) {
self.image = image
self.title = title
self.subtitle = subtitle
}
public var body: some View {
#if os(watchOS)
compactBody()
#else
defaultBody()
#endif
}
@ViewBuilder
private func compactBody() -> some View {
VStack(alignment: .leading) {
HStack(alignment: .center) {
icon(size: 20)
title?
.font(.title3)
.fontWeight(.bold)
.frame(maxWidth: .infinity, alignment: .leading)
}
subtitle
.font(.body)
}
}
@ViewBuilder
private func defaultBody() -> some View {
VStack(alignment: .leading, spacing: 16) {
icon(size: 80)
title?
.font(.title)
.fontWeight(.bold)
subtitle
}
}
@ViewBuilder
private func icon(size: CGFloat) -> some View {
image?
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: size, height: size)
.foregroundStyle(.stepIconForegroundStyle)
}
}
#Preview {
StepHeader(
image: Image(systemName: "hand.wave"),
title: Text("Welcome"),
subtitle: Text("Hello")
)
}
@@ -0,0 +1,52 @@
/*
Copyright (c) 2024, 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 SwiftUI
extension ShapeStyle where Self == StepIconForegroundStyle {
/// This foreground style is used for labels that display values associated with sliders.
static var stepIconForegroundStyle: StepIconForegroundStyle {
StepIconForegroundStyle()
}
}
struct StepIconForegroundStyle: ShapeStyle {
func resolve(in environment: EnvironmentValues) -> some ShapeStyle {
#if os(visionOS)
.white
#else
Color.accentColor
#endif
}
}
@@ -0,0 +1,39 @@
/*
Copyright (c) 2024, 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 Testing
struct ResearchKitSwiftUITests {
@Test func example() async throws {
// Write your test here and use APIs like `#expect(...)` to check expected conditions.
}
}
+2
View File
@@ -1448,11 +1448,13 @@
XCTAssertEqual([regexAnswerFormat isAnswerValidWithString:incorrectPhoneNumber], NO, @"Should return NO since it is not in the correct format");
}
#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION
- (void)testLocationAnswerFormat {
ORKLocationAnswerFormat *answerFormat = [ORKAnswerFormat locationAnswerFormat];
[answerFormat setUseCurrentLocation:YES];
XCTAssertEqual(answerFormat.useCurrentLocation, YES);
}
#endif
- (void)testWeightAnswerFormat {
ORKWeightAnswerFormat *answerFormat = [ORKAnswerFormat weightAnswerFormatWithMeasurementSystem:ORKMeasurementSystemMetric
+1 -1
View File
@@ -55,7 +55,7 @@ static BOOL ORKIsResearchKitClass(Class class) {
+ (NSArray<NSString *> *)_fetchExclusionList {
NSArray<NSString *> *classesToExclude = @[];
#if !ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION
NSArray<NSString *> *locationClasses = @[
@"ORKLocation",
@@ -40,6 +40,9 @@ NS_ASSUME_NONNULL_BEGIN
@class ORKColorChoiceAnswerFormat;
@class ORKChoiceViewCell;
@class ORKTextChoiceAnswerFormat;
@class ORKScaleAnswerFormat;
@class ORKContinuousScaleAnswerFormat;
@class ORKTextScaleAnswerFormat;
@interface ORKColorChoiceCellGroup : NSObject
@@ -36,7 +36,7 @@
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION
#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && TARGET_OS_IOS
#import "ORKLocationSelectionView.h"
@@ -356,7 +356,7 @@ static const NSString *FormattedAddressLines = @"FormattedAddressLines";
ORKLocation *location = isAnswerClassORKLocation ? (ORKLocation *)_answer : nil;
if (location) {
if (!location.userInput || !location.region |!location.postalAddress) {
// redo geo decoding if any of them is missing
[self reverseGeocodeAndDisplay:location];
@@ -29,8 +29,8 @@
*/
import Foundation
import SwiftUI
import ResearchKit
import SwiftUI
@objc
public class SwiftUIViewFactory: NSObject {
@@ -28,8 +28,8 @@
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import SwiftUI
import ResearchKit
import SwiftUI
struct TextChoiceView: View {
@ObservedObject var textChoiceHelper: SwiftUITextChoiceHelper
@@ -19,12 +19,6 @@
51F210FE2B49D04F0072B536 /* TaskListRowConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F210FD2B49D04F0072B536 /* TaskListRowConstants.swift */; };
863CCD821ACF545E009FD3B4 /* HealthKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 863CCD811ACF545E009FD3B4 /* HealthKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
86B89AA51AB2C0A5001626A4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86B89A9C1AB2C0A5001626A4 /* AppDelegate.swift */; };
8C67958C2BC3B09B00DE7286 /* TaskListRowConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F210FD2B49D04F0072B536 /* TaskListRowConstants.swift */; };
9622283924F04DBF0056E74D /* TaskScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9622283824F04DBF0056E74D /* TaskScreen.swift */; };
9622283B24F04DFE0056E74D /* CommonElements.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9622283A24F04DFE0056E74D /* CommonElements.swift */; };
9622283D24F04E6F0056E74D /* AllowScreens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9622283C24F04E6F0056E74D /* AllowScreens.swift */; };
9622283F24F04EBA0056E74D /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9622283E24F04EBA0056E74D /* Helpers.swift */; };
9622284124F04EEF0056E74D /* PreSubmissionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9622284024F04EEF0056E74D /* PreSubmissionTests.swift */; };
B125E3D41B1BA47600FF8759 /* tap.aif in Resources */ = {isa = PBXBuildFile; fileRef = B125E3D31B1BA47600FF8759 /* tap.aif */; };
BAAC3FD620C4EF1C0065B9E1 /* ResearchKit.pdf in Resources */ = {isa = PBXBuildFile; fileRef = BAAC3FD520C4EF1C0065B9E1 /* ResearchKit.pdf */; };
BC2A3CD91C58F1CA00DA64B7 /* ResearchKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BC2A3CD81C58F1CA00DA64B7 /* ResearchKit.framework */; };
@@ -49,13 +43,6 @@
remoteGlobalIDString = 869230BD1AAA890A00BFE11B;
remoteInfo = ORKCatalog;
};
9622282F24F04D780056E74D /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 869230B61AAA890A00BFE11B /* Project object */;
proxyType = 1;
remoteGlobalIDString = 869230BD1AAA890A00BFE11B;
remoteInfo = ORKCatalog;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
@@ -131,14 +118,6 @@
869230BE1AAA890A00BFE11B /* ORKCatalog.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ORKCatalog.app; sourceTree = BUILT_PRODUCTS_DIR; };
86B89A981AB2C0A5001626A4 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
86B89A9C1AB2C0A5001626A4 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = ORKCatalog/AppDelegate.swift; sourceTree = SOURCE_ROOT; };
9622282A24F04D780056E74D /* ORKCatalogUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ORKCatalogUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
9622282E24F04D780056E74D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
9622283824F04DBF0056E74D /* TaskScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskScreen.swift; sourceTree = "<group>"; };
9622283A24F04DFE0056E74D /* CommonElements.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonElements.swift; sourceTree = "<group>"; };
9622283C24F04E6F0056E74D /* AllowScreens.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllowScreens.swift; sourceTree = "<group>"; };
9622283E24F04EBA0056E74D /* Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = "<group>"; };
9622284024F04EEF0056E74D /* PreSubmissionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreSubmissionTests.swift; sourceTree = "<group>"; };
9632B5D524F093070010818C /* ORKCatalogUI.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = ORKCatalogUI.xctestplan; sourceTree = "<group>"; };
B125E3D31B1BA47600FF8759 /* tap.aif */ = {isa = PBXFileReference; lastKnownFileType = file; path = tap.aif; sourceTree = "<group>"; };
BAAC3FD520C4EF1C0065B9E1 /* ResearchKit.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = ResearchKit.pdf; sourceTree = "<group>"; };
BAFAD7AA1EFB2907004DEC45 /* fr-CA */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "fr-CA"; path = fr_CA.lproj/Localizable.strings; sourceTree = "<group>"; };
@@ -172,13 +151,6 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
9622282724F04D780056E74D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
@@ -233,7 +205,6 @@
children = (
5116D0BF2677FB780010FAB1 /* ResearchKit-Shared.xcconfig */,
3E39B9E81ABF675000C2ABE5 /* README.md */,
9622282B24F04D780056E74D /* ORKCatalogUITests */,
51AF19782B59F1E500D3B399 /* ORKCatalogTests */,
BC2A3C9E1C58E81500DA64B7 /* Frameworks */,
869230C01AAA890A00BFE11B /* ORKCatalog */,
@@ -245,7 +216,6 @@
isa = PBXGroup;
children = (
869230BE1AAA890A00BFE11B /* ORKCatalog.app */,
9622282A24F04D780056E74D /* ORKCatalogUITests.xctest */,
51AF19772B59F1E400D3B399 /* ORKCatalogTests.xctest */,
);
name = Products;
@@ -274,28 +244,6 @@
path = "Supporting Files";
sourceTree = "<group>";
};
8CDBF98A2AFD9FC800AF767B /* Screens */ = {
isa = PBXGroup;
children = (
9622283824F04DBF0056E74D /* TaskScreen.swift */,
9622283A24F04DFE0056E74D /* CommonElements.swift */,
9622283C24F04E6F0056E74D /* AllowScreens.swift */,
);
path = Screens;
sourceTree = "<group>";
};
9622282B24F04D780056E74D /* ORKCatalogUITests */ = {
isa = PBXGroup;
children = (
8CDBF98A2AFD9FC800AF767B /* Screens */,
9622282E24F04D780056E74D /* Info.plist */,
9622283E24F04EBA0056E74D /* Helpers.swift */,
9622284024F04EEF0056E74D /* PreSubmissionTests.swift */,
9632B5D524F093070010818C /* ORKCatalogUI.xctestplan */,
);
path = ORKCatalogUITests;
sourceTree = "<group>";
};
BC2A3C9E1C58E81500DA64B7 /* Frameworks */ = {
isa = PBXGroup;
children = (
@@ -360,24 +308,6 @@
productReference = 869230BE1AAA890A00BFE11B /* ORKCatalog.app */;
productType = "com.apple.product-type.application";
};
9622282924F04D780056E74D /* ORKCatalogUITests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 9622283624F04D780056E74D /* Build configuration list for PBXNativeTarget "ORKCatalogUITests" */;
buildPhases = (
9622282624F04D780056E74D /* Sources */,
9622282724F04D780056E74D /* Frameworks */,
9622282824F04D780056E74D /* Resources */,
);
buildRules = (
);
dependencies = (
9622283024F04D780056E74D /* PBXTargetDependency */,
);
name = ORKCatalogUITests;
productName = ORKCatalogUITests;
productReference = 9622282A24F04D780056E74D /* ORKCatalogUITests.xctest */;
productType = "com.apple.product-type.bundle.ui-testing";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
@@ -398,12 +328,6 @@
LastSwiftMigration = 1530;
ProvisioningStyle = Automatic;
};
9622282924F04D780056E74D = {
CreatedOnToolsVersion = 11.6;
DevelopmentTeam = "";
ProvisioningStyle = Automatic;
TestTargetID = 869230BD1AAA890A00BFE11B;
};
};
};
buildConfigurationList = 869230B91AAA890A00BFE11B /* Build configuration list for PBXProject "ORKCatalog" */;
@@ -467,7 +391,6 @@
projectRoot = "";
targets = (
869230BD1AAA890A00BFE11B /* ORKCatalog */,
9622282924F04D780056E74D /* ORKCatalogUITests */,
51AF19762B59F1E400D3B399 /* ORKCatalogTests */,
);
};
@@ -495,13 +418,6 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
9622282824F04D780056E74D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -531,19 +447,6 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
9622282624F04D780056E74D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
9622283924F04DBF0056E74D /* TaskScreen.swift in Sources */,
9622283B24F04DFE0056E74D /* CommonElements.swift in Sources */,
8C67958C2BC3B09B00DE7286 /* TaskListRowConstants.swift in Sources */,
9622284124F04EEF0056E74D /* PreSubmissionTests.swift in Sources */,
9622283F24F04EBA0056E74D /* Helpers.swift in Sources */,
9622283D24F04E6F0056E74D /* AllowScreens.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
@@ -552,11 +455,6 @@
target = 869230BD1AAA890A00BFE11B /* ORKCatalog */;
targetProxy = 51AF197B2B59F1E500D3B399 /* PBXContainerItemProxy */;
};
9622283024F04D780056E74D /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 869230BD1AAA890A00BFE11B /* ORKCatalog */;
targetProxy = 9622282F24F04D780056E74D /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
@@ -860,68 +758,6 @@
};
name = Debug;
};
9622283124F04D780056E74D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = "";
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = ORKCatalogUITests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.HRP.ORKCatalogUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_TARGET_NAME = ORKCatalog;
};
name = Debug;
};
9622283424F04D780056E74D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = ORKCatalogUITests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.HRP.ORKCatalogUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_TARGET_NAME = ORKCatalog;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
@@ -952,15 +788,6 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Debug;
};
9622283624F04D780056E74D /* Build configuration list for PBXNativeTarget "ORKCatalogUITests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
9622283124F04D780056E74D /* Debug */,
9622283424F04D780056E74D /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Debug;
};
/* End XCConfigurationList section */
};
rootObject = 869230B61AAA890A00BFE11B /* Project object */;
@@ -1,105 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1500"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "869230BD1AAA890A00BFE11B"
BuildableName = "ORKCatalog.app"
BlueprintName = "ORKCatalog"
ReferencedContainer = "container:ORKCatalog.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<TestPlans>
<TestPlanReference
reference = "container:ORKCatalogUITests/ORKCatalogUI.xctestplan"
default = "YES">
</TestPlanReference>
</TestPlans>
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "9622282924F04D780056E74D"
BuildableName = "ORKCatalogUITests.xctest"
BlueprintName = "ORKCatalogUITests"
ReferencedContainer = "container:ORKCatalog.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "51AF19762B59F1E400D3B399"
BuildableName = "ORKCatalogTests.xctest"
BlueprintName = "ORKCatalogTests"
ReferencedContainer = "container:ORKCatalog.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "869230BD1AAA890A00BFE11B"
BuildableName = "ORKCatalog.app"
BlueprintName = "ORKCatalog"
ReferencedContainer = "container:ORKCatalog.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "869230BD1AAA890A00BFE11B"
BuildableName = "ORKCatalog.app"
BlueprintName = "ORKCatalog"
ReferencedContainer = "container:ORKCatalog.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
@@ -126,3 +126,5 @@ extension ResultViewController: ResultProviderDelegate {
present(shareSheet, animated: true, completion: nil)
}
}
@@ -286,3 +286,5 @@ class TaskListViewController: UITableViewController, ORKTaskViewControllerDelega
}
}
@@ -1,297 +0,0 @@
/*
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 Foundation
import XCTest
class Helpers: XCTestCase {
let app = XCUIApplication()
let commonElements = CommonElements()
let taskScreen = TaskScreen()
enum SliderTesting {
case slider1
case slider2
case slider3
case slider4
case slider5
case slider6
}
enum SwipeDirection {
case up
case down
case left
case right
case skip
}
//Verify existence of any XCUIElement with an option to tap that element
func verifyElement(_ element: XCUIElement, _ tap: Bool = false) -> Bool {
if element.exists {
if tap && element.isEnabled {
element.tap()
}
return true
}
XCTFail("Unable to confirm \(element) exists")
return false
}
//Verify an element exists based on static text label, option to tap the element after verification
func verifyElementByText(identifier: String, _ tap: Bool = false) -> Bool {
let item = app.staticTexts["\(identifier)"]
if item.exists {
if tap && item.isEnabled {
item.tap()
}
return true
}
XCTFail("Unable to locate Element based on text \(identifier)")
return false
}
//Verify an element matching XCUIElement.ElementType exists based on an identifying string existing in the label or value of the element.
func verifyElementByType(_ elementType: XCUIElement.ElementType, _ identifier: String, _ tap: Bool = false) -> Bool {
let identifyingOptions = ["label", "value"]
for option in identifyingOptions {
let predicate = NSPredicate(format: "\(option) CONTAINS '\(identifier)'")
let target = app.descendants(matching: elementType).element(matching: predicate)
if target.exists {
if tap && target.isEnabled {
target.tap()
}
return true
}
}
XCTFail("Unable to locate Element based on text \(identifier)")
return false
}
//Verify any XCUIElement exists and return that element for assignment, option to tap available
func verifyAndAssign(_ element: XCUIElement, _ tap: Bool = false) -> XCUIElement? {
if element.exists {
if tap && element.isEnabled {
element.tap()
}
return element
}
XCTFail("Unable to confirm \(element) exists")
return nil
}
//Verify an element exists based on static text label, option to tap the element after verification. Element returned for assignment.
func verifyAndAssignByText(_ identifier: String, _ tap: Bool = false) -> XCUIElement? {
let item = app.staticTexts["\(identifier)"]
if item.exists {
if tap && item.isEnabled {
item.tap()
}
return item
}
XCTFail("Unable to locate Element based on text \(identifier)")
return nil
}
//Verify an element matching XCUIElement.ElementType exists based on an identifying string existing in the label or value of the element.
//Element returned for assignment.
func verifyAndAssignByType(_ elementType: XCUIElement.ElementType, _ identifier: String, _ tap: Bool = false) -> XCUIElement? {
let identifyingOptions = ["label", "value"]
for option in identifyingOptions {
let predicate = NSPredicate(format: "\(option) CONTAINS '\(identifier)'")
let target = app.descendants(matching: elementType).element(matching: predicate)
if target.exists {
if tap && target.isEnabled {
target.tap()
}
return target
}
}
XCTFail("Unable to locate Element based on text \(identifier)")
return nil
}
//Enter any ORKCatalog task based on the name of the task and leave without taking action
func launchAndLeave(_ task: String) -> Bool {
XCTAssert(verifyElement(taskScreen.mainTaskScreen))
let currentTask = verifyAndAssignByText(task)!
currentTask.tap()
sleep(1)
guard let cancelButton = commonElements.cancelButton else {
XCTFail("Unable to locate Cancel Button")
return false
}
cancelButton.tap()
sleep(1)
guard let exitButton = commonElements.getExitButton() else {
XCTFail("Unable to locate End Task or Discard Results button")
return false
}
exitButton.tap()
return verifyElement(taskScreen.mainTaskScreen)
}
//Deletes the ORKCatalog app from the simulator instance.
func deleteORKCatalog() {
app.terminate()
let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
let icon = springboard.icons["ORKCatalog"]
if icon.exists {
icon.press(forDuration: 1.3)
springboard.buttons["Delete App"].tap()
sleep(2)
springboard.buttons["Delete"].tap()
}
}
//Verify that the Navigation bar includes a page count in the form of "pageNumber of pageTotal"
func verifyPageByCount(_ pageNumber: Int, _ pageTotal: Int) -> Bool {
let pageCount = app.navigationBars["\(String(pageNumber)) of \(String(pageTotal))"]
XCTAssert(pageCount.exists)
return true
}
//Return an XCUIElement based on page number of the Scale test
func scaleTitle(_ pageNumber: Int) -> XCUIElement? {
let titles: [Int: String] = [1: "Discrete Scale", 2: "Continuous Scale", 3: "Discrete Vertical Scale", 4: "Continuous Vertical Scale", 5: "Text Scale", 6: "Text Vertical Scale"]
return app.scrollViews.otherElements.staticTexts[titles[pageNumber] ?? "Invalid Page Number"]
}
//Select and interact with sliders in the Scale Question task. Function verifies expected outputs of each slider.
func sliderValuesCheck(_ sliderScreen:SliderTesting, _ screenNum: Int, _ direction:SwipeDirection) -> Bool {
let sliderValues: [SliderTesting:String] = [
.slider1:"8",
.slider3:"8",
.slider4:"4.23",
.slider5:"Above Average",
.slider6:"Above Average"
]
let slider = verifyAndAssignByType(.slider, "Response slider")!
XCTAssert(slider.waitForExistence(timeout: 2), "Unable to locate slider on page \(screenNum)")
XCTAssert(verifyPageByCount(screenNum, 6), "Unable to locate \"\(screenNum) of 6\"")
XCTAssert(scaleTitle(screenNum)!.waitForExistence(timeout: 2), "Unable to locate \(scaleTitle(screenNum)!)")
switch direction {
case .up:
slider.swipeUp()
case .right:
slider.swipeRight()
case .left:
XCTFail("Unexpected Swipe Direction of Left Entered")
case .down:
XCTFail("Unexpected Swipe Direction of Down Entered")
case .skip:
XCTAssert(commonElements.skipButton!.waitForExistence(timeout: 2))
commonElements.skipButton!.tap()
}
if sliderScreen != .slider2 {
XCTAssert(verifyElementByType(.slider, sliderValues[sliderScreen]!))
if commonElements.doneButton!.exists {
commonElements.doneButton!.tap()
return true
}
XCTAssert(commonElements.nextButton!.waitForExistence(timeout: 2))
commonElements.nextButton!.tap()
return true
}
return true
}
//Returns true if sliders behave as expected on any given screen in the Scale Question task.
func sliderScreenCheck(_ sliderScreen:SliderTesting) -> Bool {
XCTAssert(verifyElement(app.scrollViews.otherElements.staticTexts["Scale"]))
XCTAssert(verifyElement(app.scrollViews.otherElements.staticTexts["Your question here."]))
switch sliderScreen {
case .slider1:
XCTAssert(sliderValuesCheck(sliderScreen, 1, .right), "Slider1 Values Check Failed")
case .slider2:
XCTAssert(sliderValuesCheck(sliderScreen, 2, .skip), "Slider2 Values Check Failed")
case .slider3:
XCTAssert(sliderValuesCheck(sliderScreen, 3, .up), "Slider3 Values Check Failed")
case .slider4:
XCTAssert(sliderValuesCheck(sliderScreen, 4, .up), "Slider4 Values Check Failed")
case .slider5:
XCTAssert(sliderValuesCheck(sliderScreen, 5, .right), "Slider5 Values Check Failed")
case .slider6:
XCTAssert(sliderValuesCheck(sliderScreen, 6, .up), "Slider6 Values Check Failed")
}
return true
}
//Monitors for any alerts that may appear and interupt the normal flow of the applicaiton.
func monitorAlerts() {
addUIInterruptionMonitor(withDescription: "Alert") { element in
do {
// Push Notification
let button = element.buttons["Allow"]
let title = element.staticTexts["“ORKCatalog” Would Like to Send You Notifications"]
if title.exists && button.exists {
button.tap()
return true
}
}
do {
// Location
let button = element.buttons["Allow While Using App"]
if button.exists {
button.tap()
return true
}
}
do {
// Microphone
let button = element.buttons["OK"]
let title = element.staticTexts["“ORKCatalog” Would Like to Access the Microphone"]
if title.exists && button.exists {
button.tap()
return true
}
}
return false
}
}
func verifyElementByText(_ text: String, _ tap: Bool = false) -> Bool {
let item = app.staticTexts["\(text)"]
XCTAssert(item.waitForExistence(timeout: 3))
if tap && item.isEnabled {
item.tap()
}
return true
}
}
@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>
@@ -1,46 +0,0 @@
{
"configurations" : [
{
"id" : "CE2DCF31-672A-4F3F-A538-AF0ADA4806F4",
"name" : "Configuration 1",
"options" : {
}
}
],
"defaultOptions" : {
"codeCoverage" : false,
"targetForVariableExpansion" : {
"containerPath" : "container:ORKCatalog.xcodeproj",
"identifier" : "869230BD1AAA890A00BFE11B",
"name" : "ORKCatalog"
}
},
"testTargets" : [
{
"selectedTests" : [
"PreSubmissionTests\/testAccessActiveTasks()",
"PreSubmissionTests\/testAccessSurveyQuestions()",
"PreSubmissionTests\/testAccessSurveyTasks()",
"PreSubmissionTests\/testImageMultipleChoice()",
"PreSubmissionTests\/testSQPickerWheel()",
"PreSubmissionTests\/testSQSliders()",
"PreSubmissionTests\/testSQTextEntry()",
"PreSubmissionTests\/testWrittenMultipleChoice()"
],
"target" : {
"containerPath" : "container:ORKCatalog.xcodeproj",
"identifier" : "9622282924F04D780056E74D",
"name" : "ORKCatalogUITests"
}
},
{
"target" : {
"containerPath" : "container:ORKCatalog.xcodeproj",
"identifier" : "51AF19762B59F1E400D3B399",
"name" : "ORKCatalogTests"
}
}
],
"version" : 1
}

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