Compare commits

..

2 Commits

Author SHA1 Message Date
Pariece McKinney 5a96848943 Public release 3.1.1 2024-11-13 13:23:22 -05:00
simon-apple 45f7bce91f Added issue templates. (#1586) 2024-11-07 14:53:39 -08:00
108 changed files with 601 additions and 8121 deletions
+34
View File
@@ -0,0 +1,34 @@
---
name: Bug Report
about: File a bug to help us improve ResearchKit
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots or screen recordings**
Screenshots or screen recordings help us understand your problem with more clarity.
**ResearchKit Version**
- Version: [e.g. 3.0.1]
**Device Information**
- OS: [e.g. iOS 18.1/watchOS 11.0/visionOS 2.0]
- Device: [e.g. iPhone 16 Pro]
**Additional context**
Add surrounding context to bring more clarity to your problem.
+20
View File
@@ -0,0 +1,20 @@
---
name: Feature Request
about: Suggest an idea for ResearchKit
title: ''
labels: new feature
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
@@ -0,0 +1,10 @@
---
name: General Question
about: Ask a general question about ResearchKit's capabilities
title: ''
labels: question
assignees: ''
---
@@ -0,0 +1,10 @@
---
name: Technical Question
about: Ask a question about ResearchKit's API
title: ''
labels: tech question
assignees: ''
---
+1 -72
View File
@@ -1,3 +1,4 @@
![ResearchKit](https://github.com/user-attachments/assets/0384c1a6-ec67-45d3-be68-136a2e4cacff)
ResearchKit Framework
@@ -79,78 +80,6 @@ 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
+13
View File
@@ -1,5 +1,18 @@
# ResearchKit Release Notes
## ResearchKit 3.1.1 Release Notes
General bug fixes for the following:
- **ORKMotionActivityPermissionType**
Fixed issue that caused the next button to remain disabled after gaining permission from user.
- **ORKRegistrationStep**
Removed yellow overlay that prevented password entry.
- **ORKdBHLToneAudiometryStep**
Fixed issue that caused the tap button to appear twice.
## ResearchKit 3.1 Release Notes
In addition to general stability and performance improvements, ResearchKit 3.1 includes the following updates:
-3
View File
@@ -7,7 +7,4 @@
<FileRef
location = "group:samples/ORKCatalog/ORKCatalog.xcodeproj">
</FileRef>
<FileRef
location = "group:ResearchKitSwiftUI/ResearchKitSwiftUI.xcodeproj">
</FileRef>
</Workspace>
+187 -196
View File
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 60;
objectVersion = 54;
objects = {
/* Begin PBXAggregateTarget section */
@@ -11,7 +11,6 @@
isa = PBXAggregateTarget;
buildConfigurationList = 0BC672D92BD9C52D005798AC /* Build configuration list for PBXAggregateTarget "ResearchKitAllTargets" */;
buildPhases = (
0BC672E82BD9C69C005798AC /* ShellScript */,
);
dependencies = (
0BC672E02BD9C541005798AC /* PBXTargetDependency */,
@@ -24,19 +23,19 @@
/* End PBXAggregateTarget section */
/* Begin PBXBuildFile section */
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; };
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 */; };
03057F492518ECDC00C4EC5B /* ORKAudioStepViewControllerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 03057F482518ECDC00C4EC5B /* ORKAudioStepViewControllerTests.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; };
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 */; };
0324C1D825439E1800BBE77B /* ORKVideoInstructionStepViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0324C1D725439E1800BBE77B /* ORKVideoInstructionStepViewControllerTests.swift */; };
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; };
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 */; };
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 +49,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 */; platformFilter = ios; };
0BE9D5272947EA4900DA0625 /* ORKConsentDocument+ORKInstructionStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 0BE9D5252947EA4900DA0625 /* ORKConsentDocument+ORKInstructionStep.m */; };
0BFD27562B8D1D3B00B540E8 /* ORKJSONSerializationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 51AF19562B583BBA00D3B399 /* ORKJSONSerializationTests.m */; };
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, ); }; };
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, ); }; };
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 +72,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 */; 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, ); }; };
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, ); }; };
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 */; 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; };
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 */; };
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 */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
511987C4246330CA004FC2C7 /* ORKRequestPermissionsStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 511987C2246330CA004FC2C7 /* ORKRequestPermissionsStep.m */; platformFilter = ios; };
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 */; };
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 +179,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 */; 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, ); }; };
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, ); }; };
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 */; 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, ); }; };
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, ); }; };
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 +203,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 */; 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; };
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 */; };
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,12 +226,13 @@
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 */; 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, ); }; };
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, ); }; };
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 */; };
@@ -241,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 */; 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, ); }; };
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, ); }; };
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 */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; };
51B94DC72B3254FE0039B0E7 /* CLLocationManager+ResearchKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 51B94DC62B3254FD0039B0E7 /* CLLocationManager+ResearchKit.m */; platformFilter = ios; };
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 */; };
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 */; };
@@ -268,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 */; 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; };
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 */; };
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 */; };
@@ -285,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 */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
51F716CB297E2A1100D8ACF7 /* ORKConsentDocument+ORKInstructionStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 0BE9D5242947EA4900DA0625 /* ORKConsentDocument+ORKInstructionStep.h */; 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, ); }; };
@@ -294,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 */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; };
5D04885C25F1B3AB0006C68B /* ORKDevice_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D04885B25F1B3AB0006C68B /* ORKDevice_Private.h */; 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 */; };
@@ -303,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 */; platformFilter = ios; };
714080DB235FD14700281E04 /* ResearchKit.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 714080D9235FD14700281E04 /* ResearchKit.stringsdict */; };
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 */; 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; };
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 */; };
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 */; 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; };
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 */; };
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, ); }; };
@@ -332,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 */; platformFilter = ios; };
86C40D1A1A8D7C5C00081FAC /* ORKFormItem_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B7F1A8D7C5C00081FAC /* ORKFormItem_Internal.h */; };
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 */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
86C40D321A8D7C5C00081FAC /* ORKHealthAnswerFormat.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B8B1A8D7C5C00081FAC /* ORKHealthAnswerFormat.m */; platformFilter = ios; };
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 */; };
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 */; };
@@ -344,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 */; platformFilter = ios; };
86C40D621A8D7C5C00081FAC /* ORKQuestionStep_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BA31A8D7C5C00081FAC /* ORKQuestionStep_Internal.h */; };
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, ); }; };
@@ -352,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 */; 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; };
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 */; };
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 */; };
@@ -371,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 */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
B11C549B1A9EEF8800265E61 /* ORKConsentSharingStep.m in Sources */ = {isa = PBXBuildFile; fileRef = B11C54971A9EEF8800265E61 /* ORKConsentSharingStep.m */; platformFilter = ios; };
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 */; };
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 */; platformFilter = ios; };
B1C7955E1A9FBF04007279BA /* HealthKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B1C7955D1A9FBF04007279BA /* HealthKit.framework */; platformFilter = ios; settings = {ATTRIBUTES = (Required, ); }; };
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, ); }; };
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 */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
BABBB1AF2097D97200CB29E5 /* ORKPDFViewerStep.m in Sources */ = {isa = PBXBuildFile; fileRef = BABBB1AD2097D97200CB29E5 /* ORKPDFViewerStep.m */; platformFilter = ios; };
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 */; };
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 */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; };
BC081DE224CBC4DE00AD92AA /* ORKTypes_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = BC081DE124CBC4DE00AD92AA /* ORKTypes_Private.h */; 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, ); }; };
@@ -394,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 */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
BC94EF361E96394C00143081 /* ORKRegistrationStep_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = BC94EF351E96394C00143081 /* ORKRegistrationStep_Internal.h */; platformFilter = ios; };
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 */; };
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 */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; };
BCCE9EC121104B2200B809F8 /* ORKConsentDocument_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = BCCE9EC021104B2200B809F8 /* ORKConsentDocument_Private.h */; 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 */; 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; };
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 */; };
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 */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; };
CA23A31D2899941700EC6E91 /* ORKSkin_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = CA23A31C2899941700EC6E91 /* ORKSkin_Private.h */; 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 */; };
@@ -537,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 */; 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, ); }; };
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, ); }; };
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 */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; };
CA2B901228A17D170025B773 /* ORKFileResult.m in Sources */ = {isa = PBXBuildFile; fileRef = FF919A3A1E81AF1D005C2A1E /* ORKFileResult.m */; platformFilter = ios; };
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 */; };
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 */; 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; };
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 */; };
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, ); }; };
@@ -566,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 */; 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; };
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 */; };
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 */; platformFilter = ios; };
CA994D5628AB08410019DEA4 /* ORKDeprecated.m in Sources */ = {isa = PBXBuildFile; fileRef = BC94EF301E962F7400143081 /* ORKDeprecated.m */; };
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, ); }; };
@@ -988,42 +988,41 @@
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 */; 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 */; };
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 */; };
FA7A9D2B1B082688005A2BEA /* ORKConsentDocumentTests.m in Sources */ = {isa = PBXBuildFile; fileRef = FA7A9D2A1B082688005A2BEA /* ORKConsentDocumentTests.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; };
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 */; };
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 */; 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; };
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 */; };
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 */; 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; };
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 */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -4669,20 +4668,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,16 +4706,6 @@
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 */,
@@ -4739,16 +4728,25 @@
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 */,
@@ -4891,7 +4889,6 @@
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 */,
@@ -5239,7 +5236,7 @@
attributes = {
BuildIndependentTargetsInParallel = YES;
CLASSPREFIX = ORK;
LastSwiftUpdateCheck = 1600;
LastSwiftUpdateCheck = 1530;
LastUpgradeCheck = 1500;
ORGANIZATIONNAME = researchkit.org;
TargetAttributes = {
@@ -5261,7 +5258,7 @@
};
};
buildConfigurationList = 3FFF18381829DB1D00167070 /* Build configuration list for PBXProject "ResearchKit" */;
compatibilityVersion = "Xcode 15.0";
compatibilityVersion = "Xcode 3.2";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
@@ -5532,18 +5529,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 */,
@@ -5563,7 +5560,6 @@
51A11F192BD08D5E0060C07E /* HKSample+ORKJSONDictionary.m in Sources */,
86C40D581A8D7C5C00081FAC /* ORKOrderedTask.m in Sources */,
86C40D601A8D7C5C00081FAC /* ORKQuestionStep.m in Sources */,
BCB080A11B83EFB900A3F400 /* ORKStepNavigationRule.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -5695,6 +5691,7 @@
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 */,
@@ -6042,7 +6039,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 5D000ED12620F27100E5442A /* Project-Debug.xcconfig */;
buildSettings = {
ENABLE_MODULE_VERIFIER = NO;
ENABLE_MODULE_VERIFIER = YES;
ENABLE_TESTING_SEARCH_PATHS = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
};
@@ -6096,11 +6093,9 @@
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 watchos watchsimulator xros xrsimulator";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
TARGETED_DEVICE_FAMILY = "1,2,4,7";
WATCHOS_DEPLOYMENT_TARGET = 10.0;
XROS_DEPLOYMENT_TARGET = 1.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
@@ -6110,11 +6105,9 @@
buildSettings = {
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MODULEMAP_PRIVATE_FILE = ResearchKit/ResearchKit_Private.modulemap;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator watchos watchsimulator xros xrsimulator";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
TARGETED_DEVICE_FAMILY = "1,2,4,7";
WATCHOS_DEPLOYMENT_TARGET = 10.0;
XROS_DEPLOYMENT_TARGET = 1.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
@@ -6126,7 +6119,6 @@
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;
@@ -6143,7 +6135,6 @@
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,36 +54,6 @@
<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>
+1 -5
View File
@@ -14,11 +14,7 @@
"testTargets" : [
{
"skippedTests" : [
"ORKDataCollectionTests",
"ORKJSONSerializationTests\/testEquality",
"ORKJSONSerializationTests\/testORKSampleDeserialization",
"ORKJSONSerializationTests\/testORKSerialization",
"ORKJSONSerializationTests\/testSecureCoding"
"ORKDataCollectionTests"
],
"target" : {
"containerPath" : "container:ResearchKit.xcodeproj",
+13 -4
View File
@@ -32,7 +32,11 @@
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;
@@ -52,6 +56,7 @@
@class ORKLocationAnswerFormat;
@class ORKSESAnswerFormat;
@class ORKImageChoice;
#endif
NS_ASSUME_NONNULL_BEGIN
@@ -434,6 +439,10 @@ ORK_CLASS_AVAILABLE
@end
#pragma mark - iOS
#if TARGET_OS_IOS
@interface ORKAnswerFormat()
/// @name Factory methods
@@ -523,7 +532,7 @@ ORK_CLASS_AVAILABLE
minimumValue:(double)minimumValue
maximumValue:(double)maximumValue
defaultValue:(double)defaultValue;
#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && TARGET_OS_IOS
#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION
+ (ORKLocationAnswerFormat *)locationAnswerFormat;
#endif
@@ -1781,7 +1790,6 @@ 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.
@@ -1824,7 +1832,6 @@ 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
@@ -2207,7 +2214,7 @@ ORK_CLASS_AVAILABLE
An `ORKLocationAnswerFormat` object produces an `ORKLocationQuestionResult` object.
*/
#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && TARGET_OS_IOS
#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION
ORK_CLASS_AVAILABLE
@interface ORKLocationAnswerFormat : ORKAnswerFormat
@@ -2243,4 +2250,6 @@ ORK_CLASS_AVAILABLE
@end
#endif
NS_ASSUME_NONNULL_END
+26 -10
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,6 +289,8 @@ static NSNumberFormatterStyle ORKNumberFormattingStyleConvert(ORKNumberFormattin
@implementation ORKAnswerFormat
#if TARGET_OS_IOS
+ (ORKScaleAnswerFormat *)scaleAnswerFormatWithMaximumValue:(NSInteger)scaleMaximum
minimumValue:(NSInteger)scaleMinimum
defaultValue:(NSInteger)defaultValue
@@ -422,7 +424,7 @@ static NSNumberFormatterStyle ORKNumberFormattingStyleConvert(ORKNumberFormattin
calendar:calendar];
[answerFormat setDaysBeforeCurrentDateToSetMinimumDate:daysBefore];
[answerFormat setDaysAfterCurrentDateToSetMinimumDate:daysAfter];
return answerFormat;
}
@@ -487,7 +489,7 @@ static NSNumberFormatterStyle ORKNumberFormattingStyleConvert(ORKNumberFormattin
defaultValue:defaultValue];
}
#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && TARGET_OS_IOS
#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION
+ (ORKLocationAnswerFormat *)locationAnswerFormat {
return [ORKLocationAnswerFormat new];
}
@@ -499,6 +501,8 @@ static NSNumberFormatterStyle ORKNumberFormattingStyleConvert(ORKNumberFormattin
bottomRungText:bottomRungText];
}
#endif
+ (ORKBooleanAnswerFormat *)booleanAnswerFormat {
return [ORKBooleanAnswerFormat new];
}
@@ -684,6 +688,7 @@ static NSArray *ork_processTextChoices(NSArray<ORKTextChoice *> *textChoices) {
return choices;
}
#if TARGET_OS_IOS
@implementation ORKValuePickerAnswerFormat {
ORKChoiceAnswerFormatHelper *_helper;
ORKTextChoice *_nullTextChoice;
@@ -836,7 +841,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));
@@ -885,7 +890,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) {
@@ -896,11 +901,11 @@ static NSArray *ork_processTextChoices(NSArray<ORKTextChoice *> *textChoices) {
*stop = YES;
}
}];
if (answerTexts.count != self.valuePickers.count) {
return nil;
}
return [answerTexts componentsJoinedByString:self.separator];
}
@@ -1022,6 +1027,8 @@ static NSArray *ork_processTextChoices(NSArray<ORKTextChoice *> *textChoices) {
}
@end
#endif
#pragma mark - ORKTextChoiceAnswerFormat
@@ -1481,6 +1488,7 @@ NSArray<Class> *ORKAllowableValueClasses(void) {
#pragma mark - ORKTextChoiceOther
#if TARGET_OS_IOS
@implementation ORKTextChoiceOther
+ (instancetype)new {
@@ -1704,6 +1712,7 @@ NSArray<Class> *ORKAllowableValueClasses(void) {
}
@end
#endif
#pragma mark - ORKBooleanAnswerFormat
@@ -1791,7 +1800,7 @@ NSArray<Class> *ORKAllowableValueClasses(void) {
#pragma mark - ORKTimeOfDayAnswerFormat
#if TARGET_OS_IOS
@implementation ORKTimeOfDayAnswerFormat
- (instancetype)init {
@@ -2818,7 +2827,7 @@ NSArray<Class> *ORKAllowableValueClasses(void) {
#pragma mark - ORKTextScaleAnswerFormat
#if TARGET_OS_IOS
@interface ORKTextScaleAnswerFormat () {
ORKChoiceAnswerFormatHelper *_helper;
@@ -3029,15 +3038,19 @@ NSArray<Class> *ORKAllowableValueClasses(void) {
@end
#endif
#pragma mark - ORKTextAnswerFormat
@interface ORKTextAnswerFormat()
@end
@implementation ORKTextAnswerFormat
- (Class)questionResultClass {
return [ORKTextQuestionResult class];
}
@@ -3056,6 +3069,7 @@ NSArray<Class> *ORKAllowableValueClasses(void) {
}
- (instancetype)initWithMaximumLength:(NSInteger)maximumLength {
self = [super init];
if (self) {
@@ -3307,6 +3321,7 @@ static NSString *const kSecureTextEntryEscapeString = @"*";
return answerString;
}
- (ORKQuestionResult *)resultWithIdentifier:(NSString *)identifier answer:(id)answer {
ORKQuestionResult *questionResult = nil;
questionResult = (ORKQuestionResult *)[super resultWithIdentifier:identifier answer:answer];
@@ -4004,7 +4019,7 @@ static const NSInteger ORKAgeAnswerDefaultMaxAge = 125;
@end
#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && TARGET_OS_IOS
#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION
#pragma mark - ORKLocationAnswerFormat
@implementation ORKLocationAnswerFormat
@@ -4138,3 +4153,4 @@ static const NSInteger ORKAgeAnswerDefaultMaxAge = 125;
}
@end
#endif
@@ -33,9 +33,10 @@
#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
@@ -57,7 +58,7 @@ NSString *ORKQuestionTypeString(ORKQuestionType questionType);
@end
ORK_DESIGNATE_CODING_AND_SERIALIZATION_INITIALIZERS(ORKAnswerFormat)
#if TARGET_OS_IOS || TARGET_OS_VISION
#if TARGET_OS_IOS
ORK_DESIGNATE_CODING_AND_SERIALIZATION_INITIALIZERS(ORKImageChoiceAnswerFormat)
ORK_DESIGNATE_CODING_AND_SERIALIZATION_INITIALIZERS(ORKValuePickerAnswerFormat)
ORK_DESIGNATE_CODING_AND_SERIALIZATION_INITIALIZERS(ORKMultipleValuePickerAnswerFormat)
@@ -134,11 +135,6 @@ ORK_DESIGNATE_CODING_AND_SERIALIZATION_INITIALIZERS(ORKTextChoice)
@end
@interface ORKDateAnswerFormat () {
NSDate *_currentDateOverride;
}
@end
#if TARGET_OS_IOS
@protocol ORKScaleAnswerFormatProvider <NSObject>
@@ -202,7 +198,6 @@ NSArray<Class> *ORKAllowableValueClasses(void);
@end
#if TARGET_OS_IOS
@interface ORKValuePickerAnswerFormat ()
- (instancetype)initWithTextChoices:(NSArray<ORKTextChoice *> *)textChoices nullChoice:(ORKTextChoice *)nullChoice NS_DESIGNATED_INITIALIZER;
@@ -211,6 +206,7 @@ NSArray<Class> *ORKAllowableValueClasses(void);
@end
@interface ORKImageChoice () <ORKAnswerOption>
@end
@@ -222,7 +218,11 @@ NSArray<Class> *ORKAllowableValueClasses(void);
@end
@interface ORKDateAnswerFormat ()
@interface ORKDateAnswerFormat () {
NSDate *_currentDateOverride;
}
- (NSDate *)pickerDefaultDate;
- (nullable NSDate *)pickerMinimumDate;
- (nullable NSDate *)pickerMaximumDate;
@@ -28,7 +28,12 @@
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
@@ -55,6 +60,7 @@ 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
@@ -89,6 +95,7 @@ ORK_CLASS_AVAILABLE
@property (nonatomic, copy, readonly) NSString *errorMessage;
@end
#endif
@protocol ORKAnswerFormatPlatterPresentable <NSObject>
+1 -6
View File
@@ -30,20 +30,16 @@
#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];
@@ -54,7 +50,6 @@
}
return self;
}
#endif
- (BOOL)isCustomButtonItemType
{
@@ -34,11 +34,9 @@ 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,7 +28,10 @@
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,7 +29,11 @@
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
+2 -1
View File
@@ -30,9 +30,10 @@
#import <Foundation/Foundation.h>
#import <ResearchKit/ORKInstructionStep.h>
#if TARGET_OS_IOS
#import <ResearchKit/ORKInstructionStep.h>
#import <ResearchKit/ORKTask.h>
#endif
@@ -28,7 +28,6 @@
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#if !TARGET_OS_VISION
#import <Foundation/Foundation.h>
#import <ResearchKit/ResearchKit.h>
@@ -201,5 +200,3 @@ ORK_CLASS_AVAILABLE
@end
NS_ASSUME_NONNULL_END
#endif
+3
View File
@@ -29,7 +29,10 @@
*/
#import <Foundation/Foundation.h>
#if TARGET_OS_IOS
#import <ResearchKit/ORKDefines.h>
#endif
NS_ASSUME_NONNULL_BEGIN
+3
View File
@@ -28,7 +28,10 @@
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,7 +29,11 @@
*/
#import <Foundation/Foundation.h>
#if TARGET_OS_IOS
#import <ResearchKit/ORKDefines.h>
#endif
NS_ASSUME_NONNULL_BEGIN
+5
View File
@@ -30,7 +30,12 @@
#import <Foundation/Foundation.h>
#if TARGET_OS_IOS
#import <ResearchKit/ORKDefines.h>
#endif
NS_ASSUME_NONNULL_BEGIN
+5 -1
View File
@@ -30,8 +30,12 @@
#import <Foundation/Foundation.h>
#import <ResearchKit/ORKDefines.h>
#if TARGET_OS_IOS
#import <ResearchKit/ORKStep.h>
#import <ResearchKit/ORKDefines.h>
#endif
/**
Values that determine the style
+1 -14
View File
@@ -39,10 +39,7 @@
#import "ORKStep_Private.h"
#import "ORKHelpers_Internal.h"
#if !TARGET_OS_WATCH
#import "ORKFormItemVisibilityRule.h"
#endif
@implementation ORKFormStep
@@ -185,7 +182,7 @@
}
}
#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION && TARGET_OS_IOS
#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION
- (NSSet<HKObjectType *> *)requestedHealthKitTypesForReading {
NSMutableSet<HKObjectType *> *healthTypes = [NSMutableSet set];
@@ -265,7 +262,6 @@
return self;
}
#if TARGET_OS_IOS
- (ORKFormItem *)confirmationAnswerFormItemWithIdentifier:(NSString *)identifier
text:(nullable NSString *)text
errorMessage:(NSString *)errorMessage {
@@ -285,7 +281,6 @@
optional:self.optional];
return item;
}
#endif
+ (BOOL)supportsSecureCoding {
return YES;
@@ -299,9 +294,7 @@
item->_learnMoreItem = [_learnMoreItem copy];
item->_showsProgress = _showsProgress;
item->_tagText = [_tagText copy];
#if !TARGET_OS_WATCH
item->_visibilityRule = [_visibilityRule copy];
#endif
return item;
}
@@ -318,9 +311,7 @@
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;
}
@@ -359,12 +350,8 @@
}
- (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 {
+3 -4
View File
@@ -31,7 +31,7 @@
#import <ResearchKit/ORKAnswerFormat.h>
#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION && TARGET_OS_IOS
#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION
#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 && TARGET_OS_IOS
#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION
ORK_CLASS_AVAILABLE
@interface ORKHealthKitCharacteristicTypeAnswerFormat : ORKAnswerFormat
@@ -226,7 +226,6 @@ included in the question result generated by form items or question steps
- (NSString *)localizedUnitString;
@end
#endif
NS_ASSUME_NONNULL_END
+10 -4
View File
@@ -29,13 +29,19 @@
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import <Foundation/Foundation.h>
#import <os/log.h>
#import <UIKit/UIKit.h>
#import <ResearchKit/ORKErrors.h>
#import <ResearchKit/ORKHelpers_Private.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>
NS_ASSUME_NONNULL_BEGIN
+4
View File
@@ -30,7 +30,11 @@
#import <Foundation/Foundation.h>
#if TARGET_OS_IOS
#import <ResearchKit/ORKDefines.h>
#endif
NS_ASSUME_NONNULL_BEGIN
+6
View File
@@ -28,8 +28,14 @@
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
@@ -43,20 +43,25 @@ static const uint32_t IconDarkTintColor = 0xEF6FD8;
@property (nonatomic, readonly, assign) BOOL canContinue;
@end
@implementation ORKMotionActivityPermissionType
@implementation ORKMotionActivityPermissionType {
__weak NSTimer *_checkStatusTimer;
}
+ (instancetype)new {
return [[ORKMotionActivityPermissionType alloc] init];
}
- (instancetype)init
{
- (instancetype)init {
self = [super init];
if (self) {
}
return self;
}
- (void)cleanUp {
[self _invalidateCheckStatusTimer];
}
- (CMMotionActivityManager *)activityManager {
if (!_activityManager) {
_activityManager = [[CMMotionActivityManager alloc] init];
@@ -105,9 +110,33 @@ static const uint32_t IconDarkTintColor = 0xEF6FD8;
[self.activityManager startActivityUpdatesToQueue:[NSOperationQueue mainQueue]
withHandler:^(CMMotionActivity * _Nullable activity) {}];
[self.activityManager stopActivityUpdates];
if (self.permissionsStatusUpdateCallback != nil) {
self.permissionsStatusUpdateCallback();
}
if (!_checkStatusTimer) {
__weak typeof(self) weakSelf = self;
_checkStatusTimer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:weakSelf
selector:@selector(_checkPermissionStatus)
userInfo:nil
repeats:YES];
}
}
- (void)_checkPermissionStatus {
if ([self permissionState] == ORKRequestPermissionsStateConnected && self.permissionsStatusUpdateCallback != nil) {
self.permissionsStatusUpdateCallback();
[self _invalidateCheckStatusTimer];
}
}
- (void)_invalidateCheckStatusTimer {
if (_checkStatusTimer) {
[_checkStatusTimer invalidate];
_checkStatusTimer = nil;
}
}
- (BOOL)isEqual:(id)object {
+5
View File
@@ -30,7 +30,12 @@
#import <UIKit/UIKit.h>
#if TARGET_OS_IOS
#import <ResearchKit/ORKTask.h>
#endif
NS_ASSUME_NONNULL_BEGIN
+16 -10
View File
@@ -30,19 +30,16 @@
*/
#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
@@ -266,6 +263,7 @@
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) {
@@ -279,6 +277,14 @@
}
totalQuestions += 1;
}
#else
if ([step isKindOfClass:[ORKQuestionStep class]]) {
if (step.identifier == currentStep.identifier) {
currentStepStartingProgressNumber = (totalQuestions + 1);
}
totalQuestions += 1;
}
#endif
}
totalProgress.currentStepStartingProgressPosition = currentStepStartingProgressNumber;
@@ -287,6 +293,7 @@
return totalProgress;
}
#if TARGET_OS_IOS
- (NSMutableArray *)calculateSectionsForFormItems:(NSArray *)formItems {
NSMutableArray<NSMutableArray *> *_sections = [NSMutableArray new];
NSMutableArray *section = nil;
@@ -367,7 +374,6 @@
return NO;
}
#if TARGET_OS_IOS
- (BOOL)providesBackgroundAudioPrompts {
BOOL providesAudioPrompts = NO;
for (ORKStep *step in self.steps) {
+4
View File
@@ -28,8 +28,12 @@
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
+2 -1
View File
@@ -32,7 +32,6 @@
#import <UIKit/UIKit.h>
#import <ResearchKit/ORKDefines.h>
#import <UserNotifications/UserNotifications.h>
NS_ASSUME_NONNULL_BEGIN
@@ -50,6 +49,7 @@ typedef NS_ENUM(NSInteger, ORKRequestPermissionsState) {
ORKRequestPermissionsStateError,
};
typedef NS_OPTIONS(NSUInteger, UNAuthorizationOptions);
typedef NSString * SRSensor NS_TYPED_ENUM API_AVAILABLE(ios(14.0));
/**
@@ -69,6 +69,7 @@ ORK_CLASS_AVAILABLE
@property (nonatomic, assign, readonly) BOOL canContinue;
- (void)requestPermission;
- (void)cleanUp;
+ (ORKHealthKitPermissionType *)healthKitPermissionTypeWithSampleTypesToWrite:(nullable NSSet<HKSampleType *> *)sampleTypesToWrite
objectTypesToRead:(nullable NSSet<HKObjectType *> *)objectTypesToRead;
+4
View File
@@ -66,6 +66,10 @@
ORKThrowMethodUnavailableException();
}
- (void)cleanUp {
// left empty for optional subclass override
}
+ (ORKHealthKitPermissionType *)healthKitPermissionTypeWithSampleTypesToWrite:(NSSet<HKSampleType *> *)sampleTypesToWrite objectTypesToRead:(NSSet<HKObjectType *> *)objectTypesToRead {
return [[ORKHealthKitPermissionType alloc] initWithSampleTypesToWrite:sampleTypesToWrite
objectTypesToRead:objectTypesToRead];
+10 -7
View File
@@ -28,13 +28,17 @@
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import <Contacts/Contacts.h>
#if TARGET_OS_IOS
#import <ResearchKit/ORKResult.h>
#endif
#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && TARGET_OS_IOS
#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION
#import <CoreLocation/CLLocation.h>
#endif
#import <Contacts/Contacts.h>
NS_ASSUME_NONNULL_BEGIN
@class CLCircularRegion;
@@ -149,7 +153,7 @@ ORK_CLASS_AVAILABLE
@end
#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && TARGET_OS_IOS
#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION
/**
The `ORKLocation` class represents the location addess obtained from a location question.
*/
@@ -180,7 +184,7 @@ ORK_CLASS_AVAILABLE
@property (nonatomic, copy, readonly, nullable) CNPostalAddress *postalAddress;
@end
#endif
#endif
/**
A result object from a location answer format.
@@ -192,8 +196,7 @@ 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 && TARGET_OS_IOS
#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION
ORK_CLASS_AVAILABLE
@interface ORKLocationQuestionResult : ORKQuestionResult
@@ -203,7 +206,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.
+10 -12
View File
@@ -28,18 +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/ORKQuestionResult.h>
#import <ResearchKit/ORKQuestionStep.h>
#import <ResearchKit/ORKQuestionStep_Private.h>
#import <ResearchKit/ORKResult_Private.h>
#import <ResearchKit/ORKStep_Private.h>
#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && TARGET_OS_IOS
#import <CoreLocation/CoreLocation.h>
#import <MapKit/MapKit.h>
#import "ORKQuestionResult_Private.h"
#import "ORKResult_Private.h"
#if TARGET_OS_IOS
#import "ORKQuestionStep.h"
#import "ORKHelpers_Internal.h"
#import "ORKAnswerFormat_Internal.h"
#endif
@implementation ORKQuestionResult {
@@ -329,7 +326,7 @@
@end
#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && TARGET_OS_IOS
#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION
#pragma mark - ORKLocationQuestionResult
@implementation ORKLocation
@@ -471,6 +468,7 @@ static NSString *const RegionIdentifierKey = @"region.identifier";
}
@end
#endif
#pragma mark - ORKSESQuestionResult
@@ -28,10 +28,15 @@
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 ()
@@ -44,10 +49,9 @@ NS_ASSUME_NONNULL_BEGIN
@end
#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && TARGET_OS_IOS
#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION
@interface ORKLocation ()
- (instancetype)initWithCoordinate:(CLLocationCoordinate2D)coordinate
region:(nullable CLCircularRegion *)region
userInput:(nullable NSString *)userInput
@@ -56,6 +60,5 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)initWithPlacemark:(CLPlacemark *)placemark userInput:(NSString *)userInput;
@end
#endif
#endif
NS_ASSUME_NONNULL_END
+4
View File
@@ -29,7 +29,11 @@
*/
#import <Foundation/Foundation.h>
#if TARGET_OS_IOS
#import <ResearchKit/ORKTypes.h>
#endif
NS_ASSUME_NONNULL_BEGIN
+3
View File
@@ -28,7 +28,10 @@
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,8 +28,12 @@
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,7 +30,11 @@
*/
#import <UIKit/UIKit.h>
#if TARGET_OS_IOS
#import <ResearchKit/ORKSkin.h>
#endif
#if TARGET_OS_IOS
+8 -7
View File
@@ -28,9 +28,13 @@
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;
@@ -176,14 +180,14 @@ ORK_CLASS_AVAILABLE API_AVAILABLE(ios(11.0), watchos(6.0))
/**
A property that gates automatic tint color image changes based on appearance changes.
The default value for this property is NO.
The default value for this property is NO.
*/
@property (nonatomic) BOOL shouldAutomaticallyAdjustImageTintColor;
/**
A property that determines whether to show progress for this step when presented.
The default is YES.
The default is YES.
*/
@property (nonatomic, assign) BOOL showsProgress;
@@ -248,7 +252,7 @@ ORK_CLASS_AVAILABLE API_AVAILABLE(ios(11.0), watchos(6.0))
#pragma mark - iOS
#if TARGET_OS_IOS || TARGET_OS_VISION
#if TARGET_OS_IOS
@class ORKBodyItem;
@@ -309,7 +313,4 @@ API_AVAILABLE(ios(11))
#endif
#pragma mark - watchOS / VisionOS
NS_ASSUME_NONNULL_END
+3
View File
@@ -28,7 +28,10 @@
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,7 +35,11 @@
#import <HealthKit/HealthKit.h>
#endif
#if TARGET_OS_IOS
#import <ResearchKit/ORKTypes.h>
#endif
NS_ASSUME_NONNULL_BEGIN
+4
View File
@@ -30,7 +30,11 @@
#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,14 +87,12 @@ 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,10 +63,8 @@ NS_ASSUME_NONNULL_BEGIN
@optional
- (UIView<ORKCustomSignatureAccessoryViewProtocol> * _Nullable)customHeaderViewForSignatureContent;
#if !TARGET_OS_VISION
@optional
- (UIScrollViewKeyboardDismissMode)keyboardDismissModeForCustomView;
#endif
@end
@@ -23,7 +23,7 @@ PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*] =
SWIFT_VERSION = 5.0
CLANG_STATIC_ANALYZER_MODE = deep
ORK_FRAMEWORK_VERSION_NUMBER = 3.1
ORK_FRAMEWORK_VERSION_NUMBER = 3.1.1
ORK_FRAMEWORK_BUILD_NUMBER = $(ORK_FRAMEWORK_BUILD_NUMBER_CI_$(CI)) // ORK_FRAMEWORK_BUILD_NUMBER_CI_TRUE or ORK_FRAMEWORK_BUILD_NUMBER_CI_
ORK_FRAMEWORK_BUILD_NUMBER_CI_TRUE = $(CI_BUILD_NUMBER)
+2 -7
View File
@@ -73,13 +73,8 @@ static NSArray <ORKFormItem*> *ORKRegistrationFormItems(ORKRegistrationStepOptio
answerFormat.autocapitalizationType = UITextAutocapitalizationTypeNone;
answerFormat.autocorrectionType = UITextAutocorrectionTypeNo;
answerFormat.spellCheckingType = UITextSpellCheckingTypeNo;
if (@available(iOS 12.0, *)) {
answerFormat.textContentType = UITextContentTypeNewPassword;
} else {
answerFormat.textContentType = UITextContentTypePassword;
}
answerFormat.textContentType = UITextContentTypeOneTimeCode;
ORKFormItem *item = [[ORKFormItem alloc] initWithIdentifier:ORKRegistrationFormItemIdentifierPassword
text:ORKLocalizedString(@"PASSWORD_FORM_ITEM_TITLE", nil)
answerFormat:answerFormat
@@ -18,7 +18,7 @@ Present a specific question and collect an answer.
- ``ORKAnswerFormat``
- ``ORKNumericPrecision``
### Height, and Weight
### Height and Weight
- ``ORKHeightAnswerFormat``
- ``ORKWeightAnswerFormat``
+25 -26
View File
@@ -29,42 +29,26 @@
*/
#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/ORKTypes.h>
#import <ResearchKit/ORKStep.h>
#import <ResearchKit/ORKActiveStep.h>
#import <ResearchKit/ORKCompletionStep.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>
@@ -77,15 +61,24 @@
#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>
@@ -99,6 +92,10 @@
#import <ResearchKit/ORKConsentSignature.h>
#import <ResearchKit/ORKKeychainWrapper.h>
#import <ResearchKit/ORKBodyItem.h>
#import <ResearchKit/ORKLearnMoreItem.h>
#import <ResearchKit/ORKDataCollectionManager.h>
#import <ResearchKit/ORKCollector.h>
@@ -110,7 +107,11 @@
#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>
@@ -121,5 +122,3 @@
#import <ResearchKit/ORKHealthCondition.h>
#import <ResearchKit/ORKRelatedPerson.h>
#import <ResearchKit/ORKRelativeGroup.h>
#endif
+10 -15
View File
@@ -28,35 +28,30 @@
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,7 +30,11 @@
#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
+5 -1
View File
@@ -28,8 +28,12 @@
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import <ResearchKit/ORKAnswerFormat_Private.h>
#if TARGET_OS_IOS
#import <ResearchKit/ORKQuestionStep.h>
#import <ResearchKit/ORKAnswerFormat_Private.h>
#endif
NS_ASSUME_NONNULL_BEGIN
@@ -122,18 +122,20 @@ NSString * const ORKdBHLToneAudiometryStepViewAccessibilityIdentifier = @"ORKdBH
- (void)configureStep {
ORKdBHLToneAudiometryStep *dBHLTAStep = [self dBHLToneAudiometryStep];
self.dBHLToneAudiometryContentView = [[ORKdBHLToneAudiometryContentView alloc] init];
self.activeStepView.activeCustomView = self.dBHLToneAudiometryContentView;
self.activeStepView.customContentFillsAvailableSpace = YES;
[self.activeStepView.navigationFooterView setHidden:YES];
if (!self.dBHLToneAudiometryContentView) {
self.dBHLToneAudiometryContentView = [[ORKdBHLToneAudiometryContentView alloc] init];
self.activeStepView.activeCustomView = self.dBHLToneAudiometryContentView;
self.activeStepView.customContentFillsAvailableSpace = YES;
[self.activeStepView.navigationFooterView setHidden:YES];
[self.dBHLToneAudiometryContentView.tapButton addTarget:self action:@selector(tapButtonPressed) forControlEvents:UIControlEventTouchDown];
_audioChannel = dBHLTAStep.earPreference;
_audioGenerator = [self createAudioGeneratorFromHeadphoneType:dBHLTAStep.headphoneType];
_audioGenerator.delegate = self;
[self.dBHLToneAudiometryContentView.tapButton addTarget:self action:@selector(tapButtonPressed) forControlEvents:UIControlEventTouchDown];
_audioChannel = dBHLTAStep.earPreference;
_audioGenerator = [self createAudioGeneratorFromHeadphoneType:dBHLTAStep.headphoneType];
_audioGenerator.delegate = self;
_hapticFeedback = [[UIImpactFeedbackGenerator alloc] initWithStyle: UIImpactFeedbackStyleHeavy];
_hapticFeedback = [[UIImpactFeedbackGenerator alloc] initWithStyle: UIImpactFeedbackStyleHeavy];
}
}
- (void)addObservers {
@@ -1,851 +0,0 @@
// !$*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 */;
}
@@ -1,67 +0,0 @@
<?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>
@@ -1,55 +0,0 @@
<?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>
@@ -1,80 +0,0 @@
/*
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
}
}
}
@@ -1,170 +0,0 @@
/*
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
@@ -1,98 +0,0 @@
/*
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
}
}
@@ -1,70 +0,0 @@
/*
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
}
}
@@ -1,51 +0,0 @@
/*
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
}
}
@@ -1,115 +0,0 @@
/*
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.")
)
}
}
}
@@ -1,78 +0,0 @@
/*
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
}
}
@@ -1,43 +0,0 @@
/*
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
@@ -1,34 +0,0 @@
/*
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";
@@ -1,81 +0,0 @@
/*
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 })
}
}
@@ -1,149 +0,0 @@
/*
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()
}
@@ -1,131 +0,0 @@
/*
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()
}
}
@@ -1,36 +0,0 @@
/*
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>)
}
@@ -1,68 +0,0 @@
/*
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()
}
}
@@ -1,90 +0,0 @@
/*
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
}
}
@@ -1,341 +0,0 @@
/*
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)
}
}
}
@@ -1,387 +0,0 @@
/*
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
)
}
}
@@ -1,567 +0,0 @@
/*
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
)
}
}
@@ -1,352 +0,0 @@
/*
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)
}
}
}
@@ -1,77 +0,0 @@
/*
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
}
}
@@ -1,205 +0,0 @@
/*
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)
}
}
}
}
@@ -1,797 +0,0 @@
/*
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
@@ -1,51 +0,0 @@
/*
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
}
}
@@ -1,345 +0,0 @@
/*
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
)
}
}
@@ -1,647 +0,0 @@
/*
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
}
}
@@ -1,75 +0,0 @@
# ``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``
@@ -1,217 +0,0 @@
/*
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
}
@@ -1,55 +0,0 @@
/*
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
}
}
}
@@ -1,64 +0,0 @@
/*
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)
}
}
@@ -1,75 +0,0 @@
/*
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
)
}
}
}
@@ -1,197 +0,0 @@
/*
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
)
}
}
}
@@ -1,322 +0,0 @@
/*
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)
}
}
@@ -1,230 +0,0 @@
/*
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
)
}
}
@@ -1,145 +0,0 @@
/*
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()
}
}
@@ -1,105 +0,0 @@
/*
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")
)
}
@@ -1,52 +0,0 @@
/*
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
}
}
@@ -1,39 +0,0 @@
/*
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,13 +1448,11 @@
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
+3 -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",
@@ -485,6 +485,7 @@ ORK_MAKE_TEST_INIT(ORKAccuracyStroopStep, (^{ return [[ORKAccuracyStroopStep all
[ORKTouchAbilityPinchStep class],
[ORKTouchAbilitySwipeStep class],
[ORKTouchAbilityTapResult class],
[ORKTouchAbilityTouchTracker class],
[ORKTouchAbilityRotationStep class],
[ORKTouchAbilityLongPressStep class],
[ORKTouchAbilityScrollStep class],
@@ -619,6 +620,7 @@ ORK_MAKE_TEST_INIT(ORKAccuracyStroopStep, (^{ return [[ORKAccuracyStroopStep all
@"ORKTimeIntervalAnswerFormat.defaultInterval",
@"ORKTimeIntervalAnswerFormat.maximumInterval",
@"ORKTimeIntervalAnswerFormat.step",
@"ORKTouchAbilityTouchTracker.delegate",
@"ORKVerificationStep.verificationViewControllerClass",
@"ORKVideoCaptureStep.templateImage",
@"ORKWeightAnswerFormat.useMetricSystem",

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