diff --git a/Package.swift b/Package.swift index ae57bb6f3c..95f1adc342 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.10 +// swift-tools-version: 6.0 import class Foundation.ProcessInfo import PackageDescription @@ -13,6 +13,7 @@ let swiftConcurrency: SwiftSetting = .enableUpcomingFeature("StrictConcurrency") let package = Package( name: "ResearchKit", + defaultLocalization: "en", platforms: [ .iOS(.v17), .visionOS(.v1) @@ -44,9 +45,6 @@ let package = Package( .target(name: "ResearchKitUI"), .target(name: "ResearchKitActiveTask", condition: .when(platforms: [.iOS])) ], - swiftSettings: [ - swiftConcurrency - ], plugins: [] + swiftLintPlugin() ) ] diff --git a/README.md b/README.md index 230935f791..e07d0ee209 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +![ResearchKit](https://github.com/user-attachments/assets/0384c1a6-ec67-45d3-be68-136a2e4cacff) + ResearchKit Framework =========== @@ -86,6 +88,78 @@ The height question is presented in the figure below. |---|---| | ![height-question](https://github.com/ResearchKit/ResearchKit/assets/29615893/4f425329-83b7-45c3-84f9-58cdbcaf2529) | ![height-question-2](https://github.com/ResearchKit/ResearchKit/assets/29615893/2cc0dc2c-5c2a-4b50-a4be-834363fb64b5) | +### ResearchKit SwiftUI + +We are excited to announce the release of a new beta API for surveys in ResearchKit. This API is designed to enhance the flexibility, customization, and cross-platform compatibility of surveys in your ResearchKit apps. Below are the key features and usage details. + +New Form APIs offer an easily configurable and flexible UI, with the same look and feel of `ORKFormStep`: +* `ResearchForm` + * Manages the navigation between steps in a survey. +* `ResearchFormStep` + * Represents a step in a survey and lays out the header and questions on one page. Question numbers (e.g. 1 of 3) are automatically added at the top of each question to denote progress in a step. +* `ResearchFormCompletion` + * Represents the context for a survey's completion +* `ResearchFormResult` + * Represents responses for the different kinds of questions. +* `StepHeader` + * A step header containing an image, title, and subtitle. +* `QuestionHeader` + * A question header containing a title and detail. +* `InstructionBodyItem` + * Displays an image and text side by side. +* `questionRequired` (`ViewModifier`) + * Designates a question as required or optional. + +#### Survey Question Types: +* `MultipleChoiceQuestion` +* `HeightQuestion` +* `WeightQuestion` +* `SliderQuestion` +* `TextQuestion` +* `DateTimeQuestion` +* `NumericQuestion` +* `ImageChoiceQuestion` + +The example below shows how to create a `ResearchForm` to present a text question for the participant to answer, and then save their results. + +```swift +import ResearchKitSwiftUI + +ResearchForm( + id: "SurveyTask", + steps: { + ResearchFormStep( + title: "Demographics", + subtitle: "Tell us about yourself", + content: { + TextQuestion( + id: "textQuestion", + title: "What is your name?", + prompt: "Enter your name here", + lineLimit: .singleLine, + characterLimit: 0 + ) + .questionRequired(true) + } + ) + }, + onResearchFormCompletion: { completion in + switch completion { + case .completed(let results): + save(results) + case .discarded: + cancel() + default: + cancel() + } + } +) +``` + +#### Install as an embedded framework + +Download the project source code and drag in the *ResearchKitSwiftUI* folder. In the dialog that pops up, choose to copy files to destination and create folders. Then hit the finish button. Finally, embed the *ResearchKitSwiftUI* framework in your app by adding it to the "Frameworks, Libraries, and Embedded Content" section for your target. + # Consent The *ResearchKit framework* provides classes that you can customize to explain the diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 3243756ce4..94ac15af2e 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -1,5 +1,24 @@ # ResearchKit Release Notes +## ResearchKit 3.1 Release Notes +In addition to general stability and performance improvements, ResearchKit 3.1 includes the following updates: + +- **ORKFamilyHistoryStep** +The `ORKFamilyHistoryStep` can be configured to present a Family Health History survey. + +- **ORKColorChoiceAnswerFormat** +The `ORKColorChoiceAnswerFormat` presents the user with a list of color choices. + +- **ORKAgeAnswerFormat** +The `ORKAgeAnswerFormat` presents a age picker that presents birth year or current age options depending on how you configure it. + +- **CLLocation Flag** +A compiler flag that prevents your app from being flagged during app store submission if your app doesn't require location services. + +- **HealthKit Flag** +A compiler flag that prevents your app from being flagged during app store submission if your app doesn't use HealthKit. + + ## ResearchKit 3.0.1 Release Notes In addition to general stability and performance improvements, ResearchKit 3.0.1 includes the following updates: diff --git a/RKWorkspace.xcworkspace/contents.xcworkspacedata b/RKWorkspace.xcworkspace/contents.xcworkspacedata index 05c8ab4b21..7e57b4298d 100644 --- a/RKWorkspace.xcworkspace/contents.xcworkspacedata +++ b/RKWorkspace.xcworkspace/contents.xcworkspacedata @@ -7,4 +7,7 @@ + + diff --git a/ResearchKit.podspec b/ResearchKit.podspec new file mode 100644 index 0000000000..4ac578f0d8 --- /dev/null +++ b/ResearchKit.podspec @@ -0,0 +1,34 @@ +Pod::Spec.new do |s| + s.name = 'ResearchKit' + s.version = '3.0.1' + s.summary = 'ResearchKit is an open source software framework that makes it easy to create apps for medical research or for other research projects.' + s.homepage = 'https://www.github.com/ResearchKit/ResearchKit' + s.documentation_url = 'http://researchkit.github.io/docs/' + s.license = { :type => 'BSD', :file => 'LICENSE' } + s.author = { 'researchkit.org' => 'http://researchkit.org' } + s.source = { :git => 'https://github.com/ResearchKit/ResearchKit.git', :tag => s.version.to_s } + + s.default_subspec = "ResearchKitAllTargets" + + s.subspec 'ResearchKitCore' do |ss| + ss.vendored_frameworks = 'xcframework/ResearchKit.xcframework' + end + + s.subspec 'ResearchKitUI' do |ss| + ss.vendored_frameworks = 'xcframework/ResearchKitUI.xcframework' + ss.dependency 'ResearchKit/ResearchKitCore' + end + + s.subspec 'ResearchKitActiveTask' do |ss| + ss.vendored_frameworks = 'xcframework/ResearchKitActiveTask.xcframework' + ss.dependency 'ResearchKit/ResearchKitUI' + ss.dependency 'ResearchKit/ResearchKitCore' + end + + s.subspec 'ResearchKitAllTargets' do |ss| + ss.dependency 'ResearchKit/ResearchKitCore' + ss.dependency 'ResearchKit/ResearchKitUI' + ss.dependency 'ResearchKit/ResearchKitActiveTask' + end +end + diff --git a/ResearchKit.xcodeproj/project.pbxproj b/ResearchKit.xcodeproj/project.pbxproj index c52483ae19..0435609e43 100644 --- a/ResearchKit.xcodeproj/project.pbxproj +++ b/ResearchKit.xcodeproj/project.pbxproj @@ -3,23 +3,40 @@ archiveVersion = 1; classes = { }; - objectVersion = 55; + objectVersion = 60; objects = { +/* Begin PBXAggregateTarget section */ + 0BC672D82BD9C52D005798AC /* ResearchKitAllTargets */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 0BC672D92BD9C52D005798AC /* Build configuration list for PBXAggregateTarget "ResearchKitAllTargets" */; + buildPhases = ( + 0BC672E82BD9C69C005798AC /* ShellScript */, + ); + dependencies = ( + 0BC672E02BD9C541005798AC /* PBXTargetDependency */, + 0BC672E22BD9C541005798AC /* PBXTargetDependency */, + 0BC672E62BD9C541005798AC /* PBXTargetDependency */, + ); + name = ResearchKitAllTargets; + productName = ResearchKitAllTargets; + }; +/* End PBXAggregateTarget section */ + /* Begin PBXBuildFile section */ - 00B1F7852241503900D022FE /* Speech.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00B1F7842241503900D022FE /* Speech.framework */; }; - 00C2668E23022CD400337E0B /* ORKCustomStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 00C2668C23022CD400337E0B /* ORKCustomStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 00C2668F23022CD400337E0B /* ORKCustomStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 00C2668D23022CD400337E0B /* ORKCustomStep.m */; }; + 00B1F7852241503900D022FE /* Speech.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00B1F7842241503900D022FE /* Speech.framework */; platformFilter = ios; }; + 00C2668E23022CD400337E0B /* ORKCustomStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 00C2668C23022CD400337E0B /* ORKCustomStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + 00C2668F23022CD400337E0B /* ORKCustomStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 00C2668D23022CD400337E0B /* ORKCustomStep.m */; platformFilter = ios; }; 03057F492518ECDC00C4EC5B /* ORKAudioStepViewControllerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 03057F482518ECDC00C4EC5B /* ORKAudioStepViewControllerTests.m */; }; - 031A0FC124CF4ECD000E4455 /* ORKSensorPermissionType.h in Headers */ = {isa = PBXBuildFile; fileRef = 031A0FBF24CF4ECD000E4455 /* ORKSensorPermissionType.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 031A0FC224CF4ECD000E4455 /* ORKSensorPermissionType.m in Sources */ = {isa = PBXBuildFile; fileRef = 031A0FC024CF4ECD000E4455 /* ORKSensorPermissionType.m */; }; + 031A0FC124CF4ECD000E4455 /* ORKSensorPermissionType.h in Headers */ = {isa = PBXBuildFile; fileRef = 031A0FBF24CF4ECD000E4455 /* ORKSensorPermissionType.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + 031A0FC224CF4ECD000E4455 /* ORKSensorPermissionType.m in Sources */ = {isa = PBXBuildFile; fileRef = 031A0FC024CF4ECD000E4455 /* ORKSensorPermissionType.m */; platformFilter = ios; }; 0324C1D825439E1800BBE77B /* ORKVideoInstructionStepViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0324C1D725439E1800BBE77B /* ORKVideoInstructionStepViewControllerTests.swift */; }; - 036B1E8D25351BAD008483DF /* ORKMotionActivityPermissionType.h in Headers */ = {isa = PBXBuildFile; fileRef = 036B1E8B25351BAD008483DF /* ORKMotionActivityPermissionType.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 036B1E8E25351BAD008483DF /* ORKMotionActivityPermissionType.m in Sources */ = {isa = PBXBuildFile; fileRef = 036B1E8C25351BAD008483DF /* ORKMotionActivityPermissionType.m */; }; - 03BD9EA3253E62A0008ADBE1 /* ORKBundleAsset.h in Headers */ = {isa = PBXBuildFile; fileRef = 03BD9EA1253E62A0008ADBE1 /* ORKBundleAsset.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 03BD9EA4253E62A0008ADBE1 /* ORKBundleAsset.m in Sources */ = {isa = PBXBuildFile; fileRef = 03BD9EA2253E62A0008ADBE1 /* ORKBundleAsset.m */; }; - 03EDD58024CA6B1D006245E9 /* ORKNotificationPermissionType.h in Headers */ = {isa = PBXBuildFile; fileRef = 03EDD57E24CA6B1D006245E9 /* ORKNotificationPermissionType.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 03EDD58124CA6B1D006245E9 /* ORKNotificationPermissionType.m in Sources */ = {isa = PBXBuildFile; fileRef = 03EDD57F24CA6B1D006245E9 /* ORKNotificationPermissionType.m */; }; + 036B1E8D25351BAD008483DF /* ORKMotionActivityPermissionType.h in Headers */ = {isa = PBXBuildFile; fileRef = 036B1E8B25351BAD008483DF /* ORKMotionActivityPermissionType.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + 036B1E8E25351BAD008483DF /* ORKMotionActivityPermissionType.m in Sources */ = {isa = PBXBuildFile; fileRef = 036B1E8C25351BAD008483DF /* ORKMotionActivityPermissionType.m */; platformFilter = ios; }; + 03BD9EA3253E62A0008ADBE1 /* ORKBundleAsset.h in Headers */ = {isa = PBXBuildFile; fileRef = 03BD9EA1253E62A0008ADBE1 /* ORKBundleAsset.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + 03BD9EA4253E62A0008ADBE1 /* ORKBundleAsset.m in Sources */ = {isa = PBXBuildFile; fileRef = 03BD9EA2253E62A0008ADBE1 /* ORKBundleAsset.m */; platformFilter = ios; }; + 03EDD58024CA6B1D006245E9 /* ORKNotificationPermissionType.h in Headers */ = {isa = PBXBuildFile; fileRef = 03EDD57E24CA6B1D006245E9 /* ORKNotificationPermissionType.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + 03EDD58124CA6B1D006245E9 /* ORKNotificationPermissionType.m in Sources */ = {isa = PBXBuildFile; fileRef = 03EDD57F24CA6B1D006245E9 /* ORKNotificationPermissionType.m */; platformFilter = ios; }; 0B0852742BD872C400149963 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 0B0852732BD872C400149963 /* PrivacyInfo.xcprivacy */; }; 0B0852762BD872D800149963 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 0B0852752BD872D800149963 /* PrivacyInfo.xcprivacy */; }; 0B0852782BD872EA00149963 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 0B0852772BD872EA00149963 /* PrivacyInfo.xcprivacy */; }; @@ -33,11 +50,11 @@ 0BD9B6112B75992800A64EF9 /* Sentence5.wav in Resources */ = {isa = PBXBuildFile; fileRef = 7118AC6320BF6A3A00D7A6BB /* Sentence5.wav */; }; 0BD9B6122B75992C00A64EF9 /* Sentence6.wav in Resources */ = {isa = PBXBuildFile; fileRef = 7118AC6220BF6A3A00D7A6BB /* Sentence6.wav */; }; 0BD9B6132B75992F00A64EF9 /* Sentence7.wav in Resources */ = {isa = PBXBuildFile; fileRef = 7118AC6020BF6A3900D7A6BB /* Sentence7.wav */; }; - 0BE9D5272947EA4900DA0625 /* ORKConsentDocument+ORKInstructionStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 0BE9D5252947EA4900DA0625 /* ORKConsentDocument+ORKInstructionStep.m */; }; + 0BE9D5272947EA4900DA0625 /* ORKConsentDocument+ORKInstructionStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 0BE9D5252947EA4900DA0625 /* ORKConsentDocument+ORKInstructionStep.m */; platformFilter = ios; }; 0BFD27562B8D1D3B00B540E8 /* ORKJSONSerializationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 51AF19562B583BBA00D3B399 /* ORKJSONSerializationTests.m */; }; - 10FF9ADB1B7BA78400ECB5B4 /* ORKOrderedTask_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 10FF9AD91B7BA78400ECB5B4 /* ORKOrderedTask_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; - 12F339BF26A1F09A000665E4 /* ORKLocationPermissionType.m in Sources */ = {isa = PBXBuildFile; fileRef = 12F339BD26A1F09A000665E4 /* ORKLocationPermissionType.m */; }; - 12F339C026A1F09A000665E4 /* ORKLocationPermissionType.h in Headers */ = {isa = PBXBuildFile; fileRef = 12F339BE26A1F09A000665E4 /* ORKLocationPermissionType.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 10FF9ADB1B7BA78400ECB5B4 /* ORKOrderedTask_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 10FF9AD91B7BA78400ECB5B4 /* ORKOrderedTask_Private.h */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; }; + 12F339BF26A1F09A000665E4 /* ORKLocationPermissionType.m in Sources */ = {isa = PBXBuildFile; fileRef = 12F339BD26A1F09A000665E4 /* ORKLocationPermissionType.m */; platformFilter = ios; }; + 12F339C026A1F09A000665E4 /* ORKLocationPermissionType.h in Headers */ = {isa = PBXBuildFile; fileRef = 12F339BE26A1F09A000665E4 /* ORKLocationPermissionType.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; 1483DBB5220125BE004C26B6 /* ORKActiveStepTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1483DBB4220125BE004C26B6 /* ORKActiveStepTests.swift */; }; 148E58BD227B36DB00EEF915 /* ORKCompletionStepViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 148E58BC227B36DB00EEF915 /* ORKCompletionStepViewControllerTests.swift */; }; 148E58C2227B753F00EEF915 /* ORKContinueButtonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 148E58C1227B753F00EEF915 /* ORKContinueButtonTests.swift */; }; @@ -56,26 +73,26 @@ 14F7AC8B2269035200D52F41 /* ORKStepViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14F7AC8A2269035200D52F41 /* ORKStepViewControllerTests.swift */; }; 22ED1847285290250052406B /* ORKAudiometryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 22ED1845285290250052406B /* ORKAudiometryTests.m */; }; 22ED1848285290250052406B /* ORKAudiometryTestData.plist in Resources */ = {isa = PBXBuildFile; fileRef = 22ED1846285290250052406B /* ORKAudiometryTestData.plist */; }; - 2429D5721BBB5397003A512F /* ORKRegistrationStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 2429D5701BBB5397003A512F /* ORKRegistrationStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 2429D5731BBB5397003A512F /* ORKRegistrationStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 2429D5711BBB5397003A512F /* ORKRegistrationStep.m */; }; - 242C9E051BBDFDAC0088B7F4 /* ORKVerificationStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 242C9E031BBDFDAC0088B7F4 /* ORKVerificationStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 242C9E061BBDFDAC0088B7F4 /* ORKVerificationStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 242C9E041BBDFDAC0088B7F4 /* ORKVerificationStep.m */; }; - 2433C9E31B9A506F0052D375 /* ORKKeychainWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 2433C9E11B9A506F0052D375 /* ORKKeychainWrapper.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2429D5721BBB5397003A512F /* ORKRegistrationStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 2429D5701BBB5397003A512F /* ORKRegistrationStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + 2429D5731BBB5397003A512F /* ORKRegistrationStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 2429D5711BBB5397003A512F /* ORKRegistrationStep.m */; platformFilter = ios; }; + 242C9E051BBDFDAC0088B7F4 /* ORKVerificationStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 242C9E031BBDFDAC0088B7F4 /* ORKVerificationStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + 242C9E061BBDFDAC0088B7F4 /* ORKVerificationStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 242C9E041BBDFDAC0088B7F4 /* ORKVerificationStep.m */; platformFilter = ios; }; + 2433C9E31B9A506F0052D375 /* ORKKeychainWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 2433C9E11B9A506F0052D375 /* ORKKeychainWrapper.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; 2433C9E41B9A506F0052D375 /* ORKKeychainWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 2433C9E21B9A506F0052D375 /* ORKKeychainWrapper.m */; }; 244EFAD21BCEFD83001850D9 /* ORKAnswerFormat_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 244EFAD11BCEFD83001850D9 /* ORKAnswerFormat_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; 248604061B4C98760010C8A0 /* ORKAnswerFormatTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 248604051B4C98760010C8A0 /* ORKAnswerFormatTests.m */; }; - 2489F7B11D65214D008DEF20 /* ORKVideoCaptureStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 2489F7A91D65214D008DEF20 /* ORKVideoCaptureStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 2489F7B21D65214D008DEF20 /* ORKVideoCaptureStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 2489F7AA1D65214D008DEF20 /* ORKVideoCaptureStep.m */; }; - 24A4DA141B8D1115009C797A /* ORKPasscodeStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 24A4DA121B8D1115009C797A /* ORKPasscodeStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 24A4DA151B8D1115009C797A /* ORKPasscodeStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 24A4DA131B8D1115009C797A /* ORKPasscodeStep.m */; }; - 24BC5CEE1BC345D900846B43 /* ORKLoginStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 24BC5CEC1BC345D900846B43 /* ORKLoginStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 24BC5CEF1BC345D900846B43 /* ORKLoginStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 24BC5CED1BC345D900846B43 /* ORKLoginStep.m */; }; - 24C296751BD052F800B42EF1 /* ORKVerificationStep_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 24C296741BD052F800B42EF1 /* ORKVerificationStep_Internal.h */; }; - 24C296771BD055B800B42EF1 /* ORKLoginStep_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 24C296761BD055B800B42EF1 /* ORKLoginStep_Internal.h */; }; + 2489F7B11D65214D008DEF20 /* ORKVideoCaptureStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 2489F7A91D65214D008DEF20 /* ORKVideoCaptureStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + 2489F7B21D65214D008DEF20 /* ORKVideoCaptureStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 2489F7AA1D65214D008DEF20 /* ORKVideoCaptureStep.m */; platformFilter = ios; }; + 24A4DA141B8D1115009C797A /* ORKPasscodeStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 24A4DA121B8D1115009C797A /* ORKPasscodeStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + 24A4DA151B8D1115009C797A /* ORKPasscodeStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 24A4DA131B8D1115009C797A /* ORKPasscodeStep.m */; platformFilter = ios; }; + 24BC5CEE1BC345D900846B43 /* ORKLoginStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 24BC5CEC1BC345D900846B43 /* ORKLoginStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + 24BC5CEF1BC345D900846B43 /* ORKLoginStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 24BC5CED1BC345D900846B43 /* ORKLoginStep.m */; platformFilter = ios; }; + 24C296751BD052F800B42EF1 /* ORKVerificationStep_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 24C296741BD052F800B42EF1 /* ORKVerificationStep_Internal.h */; platformFilter = ios; }; + 24C296771BD055B800B42EF1 /* ORKLoginStep_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 24C296761BD055B800B42EF1 /* ORKLoginStep_Internal.h */; platformFilter = ios; }; 2EBFE11D1AE1B32D00CB8254 /* ORKUIViewAccessibilityTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2EBFE11C1AE1B32D00CB8254 /* ORKUIViewAccessibilityTests.m */; }; 2EBFE1201AE1B74100CB8254 /* ORKVoiceEngineTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2EBFE11F1AE1B74100CB8254 /* ORKVoiceEngineTests.m */; }; - 511987C3246330CA004FC2C7 /* ORKRequestPermissionsStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 511987C1246330CA004FC2C7 /* ORKRequestPermissionsStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 511987C4246330CA004FC2C7 /* ORKRequestPermissionsStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 511987C2246330CA004FC2C7 /* ORKRequestPermissionsStep.m */; }; + 511987C3246330CA004FC2C7 /* ORKRequestPermissionsStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 511987C1246330CA004FC2C7 /* ORKRequestPermissionsStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + 511987C4246330CA004FC2C7 /* ORKRequestPermissionsStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 511987C2246330CA004FC2C7 /* ORKRequestPermissionsStep.m */; platformFilter = ios; }; 511BB024298DCCC200936EC0 /* ORKSpeechRecognitionStepViewController_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 511BB022298DCCC200936EC0 /* ORKSpeechRecognitionStepViewController_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; 511E8D622995C20E00A384A5 /* ORKEnvironmentSPLMeterStepViewController_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 511E8D602995C20E00A384A5 /* ORKEnvironmentSPLMeterStepViewController_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; 512741272B1557220045A449 /* ResearchKit.docc in Sources */ = {isa = PBXBuildFile; fileRef = 512741262B1557220045A449 /* ResearchKit.docc */; }; @@ -163,33 +180,59 @@ 5192BEED2AE043D3006E43FB /* ORKTimedWalkStepViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BEE92AE043D3006E43FB /* ORKTimedWalkStepViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5192BEEE2AE043D3006E43FB /* ORKTimedWalkContentView.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BEEA2AE043D3006E43FB /* ORKTimedWalkContentView.h */; }; 5192BEEF2AE043D3006E43FB /* ORKTimedWalkStepViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5192BEEB2AE043D3006E43FB /* ORKTimedWalkStepViewController.m */; }; - 5192BF502AE09673006E43FB /* ORKPredicateFormItemVisibilityRule.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BF4D2AE09672006E43FB /* ORKPredicateFormItemVisibilityRule.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 5192BF512AE09673006E43FB /* ORKPredicateFormItemVisibilityRule.m in Sources */ = {isa = PBXBuildFile; fileRef = 5192BF4E2AE09672006E43FB /* ORKPredicateFormItemVisibilityRule.m */; }; - 5192BF522AE09673006E43FB /* ORKPredicateFormItemVisibilityRule_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BF4F2AE09672006E43FB /* ORKPredicateFormItemVisibilityRule_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 5192BF502AE09673006E43FB /* ORKPredicateFormItemVisibilityRule.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BF4D2AE09672006E43FB /* ORKPredicateFormItemVisibilityRule.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + 5192BF512AE09673006E43FB /* ORKPredicateFormItemVisibilityRule.m in Sources */ = {isa = PBXBuildFile; fileRef = 5192BF4E2AE09672006E43FB /* ORKPredicateFormItemVisibilityRule.m */; platformFilter = ios; }; + 5192BF522AE09673006E43FB /* ORKPredicateFormItemVisibilityRule_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BF4F2AE09672006E43FB /* ORKPredicateFormItemVisibilityRule_Private.h */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; }; 5192BF592AE09794006E43FB /* ORKFormItemVisibilityRule.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BF572AE09793006E43FB /* ORKFormItemVisibilityRule.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5192BF5A2AE09794006E43FB /* ORKFormItemVisibilityRule.m in Sources */ = {isa = PBXBuildFile; fileRef = 5192BF582AE09794006E43FB /* ORKFormItemVisibilityRule.m */; }; 5192BF5D2AE19036006E43FB /* frequency_dBSPL_AIRPODSPROV2.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5192BF5C2AE19036006E43FB /* frequency_dBSPL_AIRPODSPROV2.plist */; }; 5192BF7D2AE1A87D006E43FB /* retspl_AIRPODSPROV2.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5192BF7C2AE1A87D006E43FB /* retspl_AIRPODSPROV2.plist */; }; 5192BF7F2AE1A9BA006E43FB /* volume_curve_AIRPODSPROV2.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5192BF7E2AE1A9BA006E43FB /* volume_curve_AIRPODSPROV2.plist */; }; - 5192BF842AE1BA47006E43FB /* ORKFrontFacingCameraStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BF802AE1BA47006E43FB /* ORKFrontFacingCameraStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 5192BF852AE1BA47006E43FB /* ORKFrontFacingCameraStepResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 5192BF812AE1BA47006E43FB /* ORKFrontFacingCameraStepResult.m */; }; - 5192BF862AE1BA47006E43FB /* ORKFrontFacingCameraStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 5192BF822AE1BA47006E43FB /* ORKFrontFacingCameraStep.m */; }; - 5192BF872AE1BA47006E43FB /* ORKFrontFacingCameraStepResult.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BF832AE1BA47006E43FB /* ORKFrontFacingCameraStepResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 5192BF8A2AE1BBB0006E43FB /* ORKSecondaryTaskStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 5192BF882AE1BBB0006E43FB /* ORKSecondaryTaskStep.m */; }; - 5192BF8B2AE1BBB0006E43FB /* ORKSecondaryTaskStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BF892AE1BBB0006E43FB /* ORKSecondaryTaskStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 5192BF8C2AE1BF16006E43FB /* (null) in Headers */ = {isa = PBXBuildFile; settings = {ATTRIBUTES = (Private, ); }; }; + 5192BF842AE1BA47006E43FB /* ORKFrontFacingCameraStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BF802AE1BA47006E43FB /* ORKFrontFacingCameraStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + 5192BF852AE1BA47006E43FB /* ORKFrontFacingCameraStepResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 5192BF812AE1BA47006E43FB /* ORKFrontFacingCameraStepResult.m */; platformFilter = ios; }; + 5192BF862AE1BA47006E43FB /* ORKFrontFacingCameraStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 5192BF822AE1BA47006E43FB /* ORKFrontFacingCameraStep.m */; platformFilter = ios; }; + 5192BF872AE1BA47006E43FB /* ORKFrontFacingCameraStepResult.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BF832AE1BA47006E43FB /* ORKFrontFacingCameraStepResult.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + 5192BF8A2AE1BBB0006E43FB /* ORKSecondaryTaskStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 5192BF882AE1BBB0006E43FB /* ORKSecondaryTaskStep.m */; platformFilter = ios; }; + 5192BF8B2AE1BBB0006E43FB /* ORKSecondaryTaskStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BF892AE1BBB0006E43FB /* ORKSecondaryTaskStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + 5192BF8F2AE1C051006E43FB /* ORKAgePicker.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BF8D2AE1C051006E43FB /* ORKAgePicker.h */; }; + 5192BF902AE1C051006E43FB /* ORKAgePicker.m in Sources */ = {isa = PBXBuildFile; fileRef = 5192BF8E2AE1C051006E43FB /* ORKAgePicker.m */; }; + 5192BF932AE1C2F8006E43FB /* ORKChoiceViewCell+ORKColorChoice.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BF912AE1C2F7006E43FB /* ORKChoiceViewCell+ORKColorChoice.h */; }; + 5192BF942AE1C2F8006E43FB /* ORKChoiceViewCell+ORKColorChoice.m in Sources */ = {isa = PBXBuildFile; fileRef = 5192BF922AE1C2F7006E43FB /* ORKChoiceViewCell+ORKColorChoice.m */; }; 5192BF972AE1C5A1006E43FB /* ORKAnswerFormat+FormStepViewControllerAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BF952AE1C5A1006E43FB /* ORKAnswerFormat+FormStepViewControllerAdditions.h */; }; 5192BF982AE1C5A1006E43FB /* ORKAnswerFormat+FormStepViewControllerAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 5192BF962AE1C5A1006E43FB /* ORKAnswerFormat+FormStepViewControllerAdditions.m */; }; 5192BF992AE1D876006E43FB /* ORKChoiceViewCell+ORKTextChoice.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B8444632A79C25000292DEA /* ORKChoiceViewCell+ORKTextChoice.h */; }; 5192BF9A2AE1D8A4006E43FB /* ORKChoiceViewCell+ORKTextChoice.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B8444642A79C25000292DEA /* ORKChoiceViewCell+ORKTextChoice.m */; }; - 5192BFA12AEAD7B5006E43FB /* Artwork.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 861610BF1A8D8EDD00245F7A /* Artwork.xcassets */; }; - 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, ); }; }; + 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; }; + 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, ); }; }; + 519CE8422C658654003BB584 /* ORKFamilyHistoryRelatedPersonCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 519CE83C2C658653003BB584 /* ORKFamilyHistoryRelatedPersonCell.m */; }; + 519CE8432C658654003BB584 /* ORKFamilyHistoryTableFooterView.h in Headers */ = {isa = PBXBuildFile; fileRef = 519CE83D2C658653003BB584 /* ORKFamilyHistoryTableFooterView.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 519CE8442C658654003BB584 /* ORKFamilyHistoryTableFooterView.m in Sources */ = {isa = PBXBuildFile; fileRef = 519CE83E2C658654003BB584 /* ORKFamilyHistoryTableFooterView.m */; }; + 519CE8452C658654003BB584 /* ORKFamilyHistoryTableHeaderView.h in Headers */ = {isa = PBXBuildFile; fileRef = 519CE83F2C658654003BB584 /* ORKFamilyHistoryTableHeaderView.h */; }; + 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, ); }; }; 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 */; }; @@ -198,13 +241,15 @@ 51AF19582B583BBA00D3B399 /* ORKDataCollectionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 51AF19532B583BB900D3B399 /* ORKDataCollectionTests.m */; }; 51AF19592B583BBA00D3B399 /* samples.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 51AF19542B583BB900D3B399 /* samples.bundle */; }; 51AF19662B583D0F00D3B399 /* ORKESerialization.m in Sources */ = {isa = PBXBuildFile; fileRef = 51AF19552B583BB900D3B399 /* ORKESerialization.m */; }; - 51AF1B152B67F30500D3B399 /* ORKSignatureFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = 51AF1B132B67F30500D3B399 /* ORKSignatureFormatter.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 51AF1B162B67F30500D3B399 /* ORKSignatureFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 51AF1B142B67F30500D3B399 /* ORKSignatureFormatter.m */; }; - 51AF1B202B683C3400D3B399 /* ORKWebViewStepResult_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 51AF1B1F2B683C3400D3B399 /* ORKWebViewStepResult_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 51AF1B152B67F30500D3B399 /* ORKSignatureFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = 51AF1B132B67F30500D3B399 /* ORKSignatureFormatter.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + 51AF1B162B67F30500D3B399 /* ORKSignatureFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 51AF1B142B67F30500D3B399 /* ORKSignatureFormatter.m */; platformFilter = ios; }; + 51AF1B202B683C3400D3B399 /* ORKWebViewStepResult_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 51AF1B1F2B683C3400D3B399 /* ORKWebViewStepResult_Private.h */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; }; 51AF1B232B69803A00D3B399 /* Window.wav in Resources */ = {isa = PBXBuildFile; fileRef = 51AF1B212B69803A00D3B399 /* Window.wav */; }; 51AF1B242B69803A00D3B399 /* Noise.wav in Resources */ = {isa = PBXBuildFile; fileRef = 51AF1B222B69803A00D3B399 /* Noise.wav */; }; - 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 */; }; + 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; }; 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 */; }; @@ -223,12 +268,12 @@ 51CB80D62AFEBE7E00A1F410 /* ORKFormStepViewControllerConditionalFormItemsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51CB80D52AFEBE7E00A1F410 /* ORKFormStepViewControllerConditionalFormItemsTests.swift */; }; 51CB80D82AFEBEC600A1F410 /* ORKPredicateFormItemVisibilityRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51CB80D72AFEBEC600A1F410 /* ORKPredicateFormItemVisibilityRuleTests.swift */; }; 51CB80DA2AFEBF3800A1F410 /* ORKFormItemVisibilityRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51CB80D92AFEBF3800A1F410 /* ORKFormItemVisibilityRuleTests.swift */; }; - 51E03D6324919711008F8406 /* ORKPermissionType.h in Headers */ = {isa = PBXBuildFile; fileRef = 51E03D6124919711008F8406 /* ORKPermissionType.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 51E03D6424919711008F8406 /* ORKPermissionType.m in Sources */ = {isa = PBXBuildFile; fileRef = 51E03D6224919711008F8406 /* ORKPermissionType.m */; }; - 51E03D682491A601008F8406 /* ORKHealthKitPermissionType.h in Headers */ = {isa = PBXBuildFile; fileRef = 51E03D662491A601008F8406 /* ORKHealthKitPermissionType.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 51E03D692491A601008F8406 /* ORKHealthKitPermissionType.m in Sources */ = {isa = PBXBuildFile; fileRef = 51E03D672491A601008F8406 /* ORKHealthKitPermissionType.m */; }; - 51EB9A532B8408D50064A515 /* ORKInstructionStepHTMLFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = 51EB9A512B8408D50064A515 /* ORKInstructionStepHTMLFormatter.h */; }; - 51EB9A542B8408D50064A515 /* ORKInstructionStepHTMLFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 51EB9A522B8408D50064A515 /* ORKInstructionStepHTMLFormatter.m */; }; + 51E03D6324919711008F8406 /* ORKPermissionType.h in Headers */ = {isa = PBXBuildFile; fileRef = 51E03D6124919711008F8406 /* ORKPermissionType.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + 51E03D6424919711008F8406 /* ORKPermissionType.m in Sources */ = {isa = PBXBuildFile; fileRef = 51E03D6224919711008F8406 /* ORKPermissionType.m */; platformFilter = ios; }; + 51E03D682491A601008F8406 /* ORKHealthKitPermissionType.h in Headers */ = {isa = PBXBuildFile; fileRef = 51E03D662491A601008F8406 /* ORKHealthKitPermissionType.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + 51E03D692491A601008F8406 /* ORKHealthKitPermissionType.m in Sources */ = {isa = PBXBuildFile; fileRef = 51E03D672491A601008F8406 /* ORKHealthKitPermissionType.m */; platformFilter = ios; }; + 51EB9A532B8408D50064A515 /* ORKInstructionStepHTMLFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = 51EB9A512B8408D50064A515 /* ORKInstructionStepHTMLFormatter.h */; platformFilter = ios; }; + 51EB9A542B8408D50064A515 /* ORKInstructionStepHTMLFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 51EB9A522B8408D50064A515 /* ORKInstructionStepHTMLFormatter.m */; platformFilter = ios; }; 51EB9A5E2B8D3BA70064A515 /* ORKInstructionStepHTMLFormatterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 51EB9A5D2B8D3BA70064A515 /* ORKInstructionStepHTMLFormatterTests.m */; }; 51F716C1297E288A00D8ACF7 /* ORKNormalizedReactionTimeStimulusView.m in Sources */ = {isa = PBXBuildFile; fileRef = 51F716B7297E288900D8ACF7 /* ORKNormalizedReactionTimeStimulusView.m */; }; 51F716C2297E288A00D8ACF7 /* ORKNormalizedReactionTimeStimulusView.h in Headers */ = {isa = PBXBuildFile; fileRef = 51F716B8297E288900D8ACF7 /* ORKNormalizedReactionTimeStimulusView.h */; }; @@ -240,7 +285,7 @@ 51F716C8297E288A00D8ACF7 /* ORKNormalizedReactionTimeStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 51F716BE297E288900D8ACF7 /* ORKNormalizedReactionTimeStep.m */; }; 51F716C9297E288A00D8ACF7 /* ORKNormalizedReactionTimeViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 51F716BF297E288A00D8ACF7 /* ORKNormalizedReactionTimeViewController.h */; settings = {ATTRIBUTES = (Private, ); }; }; 51F716CA297E288A00D8ACF7 /* ORKNormalizedReactionTimeContentView.h in Headers */ = {isa = PBXBuildFile; fileRef = 51F716C0297E288A00D8ACF7 /* ORKNormalizedReactionTimeContentView.h */; }; - 51F716CB297E2A1100D8ACF7 /* ORKConsentDocument+ORKInstructionStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 0BE9D5242947EA4900DA0625 /* ORKConsentDocument+ORKInstructionStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 51F716CB297E2A1100D8ACF7 /* ORKConsentDocument+ORKInstructionStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 0BE9D5242947EA4900DA0625 /* ORKConsentDocument+ORKInstructionStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; 51F716D1297E2CC400D8ACF7 /* ORKConsentLearnMoreViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 51F716CD297E2CB600D8ACF7 /* ORKConsentLearnMoreViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 51F716D3297E2FB600D8ACF7 /* ORKConsentLearnMoreViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 51F716CE297E2CB600D8ACF7 /* ORKConsentLearnMoreViewController.m */; }; 51F716EB2981B49000D8ACF7 /* ORKSpeechInNoiseStepViewController_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 51F716E82981AF1200D8ACF7 /* ORKSpeechInNoiseStepViewController_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -249,7 +294,7 @@ 5D04884C25EF4CC30006C68B /* ORKQuestionStep_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D04884B25EF4CC30006C68B /* ORKQuestionStep_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; 5D04885725F19A7A0006C68B /* ORKDevice.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D04885525F19A7A0006C68B /* ORKDevice.h */; settings = {ATTRIBUTES = (Private, ); }; }; 5D04885825F19A7A0006C68B /* ORKDevice.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D04885625F19A7A0006C68B /* ORKDevice.m */; }; - 5D04885C25F1B3AB0006C68B /* ORKDevice_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D04885B25F1B3AB0006C68B /* ORKDevice_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 5D04885C25F1B3AB0006C68B /* ORKDevice_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D04885B25F1B3AB0006C68B /* ORKDevice_Private.h */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; }; 5D43C5D424255675006F4084 /* ORKBodyItem_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D43C5D324255675006F4084 /* ORKBodyItem_Internal.h */; settings = {ATTRIBUTES = (Private, ); }; }; 5DABE5AE24DA173F00570C57 /* ResearchKit_Prefix.pch in Headers */ = {isa = PBXBuildFile; fileRef = 5DABE5AD24DA16E600570C57 /* ResearchKit_Prefix.pch */; }; 5E6AB7DF2BC86900009ED0D5 /* ORKTaskViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E6AB7DE2BC86900009ED0D5 /* ORKTaskViewControllerTests.swift */; }; @@ -257,26 +302,27 @@ 5EB91CC72BCE2EE600BBF23E /* ORKActiveStepView.m in Sources */ = {isa = PBXBuildFile; fileRef = 86AD910F1AB7B8A600361FEB /* ORKActiveStepView.m */; }; 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 */; }; - 714080DB235FD14700281E04 /* ResearchKit.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 714080D9235FD14700281E04 /* ResearchKit.stringsdict */; }; + 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; }; 714151D0225C4A23002CA33B /* ORKPasscodeViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14A92C6922444F93007547F2 /* ORKPasscodeViewControllerTests.swift */; }; 7141EA2222EFBC0C00650145 /* ORKLoggingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7141EA2122EFBC0C00650145 /* ORKLoggingTests.m */; }; - 7167D028231B1EAA00AAB4DD /* ORKFormStep_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 7167D027231B1EAA00AAB4DD /* ORKFormStep_Internal.h */; }; - 8419D66E1FB73CC80088D7E5 /* ORKWebViewStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 8419D66C1FB73CC80088D7E5 /* ORKWebViewStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 8419D66F1FB73CC80088D7E5 /* ORKWebViewStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 8419D66D1FB73CC80088D7E5 /* ORKWebViewStep.m */; }; + 7167D028231B1EAA00AAB4DD /* ORKFormStep_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 7167D027231B1EAA00AAB4DD /* ORKFormStep_Internal.h */; platformFilter = ios; }; + 8419D66E1FB73CC80088D7E5 /* ORKWebViewStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 8419D66C1FB73CC80088D7E5 /* ORKWebViewStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + 8419D66F1FB73CC80088D7E5 /* ORKWebViewStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 8419D66D1FB73CC80088D7E5 /* ORKWebViewStep.m */; platformFilter = ios; }; 861D11AD1AA7951F003C98A7 /* ORKChoiceAnswerFormatHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = 861D11AB1AA7951F003C98A7 /* ORKChoiceAnswerFormatHelper.h */; settings = {ATTRIBUTES = (Private, ); }; }; 861D11AE1AA7951F003C98A7 /* ORKChoiceAnswerFormatHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 861D11AC1AA7951F003C98A7 /* ORKChoiceAnswerFormatHelper.m */; }; - 866DA51F1D63D04700C9AF3F /* ORKCollector_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 866DA5131D63D04700C9AF3F /* ORKCollector_Internal.h */; }; - 866DA5201D63D04700C9AF3F /* ORKCollector.h in Headers */ = {isa = PBXBuildFile; fileRef = 866DA5141D63D04700C9AF3F /* ORKCollector.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 866DA5211D63D04700C9AF3F /* ORKCollector.m in Sources */ = {isa = PBXBuildFile; fileRef = 866DA5151D63D04700C9AF3F /* ORKCollector.m */; }; - 866DA5221D63D04700C9AF3F /* ORKDataCollectionManager_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 866DA5161D63D04700C9AF3F /* ORKDataCollectionManager_Internal.h */; }; - 866DA5231D63D04700C9AF3F /* ORKDataCollectionManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 866DA5171D63D04700C9AF3F /* ORKDataCollectionManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 866DA5241D63D04700C9AF3F /* ORKDataCollectionManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 866DA5181D63D04700C9AF3F /* ORKDataCollectionManager.m */; }; - 866DA5251D63D04700C9AF3F /* ORKHealthSampleQueryOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 866DA5191D63D04700C9AF3F /* ORKHealthSampleQueryOperation.h */; }; - 866DA5261D63D04700C9AF3F /* ORKHealthSampleQueryOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 866DA51A1D63D04700C9AF3F /* ORKHealthSampleQueryOperation.m */; }; - 866DA5271D63D04700C9AF3F /* ORKMotionActivityQueryOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 866DA51B1D63D04700C9AF3F /* ORKMotionActivityQueryOperation.h */; }; - 866DA5281D63D04700C9AF3F /* ORKMotionActivityQueryOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 866DA51C1D63D04700C9AF3F /* ORKMotionActivityQueryOperation.m */; }; - 866DA5291D63D04700C9AF3F /* ORKOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 866DA51D1D63D04700C9AF3F /* ORKOperation.h */; }; - 866DA52A1D63D04700C9AF3F /* ORKOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 866DA51E1D63D04700C9AF3F /* ORKOperation.m */; }; + 866DA51F1D63D04700C9AF3F /* ORKCollector_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 866DA5131D63D04700C9AF3F /* ORKCollector_Internal.h */; platformFilter = ios; }; + 866DA5201D63D04700C9AF3F /* ORKCollector.h in Headers */ = {isa = PBXBuildFile; fileRef = 866DA5141D63D04700C9AF3F /* ORKCollector.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + 866DA5211D63D04700C9AF3F /* ORKCollector.m in Sources */ = {isa = PBXBuildFile; fileRef = 866DA5151D63D04700C9AF3F /* ORKCollector.m */; platformFilter = ios; }; + 866DA5221D63D04700C9AF3F /* ORKDataCollectionManager_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 866DA5161D63D04700C9AF3F /* ORKDataCollectionManager_Internal.h */; platformFilter = ios; }; + 866DA5231D63D04700C9AF3F /* ORKDataCollectionManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 866DA5171D63D04700C9AF3F /* ORKDataCollectionManager.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + 866DA5241D63D04700C9AF3F /* ORKDataCollectionManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 866DA5181D63D04700C9AF3F /* ORKDataCollectionManager.m */; platformFilter = ios; }; + 866DA5251D63D04700C9AF3F /* ORKHealthSampleQueryOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 866DA5191D63D04700C9AF3F /* ORKHealthSampleQueryOperation.h */; platformFilter = ios; }; + 866DA5261D63D04700C9AF3F /* ORKHealthSampleQueryOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 866DA51A1D63D04700C9AF3F /* ORKHealthSampleQueryOperation.m */; platformFilter = ios; }; + 866DA5271D63D04700C9AF3F /* ORKMotionActivityQueryOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 866DA51B1D63D04700C9AF3F /* ORKMotionActivityQueryOperation.h */; platformFilter = ios; }; + 866DA5281D63D04700C9AF3F /* ORKMotionActivityQueryOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 866DA51C1D63D04700C9AF3F /* ORKMotionActivityQueryOperation.m */; platformFilter = ios; }; + 866DA5291D63D04700C9AF3F /* ORKOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 866DA51D1D63D04700C9AF3F /* ORKOperation.h */; platformFilter = ios; }; + 866DA52A1D63D04700C9AF3F /* ORKOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 866DA51E1D63D04700C9AF3F /* ORKOperation.m */; platformFilter = ios; }; 86C40CC01A8D7C5C00081FAC /* ORKCompletionStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B521A8D7C5B00081FAC /* ORKCompletionStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; 86C40CC21A8D7C5C00081FAC /* ORKCompletionStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B531A8D7C5B00081FAC /* ORKCompletionStep.m */; }; 86C40CE41A8D7C5C00081FAC /* ORKAnswerFormat.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B641A8D7C5B00081FAC /* ORKAnswerFormat.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -286,11 +332,11 @@ 86C40D141A8D7C5C00081FAC /* ORKHelpers_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B7C1A8D7C5C00081FAC /* ORKHelpers_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; 86C40D161A8D7C5C00081FAC /* ORKErrors.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B7D1A8D7C5C00081FAC /* ORKErrors.h */; settings = {ATTRIBUTES = (Private, ); }; }; 86C40D181A8D7C5C00081FAC /* ORKErrors.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B7E1A8D7C5C00081FAC /* ORKErrors.m */; }; - 86C40D1A1A8D7C5C00081FAC /* ORKFormItem_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B7F1A8D7C5C00081FAC /* ORKFormItem_Internal.h */; }; + 86C40D1A1A8D7C5C00081FAC /* ORKFormItem_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B7F1A8D7C5C00081FAC /* ORKFormItem_Internal.h */; platformFilter = ios; }; 86C40D201A8D7C5C00081FAC /* ORKFormStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B821A8D7C5C00081FAC /* ORKFormStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; 86C40D221A8D7C5C00081FAC /* ORKFormStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B831A8D7C5C00081FAC /* ORKFormStep.m */; }; - 86C40D301A8D7C5C00081FAC /* ORKHealthAnswerFormat.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B8A1A8D7C5C00081FAC /* ORKHealthAnswerFormat.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 86C40D321A8D7C5C00081FAC /* ORKHealthAnswerFormat.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B8B1A8D7C5C00081FAC /* ORKHealthAnswerFormat.m */; }; + 86C40D301A8D7C5C00081FAC /* ORKHealthAnswerFormat.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B8A1A8D7C5C00081FAC /* ORKHealthAnswerFormat.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + 86C40D321A8D7C5C00081FAC /* ORKHealthAnswerFormat.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B8B1A8D7C5C00081FAC /* ORKHealthAnswerFormat.m */; platformFilter = ios; }; 86C40D361A8D7C5C00081FAC /* ORKHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B8D1A8D7C5C00081FAC /* ORKHelpers.m */; }; 86C40D401A8D7C5C00081FAC /* ORKInstructionStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B921A8D7C5C00081FAC /* ORKInstructionStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; 86C40D421A8D7C5C00081FAC /* ORKInstructionStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B931A8D7C5C00081FAC /* ORKInstructionStep.m */; }; @@ -298,7 +344,7 @@ 86C40D581A8D7C5C00081FAC /* ORKOrderedTask.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B9E1A8D7C5C00081FAC /* ORKOrderedTask.m */; }; 86C40D5E1A8D7C5C00081FAC /* ORKQuestionStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BA11A8D7C5C00081FAC /* ORKQuestionStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; 86C40D601A8D7C5C00081FAC /* ORKQuestionStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40BA21A8D7C5C00081FAC /* ORKQuestionStep.m */; }; - 86C40D621A8D7C5C00081FAC /* ORKQuestionStep_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BA31A8D7C5C00081FAC /* ORKQuestionStep_Internal.h */; }; + 86C40D621A8D7C5C00081FAC /* ORKQuestionStep_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BA31A8D7C5C00081FAC /* ORKQuestionStep_Internal.h */; platformFilter = ios; }; 86C40D6A1A8D7C5C00081FAC /* ORKResult.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BA71A8D7C5C00081FAC /* ORKResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; 86C40D6C1A8D7C5C00081FAC /* ORKResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40BA81A8D7C5C00081FAC /* ORKResult.m */; }; 86C40D6E1A8D7C5C00081FAC /* ORKResult_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BA91A8D7C5C00081FAC /* ORKResult_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -306,16 +352,16 @@ 86C40D901A8D7C5C00081FAC /* ORKStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40BBA1A8D7C5C00081FAC /* ORKStep.m */; }; 86C40D921A8D7C5C00081FAC /* ORKStep_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BBB1A8D7C5C00081FAC /* ORKStep_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; 86C40DC61A8D7C5C00081FAC /* ORKTask.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BD51A8D7C5C00081FAC /* ORKTask.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 86C40DFE1A8D7C5C00081FAC /* ORKConsentDocument.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BF21A8D7C5C00081FAC /* ORKConsentDocument.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 86C40E001A8D7C5C00081FAC /* ORKConsentDocument.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40BF31A8D7C5C00081FAC /* ORKConsentDocument.m */; }; - 86C40E021A8D7C5C00081FAC /* ORKConsentDocument_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BF41A8D7C5C00081FAC /* ORKConsentDocument_Internal.h */; }; - 86C40E081A8D7C5C00081FAC /* ORKConsentReviewStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BF71A8D7C5C00081FAC /* ORKConsentReviewStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 86C40E0A1A8D7C5C00081FAC /* ORKConsentReviewStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40BF81A8D7C5C00081FAC /* ORKConsentReviewStep.m */; }; - 86C40E181A8D7C5C00081FAC /* ORKConsentSection.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BFF1A8D7C5C00081FAC /* ORKConsentSection.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 86C40E1A1A8D7C5C00081FAC /* ORKConsentSection.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40C001A8D7C5C00081FAC /* ORKConsentSection.m */; }; - 86C40E1C1A8D7C5C00081FAC /* ORKConsentSection_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40C011A8D7C5C00081FAC /* ORKConsentSection_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; - 86C40E1E1A8D7C5C00081FAC /* ORKConsentSignature.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40C021A8D7C5C00081FAC /* ORKConsentSignature.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 86C40E201A8D7C5C00081FAC /* ORKConsentSignature.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40C031A8D7C5C00081FAC /* ORKConsentSignature.m */; }; + 86C40DFE1A8D7C5C00081FAC /* ORKConsentDocument.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BF21A8D7C5C00081FAC /* ORKConsentDocument.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + 86C40E001A8D7C5C00081FAC /* ORKConsentDocument.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40BF31A8D7C5C00081FAC /* ORKConsentDocument.m */; platformFilter = ios; }; + 86C40E021A8D7C5C00081FAC /* ORKConsentDocument_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BF41A8D7C5C00081FAC /* ORKConsentDocument_Internal.h */; platformFilter = ios; }; + 86C40E081A8D7C5C00081FAC /* ORKConsentReviewStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BF71A8D7C5C00081FAC /* ORKConsentReviewStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + 86C40E0A1A8D7C5C00081FAC /* ORKConsentReviewStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40BF81A8D7C5C00081FAC /* ORKConsentReviewStep.m */; platformFilter = ios; }; + 86C40E181A8D7C5C00081FAC /* ORKConsentSection.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BFF1A8D7C5C00081FAC /* ORKConsentSection.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + 86C40E1A1A8D7C5C00081FAC /* ORKConsentSection.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40C001A8D7C5C00081FAC /* ORKConsentSection.m */; platformFilter = ios; }; + 86C40E1C1A8D7C5C00081FAC /* ORKConsentSection_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40C011A8D7C5C00081FAC /* ORKConsentSection_Private.h */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; }; + 86C40E1E1A8D7C5C00081FAC /* ORKConsentSignature.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40C021A8D7C5C00081FAC /* ORKConsentSignature.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + 86C40E201A8D7C5C00081FAC /* ORKConsentSignature.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40C031A8D7C5C00081FAC /* ORKConsentSignature.m */; platformFilter = ios; }; 86CC8EB31AC09383001CCD89 /* ORKAccessibilityTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 86CC8EA81AC09383001CCD89 /* ORKAccessibilityTests.m */; }; 86CC8EB41AC09383001CCD89 /* ORKChoiceAnswerFormatHelperTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 86CC8EA91AC09383001CCD89 /* ORKChoiceAnswerFormatHelperTests.m */; }; 86CC8EB51AC09383001CCD89 /* ORKConsentTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 86CC8EAA1AC09383001CCD89 /* ORKConsentTests.m */; }; @@ -325,21 +371,21 @@ 86D348021AC161B0006DB02B /* ORKRecorderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 86D348001AC16175006DB02B /* ORKRecorderTests.m */; }; AE75433A24E32CCC00E4C7CF /* ORKEarlyTerminationConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = AE75433824E32CCC00E4C7CF /* ORKEarlyTerminationConfiguration.h */; settings = {ATTRIBUTES = (Public, ); }; }; AE75433B24E32CCC00E4C7CF /* ORKEarlyTerminationConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = AE75433924E32CCC00E4C7CF /* ORKEarlyTerminationConfiguration.m */; }; - B11C54991A9EEF8800265E61 /* ORKConsentSharingStep.h in Headers */ = {isa = PBXBuildFile; fileRef = B11C54961A9EEF8800265E61 /* ORKConsentSharingStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; - B11C549B1A9EEF8800265E61 /* ORKConsentSharingStep.m in Sources */ = {isa = PBXBuildFile; fileRef = B11C54971A9EEF8800265E61 /* ORKConsentSharingStep.m */; }; + B11C54991A9EEF8800265E61 /* ORKConsentSharingStep.h in Headers */ = {isa = PBXBuildFile; fileRef = B11C54961A9EEF8800265E61 /* ORKConsentSharingStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + B11C549B1A9EEF8800265E61 /* ORKConsentSharingStep.m in Sources */ = {isa = PBXBuildFile; fileRef = B11C54971A9EEF8800265E61 /* ORKConsentSharingStep.m */; platformFilter = ios; }; B183A4A21A8535D100C76870 /* ResearchKit_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = B1B894391A00345200C5CF2D /* ResearchKit_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; B183A4DD1A8535D100C76870 /* ResearchKit.h in Headers */ = {isa = PBXBuildFile; fileRef = B1C1DE4F196F541F00F75544 /* ResearchKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; - B1C0F4E41A9BA65F0022C153 /* ResearchKit.strings in Resources */ = {isa = PBXBuildFile; fileRef = B1C0F4E11A9BA65F0022C153 /* ResearchKit.strings */; }; - B1C7955E1A9FBF04007279BA /* HealthKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B1C7955D1A9FBF04007279BA /* HealthKit.framework */; settings = {ATTRIBUTES = (Required, ); }; }; + B1C0F4E41A9BA65F0022C153 /* ResearchKit.strings in Resources */ = {isa = PBXBuildFile; fileRef = B1C0F4E11A9BA65F0022C153 /* ResearchKit.strings */; platformFilter = ios; }; + B1C7955E1A9FBF04007279BA /* HealthKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B1C7955D1A9FBF04007279BA /* HealthKit.framework */; platformFilter = ios; settings = {ATTRIBUTES = (Required, ); }; }; BA473FE8224DB38900A362E3 /* ORKBodyItem.h in Headers */ = {isa = PBXBuildFile; fileRef = BA473FE6224DB38900A362E3 /* ORKBodyItem.h */; settings = {ATTRIBUTES = (Public, ); }; }; BA473FE9224DB38900A362E3 /* ORKBodyItem.m in Sources */ = {isa = PBXBuildFile; fileRef = BA473FE7224DB38900A362E3 /* ORKBodyItem.m */; }; BA8C5021226FFB04001896D0 /* ORKLearnMoreItem.h in Headers */ = {isa = PBXBuildFile; fileRef = BA8C501F226FFB04001896D0 /* ORKLearnMoreItem.h */; settings = {ATTRIBUTES = (Public, ); }; }; BAB96755226E5D67006AAC56 /* ORKStepContainerViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BAB96754226E5D67006AAC56 /* ORKStepContainerViewTests.m */; }; - BABBB1AE2097D97200CB29E5 /* ORKPDFViewerStep.h in Headers */ = {isa = PBXBuildFile; fileRef = BABBB1AC2097D97200CB29E5 /* ORKPDFViewerStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; - BABBB1AF2097D97200CB29E5 /* ORKPDFViewerStep.m in Sources */ = {isa = PBXBuildFile; fileRef = BABBB1AD2097D97200CB29E5 /* ORKPDFViewerStep.m */; }; + BABBB1AE2097D97200CB29E5 /* ORKPDFViewerStep.h in Headers */ = {isa = PBXBuildFile; fileRef = BABBB1AC2097D97200CB29E5 /* ORKPDFViewerStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + BABBB1AF2097D97200CB29E5 /* ORKPDFViewerStep.m in Sources */ = {isa = PBXBuildFile; fileRef = BABBB1AD2097D97200CB29E5 /* ORKPDFViewerStep.m */; platformFilter = ios; }; BAD9E9122255E9750014FA29 /* ORKLearnMoreInstructionStep.h in Headers */ = {isa = PBXBuildFile; fileRef = BAD9E9102255E9750014FA29 /* ORKLearnMoreInstructionStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; BAD9E9132255E9750014FA29 /* ORKLearnMoreInstructionStep.m in Sources */ = {isa = PBXBuildFile; fileRef = BAD9E9112255E9750014FA29 /* ORKLearnMoreInstructionStep.m */; }; - BC081DE224CBC4DE00AD92AA /* ORKTypes_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = BC081DE124CBC4DE00AD92AA /* ORKTypes_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; + BC081DE224CBC4DE00AD92AA /* ORKTypes_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = BC081DE124CBC4DE00AD92AA /* ORKTypes_Private.h */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; }; BC13CE391B0660220044153C /* ORKNavigableOrderedTask.h in Headers */ = {isa = PBXBuildFile; fileRef = BC13CE371B0660220044153C /* ORKNavigableOrderedTask.h */; settings = {ATTRIBUTES = (Public, ); }; }; BC13CE3A1B0660220044153C /* ORKNavigableOrderedTask.m in Sources */ = {isa = PBXBuildFile; fileRef = BC13CE381B0660220044153C /* ORKNavigableOrderedTask.m */; }; BC13CE3C1B0662990044153C /* ORKStepNavigationRule_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = BC13CE3B1B0662990044153C /* ORKStepNavigationRule_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -348,23 +394,23 @@ BC2908BC1FBD628F0030AB89 /* ORKTypes.m in Sources */ = {isa = PBXBuildFile; fileRef = BC2908BB1FBD628F0030AB89 /* ORKTypes.m */; }; BC40696121104DDF00A4100C /* ResearchKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B183A5951A8535D100C76870 /* ResearchKit.framework */; }; BC4D521F27B326EA0099DC18 /* ORKSecureCodingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC4D521E27B326EA0099DC18 /* ORKSecureCodingTests.swift */; }; - BC94EF311E962F7400143081 /* ORKDeprecated.h in Headers */ = {isa = PBXBuildFile; fileRef = BC94EF2F1E962F7400143081 /* ORKDeprecated.h */; settings = {ATTRIBUTES = (Public, ); }; }; - BC94EF361E96394C00143081 /* ORKRegistrationStep_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = BC94EF351E96394C00143081 /* ORKRegistrationStep_Internal.h */; }; + BC94EF311E962F7400143081 /* ORKDeprecated.h in Headers */ = {isa = PBXBuildFile; fileRef = BC94EF2F1E962F7400143081 /* ORKDeprecated.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + BC94EF361E96394C00143081 /* ORKRegistrationStep_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = BC94EF351E96394C00143081 /* ORKRegistrationStep_Internal.h */; platformFilter = ios; }; BCA5C0351AEC05F20092AC8D /* ORKStepNavigationRule.h in Headers */ = {isa = PBXBuildFile; fileRef = BCA5C0331AEC05F20092AC8D /* ORKStepNavigationRule.h */; settings = {ATTRIBUTES = (Public, ); }; }; BCA5C0361AEC05F20092AC8D /* ORKStepNavigationRule.m in Sources */ = {isa = PBXBuildFile; fileRef = BCA5C0341AEC05F20092AC8D /* ORKStepNavigationRule.m */; }; BCAD50E81B0201EE0034806A /* ORKTaskTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BCAD50E71B0201EE0034806A /* ORKTaskTests.m */; }; BCB080A11B83EFB900A3F400 /* ORKStepNavigationRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCB080A01B83EFB900A3F400 /* ORKStepNavigationRule.swift */; }; BCB8133C1C98367A00346561 /* ORKTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = BCB8133B1C98367A00346561 /* ORKTypes.h */; settings = {ATTRIBUTES = (Public, ); }; }; BCB96C131B19C0EC002A0B96 /* ORKStepTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BCB96C121B19C0EC002A0B96 /* ORKStepTests.m */; }; - BCCE9EC121104B2200B809F8 /* ORKConsentDocument_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = BCCE9EC021104B2200B809F8 /* ORKConsentDocument_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; + BCCE9EC121104B2200B809F8 /* ORKConsentDocument_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = BCCE9EC021104B2200B809F8 /* ORKConsentDocument_Private.h */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; }; BCFF24BD1B0798D10044EC35 /* ORKResultPredicate.m in Sources */ = {isa = PBXBuildFile; fileRef = BCFF24BC1B0798D10044EC35 /* ORKResultPredicate.m */; }; - BF1D43851D4904C6007EE90B /* ORKVideoInstructionStep.h in Headers */ = {isa = PBXBuildFile; fileRef = BF1D43831D4904C6007EE90B /* ORKVideoInstructionStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; - BF1D43861D4904C6007EE90B /* ORKVideoInstructionStep.m in Sources */ = {isa = PBXBuildFile; fileRef = BF1D43841D4904C6007EE90B /* ORKVideoInstructionStep.m */; }; - BF5161501BE9C53D00174DDD /* ORKWaitStep.h in Headers */ = {isa = PBXBuildFile; fileRef = BF9155A11BDE8DA9007FA459 /* ORKWaitStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; - BF91559B1BDE8D7D007FA459 /* ORKReviewStep_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = BF9155951BDE8D7D007FA459 /* ORKReviewStep_Internal.h */; }; - BF91559C1BDE8D7D007FA459 /* ORKReviewStep.h in Headers */ = {isa = PBXBuildFile; fileRef = BF9155961BDE8D7D007FA459 /* ORKReviewStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; - BF91559D1BDE8D7D007FA459 /* ORKReviewStep.m in Sources */ = {isa = PBXBuildFile; fileRef = BF9155971BDE8D7D007FA459 /* ORKReviewStep.m */; }; - BF9155A81BDE8DA9007FA459 /* ORKWaitStep.m in Sources */ = {isa = PBXBuildFile; fileRef = BF9155A21BDE8DA9007FA459 /* ORKWaitStep.m */; }; + BF1D43851D4904C6007EE90B /* ORKVideoInstructionStep.h in Headers */ = {isa = PBXBuildFile; fileRef = BF1D43831D4904C6007EE90B /* ORKVideoInstructionStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + BF1D43861D4904C6007EE90B /* ORKVideoInstructionStep.m in Sources */ = {isa = PBXBuildFile; fileRef = BF1D43841D4904C6007EE90B /* ORKVideoInstructionStep.m */; platformFilter = ios; }; + BF5161501BE9C53D00174DDD /* ORKWaitStep.h in Headers */ = {isa = PBXBuildFile; fileRef = BF9155A11BDE8DA9007FA459 /* ORKWaitStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + BF91559B1BDE8D7D007FA459 /* ORKReviewStep_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = BF9155951BDE8D7D007FA459 /* ORKReviewStep_Internal.h */; platformFilter = ios; }; + BF91559C1BDE8D7D007FA459 /* ORKReviewStep.h in Headers */ = {isa = PBXBuildFile; fileRef = BF9155961BDE8D7D007FA459 /* ORKReviewStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + BF91559D1BDE8D7D007FA459 /* ORKReviewStep.m in Sources */ = {isa = PBXBuildFile; fileRef = BF9155971BDE8D7D007FA459 /* ORKReviewStep.m */; platformFilter = ios; }; + BF9155A81BDE8DA9007FA459 /* ORKWaitStep.m in Sources */ = {isa = PBXBuildFile; fileRef = BF9155A21BDE8DA9007FA459 /* ORKWaitStep.m */; platformFilter = ios; }; CA08054028AD7CC8001695EF /* ORKViewControllerProviding.h in Headers */ = {isa = PBXBuildFile; fileRef = CA08053F28AD7CC8001695EF /* ORKViewControllerProviding.h */; settings = {ATTRIBUTES = (Private, ); }; }; CA0AC56928BD4FAC00E80040 /* ORKStepViewControllerHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA0AC56828BD4FAB00E80040 /* ORKStepViewControllerHelpers.swift */; }; CA1C7A44288B0C68004DAB3A /* ResearchKitUI.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D06632F24FEF272005D9B40 /* ResearchKitUI.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -398,7 +444,7 @@ CA2B8F9528A16E860025B773 /* ORKdBHLToneAudiometryOnboardingStepViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 71769E302088260B00A19914 /* ORKdBHLToneAudiometryOnboardingStepViewController.m */; }; CA2B8F9628A16E8F0025B773 /* ORKdBHLToneAudiometryOnboardingStepViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 71769E2F2088260B00A19914 /* ORKdBHLToneAudiometryOnboardingStepViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; CA2B8F9728A16E930025B773 /* ORKdBHLToneAudiometryAudioGenerator.h in Headers */ = {isa = PBXBuildFile; fileRef = 716B126620A7A40400590264 /* ORKdBHLToneAudiometryAudioGenerator.h */; settings = {ATTRIBUTES = (Private, ); }; }; - CA2B8F9828A16E960025B773 /* ORKdBHLToneAudiometryContentView.h in Headers */ = {isa = PBXBuildFile; fileRef = 71769E332088291B00A19914 /* ORKdBHLToneAudiometryContentView.h */; }; + CA2B8F9828A16E960025B773 /* ORKdBHLToneAudiometryContentView.h in Headers */ = {isa = PBXBuildFile; fileRef = 71769E332088291B00A19914 /* ORKdBHLToneAudiometryContentView.h */; settings = {ATTRIBUTES = (Private, ); }; }; CA2B8F9928A16E9C0025B773 /* ORKdBHLToneAudiometryStepViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 71769E3B20884DB800A19914 /* ORKdBHLToneAudiometryStepViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; CA2B8F9A28A16EF90025B773 /* ORKAmslerGridContentView.m in Sources */ = {isa = PBXBuildFile; fileRef = BA95AA9D20ACD0E700E7FF8E /* ORKAmslerGridContentView.m */; }; CA2B8F9B28A16EF90025B773 /* ORKAmslerGridStepViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = BA95AA9920ACD0BD00E7FF8E /* ORKAmslerGridStepViewController.m */; }; @@ -491,20 +537,20 @@ CA2B8FF928A177C30025B773 /* ORKTrailmakingStepViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 781D540E1DF886AB00223305 /* ORKTrailmakingStepViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; CA2B8FFA28A177DE0025B773 /* ORKWalkingTaskStepViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B211A8D7C5B00081FAC /* ORKWalkingTaskStepViewController.m */; }; CA2B8FFB28A177E40025B773 /* ORKWalkingTaskStepViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B201A8D7C5B00081FAC /* ORKWalkingTaskStepViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; - CA2B900928A17AD90025B773 /* ORKActiveStep_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B321A8D7C5B00081FAC /* ORKActiveStep_Internal.h */; settings = {ATTRIBUTES = (Private, ); }; }; - CA2B900A28A17ADE0025B773 /* ORKActiveStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B311A8D7C5B00081FAC /* ORKActiveStep.m */; }; - CA2B900B28A17AE10025B773 /* ORKActiveStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B301A8D7C5B00081FAC /* ORKActiveStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CA2B900928A17AD90025B773 /* ORKActiveStep_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B321A8D7C5B00081FAC /* ORKActiveStep_Internal.h */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; }; + CA2B900A28A17ADE0025B773 /* ORKActiveStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B311A8D7C5B00081FAC /* ORKActiveStep.m */; platformFilter = ios; }; + CA2B900B28A17AE10025B773 /* ORKActiveStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B301A8D7C5B00081FAC /* ORKActiveStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; CA2B901028A17BEE0025B773 /* ORKActiveTaskResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A261E81A87B005C2A1E /* ORKActiveTaskResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; - CA2B901128A17D120025B773 /* ORKFileResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A391E81AF1D005C2A1E /* ORKFileResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; - CA2B901228A17D170025B773 /* ORKFileResult.m in Sources */ = {isa = PBXBuildFile; fileRef = FF919A3A1E81AF1D005C2A1E /* ORKFileResult.m */; }; + CA2B901128A17D120025B773 /* ORKFileResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A391E81AF1D005C2A1E /* ORKFileResult.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + CA2B901228A17D170025B773 /* ORKFileResult.m in Sources */ = {isa = PBXBuildFile; fileRef = FF919A3A1E81AF1D005C2A1E /* ORKFileResult.m */; platformFilter = ios; }; CA2B901628A1802C0025B773 /* ORKOrderedTask+ORKPredefinedActiveTask.m in Sources */ = {isa = PBXBuildFile; fileRef = FF154FB31E82EF5E004ED908 /* ORKOrderedTask+ORKPredefinedActiveTask.m */; }; CA2B901728A180480025B773 /* ORKOrderedTask+ORKPredefinedActiveTask.h in Headers */ = {isa = PBXBuildFile; fileRef = FF154FB21E82EF5E004ED908 /* ORKOrderedTask+ORKPredefinedActiveTask.h */; settings = {ATTRIBUTES = (Public, ); }; }; - CA2B901F28A1864A0025B773 /* ORKRecorder.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B471A8D7C5B00081FAC /* ORKRecorder.h */; settings = {ATTRIBUTES = (Public, ); }; }; - CA2B902028A186500025B773 /* ORKRecorder_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B491A8D7C5B00081FAC /* ORKRecorder_Internal.h */; }; - CA2B902128A186550025B773 /* ORKRecorder_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B4A1A8D7C5B00081FAC /* ORKRecorder_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; - CA2B902228A1867E0025B773 /* ORKRecorder.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B481A8D7C5B00081FAC /* ORKRecorder.m */; }; - CA2B902328A186A80025B773 /* ORKDataLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B3C1A8D7C5B00081FAC /* ORKDataLogger.h */; settings = {ATTRIBUTES = (Private, ); }; }; - CA2B902428A186AF0025B773 /* ORKDataLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B3D1A8D7C5B00081FAC /* ORKDataLogger.m */; }; + CA2B901F28A1864A0025B773 /* ORKRecorder.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B471A8D7C5B00081FAC /* ORKRecorder.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + CA2B902028A186500025B773 /* ORKRecorder_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B491A8D7C5B00081FAC /* ORKRecorder_Internal.h */; platformFilter = ios; }; + CA2B902128A186550025B773 /* ORKRecorder_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B4A1A8D7C5B00081FAC /* ORKRecorder_Private.h */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; }; + CA2B902228A1867E0025B773 /* ORKRecorder.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B481A8D7C5B00081FAC /* ORKRecorder.m */; platformFilter = ios; }; + CA2B902328A186A80025B773 /* ORKDataLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B3C1A8D7C5B00081FAC /* ORKDataLogger.h */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; }; + CA2B902428A186AF0025B773 /* ORKDataLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B3D1A8D7C5B00081FAC /* ORKDataLogger.m */; platformFilter = ios; }; CA2B902628A187390025B773 /* ORKTask_Util.m in Sources */ = {isa = PBXBuildFile; fileRef = CA2B902528A187390025B773 /* ORKTask_Util.m */; }; CA2B902728A18EA60025B773 /* ORKActiveStepViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B381A8D7C5B00081FAC /* ORKActiveStepViewController.m */; }; CA2B902828A18EAD0025B773 /* ORKActiveStepViewController_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B391A8D7C5B00081FAC /* ORKActiveStepViewController_Internal.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -520,15 +566,15 @@ CA6A0D7F288B51D30048C1EF /* ORKSkin.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40BB81A8D7C5C00081FAC /* ORKSkin.m */; }; CA6A0D81288B54650048C1EF /* ORKConsentReviewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BEC1A8D7C5C00081FAC /* ORKConsentReviewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; CA6A0D82288B54650048C1EF /* ORKConsentReviewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40BED1A8D7C5C00081FAC /* ORKConsentReviewController.m */; }; - CA6A0D83288B5B370048C1EF /* ORKHTMLPDFWriter.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B8E1A8D7C5C00081FAC /* ORKHTMLPDFWriter.h */; settings = {ATTRIBUTES = (Public, ); }; }; - CA6A0D84288B5B370048C1EF /* ORKHTMLPDFPageRenderer.h in Headers */ = {isa = PBXBuildFile; fileRef = 8DE27B3E1D5BC072009A26E3 /* ORKHTMLPDFPageRenderer.h */; }; - CA6A0D85288B5B370048C1EF /* ORKHTMLPDFPageRenderer.m in Sources */ = {isa = PBXBuildFile; fileRef = 8DE27B3F1D5BC0B9009A26E3 /* ORKHTMLPDFPageRenderer.m */; }; - CA6A0D86288B5B370048C1EF /* ORKHTMLPDFWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B8F1A8D7C5C00081FAC /* ORKHTMLPDFWriter.m */; }; + CA6A0D83288B5B370048C1EF /* ORKHTMLPDFWriter.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B8E1A8D7C5C00081FAC /* ORKHTMLPDFWriter.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + CA6A0D84288B5B370048C1EF /* ORKHTMLPDFPageRenderer.h in Headers */ = {isa = PBXBuildFile; fileRef = 8DE27B3E1D5BC072009A26E3 /* ORKHTMLPDFPageRenderer.h */; platformFilter = ios; }; + CA6A0D85288B5B370048C1EF /* ORKHTMLPDFPageRenderer.m in Sources */ = {isa = PBXBuildFile; fileRef = 8DE27B3F1D5BC0B9009A26E3 /* ORKHTMLPDFPageRenderer.m */; platformFilter = ios; }; + CA6A0D86288B5B370048C1EF /* ORKHTMLPDFWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B8F1A8D7C5C00081FAC /* ORKHTMLPDFWriter.m */; platformFilter = ios; }; CA6A0D9E288F1CA20048C1EF /* ResearchKitUI_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = CA6A0D9D288F1BDC0048C1EF /* ResearchKitUI_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; CA6A0DA0288F2C1C0048C1EF /* ResearchKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CA1C7A5A288B0C68004DAB3A /* ResearchKitUI.framework */; }; CA954B6E28AD8A8C0020A35C /* ORKStep+ResearchKitActiveTask.m in Sources */ = {isa = PBXBuildFile; fileRef = CA954B6D28AD8A8C0020A35C /* ORKStep+ResearchKitActiveTask.m */; }; CA954B6F28AD8DA50020A35C /* ORKStep+ResearchKitUI.m in Sources */ = {isa = PBXBuildFile; fileRef = CA08054328AD7EBA001695EF /* ORKStep+ResearchKitUI.m */; }; - CA994D5628AB08410019DEA4 /* ORKDeprecated.m in Sources */ = {isa = PBXBuildFile; fileRef = BC94EF301E962F7400143081 /* ORKDeprecated.m */; }; + CA994D5628AB08410019DEA4 /* ORKDeprecated.m in Sources */ = {isa = PBXBuildFile; fileRef = BC94EF301E962F7400143081 /* ORKDeprecated.m */; platformFilter = ios; }; CAA20D4E288B3D5700EDC764 /* ORKRecordButton.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D43C5CF24217393006F4084 /* ORKRecordButton.h */; settings = {ATTRIBUTES = (Public, ); }; }; CAA20D4F288B3D5700EDC764 /* ORKRecordButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D43C5D024217393006F4084 /* ORKRecordButton.m */; }; CAA20D50288B3D6400EDC764 /* ORKCheckmarkView.h in Headers */ = {isa = PBXBuildFile; fileRef = BAD6FAAF2332A2FD006647E7 /* ORKCheckmarkView.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -942,44 +988,66 @@ CAFAA6C228A198BD0010BBDE /* ResearchKitActiveTask_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = CAFAA6C128A198BD0010BBDE /* ResearchKitActiveTask_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; CAFAA6C428A19E200010BBDE /* ResearchKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B183A5951A8535D100C76870 /* ResearchKit.framework */; }; CAFAA6C528A19E260010BBDE /* ResearchKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CA1C7A5A288B0C68004DAB3A /* ResearchKitUI.framework */; }; - D44239791AF17F5100559D96 /* ORKImageCaptureStep.h in Headers */ = {isa = PBXBuildFile; fileRef = D44239771AF17F5100559D96 /* ORKImageCaptureStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D442397A1AF17F5100559D96 /* ORKImageCaptureStep.m in Sources */ = {isa = PBXBuildFile; fileRef = D44239781AF17F5100559D96 /* ORKImageCaptureStep.m */; }; + D44239791AF17F5100559D96 /* ORKImageCaptureStep.h in Headers */ = {isa = PBXBuildFile; fileRef = D44239771AF17F5100559D96 /* ORKImageCaptureStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + D442397A1AF17F5100559D96 /* ORKImageCaptureStep.m in Sources */ = {isa = PBXBuildFile; fileRef = D44239781AF17F5100559D96 /* ORKImageCaptureStep.m */; platformFilter = ios; }; + E454438D2CC1C78E0004E385 /* ResearchKit_Prefix.pch in Headers */ = {isa = PBXBuildFile; fileRef = 5DABE5AD24DA16E600570C57 /* ResearchKit_Prefix.pch */; }; FA7A9D2B1B082688005A2BEA /* ORKConsentDocumentTests.m in Sources */ = {isa = PBXBuildFile; fileRef = FA7A9D2A1B082688005A2BEA /* ORKConsentDocumentTests.m */; }; - FA7A9D2F1B083DD3005A2BEA /* ORKConsentSectionFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = FA7A9D2D1B083DD3005A2BEA /* ORKConsentSectionFormatter.h */; }; - FA7A9D301B083DD3005A2BEA /* ORKConsentSectionFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = FA7A9D2E1B083DD3005A2BEA /* ORKConsentSectionFormatter.m */; }; - FA7A9D331B0843A9005A2BEA /* ORKConsentSignatureFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = FA7A9D311B0843A9005A2BEA /* ORKConsentSignatureFormatter.h */; }; - FA7A9D341B0843A9005A2BEA /* ORKConsentSignatureFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = FA7A9D321B0843A9005A2BEA /* ORKConsentSignatureFormatter.m */; }; + FA7A9D2F1B083DD3005A2BEA /* ORKConsentSectionFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = FA7A9D2D1B083DD3005A2BEA /* ORKConsentSectionFormatter.h */; platformFilter = ios; }; + FA7A9D301B083DD3005A2BEA /* ORKConsentSectionFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = FA7A9D2E1B083DD3005A2BEA /* ORKConsentSectionFormatter.m */; platformFilter = ios; }; + FA7A9D331B0843A9005A2BEA /* ORKConsentSignatureFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = FA7A9D311B0843A9005A2BEA /* ORKConsentSignatureFormatter.h */; platformFilter = ios; }; + FA7A9D341B0843A9005A2BEA /* ORKConsentSignatureFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = FA7A9D321B0843A9005A2BEA /* ORKConsentSignatureFormatter.m */; platformFilter = ios; }; FA7A9D371B09365F005A2BEA /* ORKConsentSectionFormatterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = FA7A9D361B09365F005A2BEA /* ORKConsentSectionFormatterTests.m */; }; FA7A9D391B0969A7005A2BEA /* ORKConsentSignatureFormatterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = FA7A9D381B0969A7005A2BEA /* ORKConsentSignatureFormatterTests.m */; }; - FF0CB38A1FD5C4C3002D838C /* ORKWebViewStepResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF0CB3881FD5C4C3002D838C /* ORKWebViewStepResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; - FF0CB38B1FD5C4C3002D838C /* ORKWebViewStepResult.m in Sources */ = {isa = PBXBuildFile; fileRef = FF0CB3891FD5C4C3002D838C /* ORKWebViewStepResult.m */; }; - FF5051ED1D668FF80065E677 /* ORKPageStep_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = FF5051EC1D668FF80065E677 /* ORKPageStep_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; - FF5051F01D66908C0065E677 /* ORKNavigablePageStep.h in Headers */ = {isa = PBXBuildFile; fileRef = FF5051EE1D66908C0065E677 /* ORKNavigablePageStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; - FF5051F11D66908C0065E677 /* ORKNavigablePageStep.m in Sources */ = {isa = PBXBuildFile; fileRef = FF5051EF1D66908C0065E677 /* ORKNavigablePageStep.m */; }; - FF5CA6121D2C2670001660A3 /* ORKTableStep.h in Headers */ = {isa = PBXBuildFile; fileRef = FF5CA6101D2C2670001660A3 /* ORKTableStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; - FF5CA6131D2C2670001660A3 /* ORKTableStep.m in Sources */ = {isa = PBXBuildFile; fileRef = FF5CA6111D2C2670001660A3 /* ORKTableStep.m */; }; - FF5CA61B1D2C6453001660A3 /* ORKSignatureStep.h in Headers */ = {isa = PBXBuildFile; fileRef = FF5CA6191D2C6453001660A3 /* ORKSignatureStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; - FF5CA61C1D2C6453001660A3 /* ORKSignatureStep.m in Sources */ = {isa = PBXBuildFile; fileRef = FF5CA61A1D2C6453001660A3 /* ORKSignatureStep.m */; }; + FF0CB38A1FD5C4C3002D838C /* ORKWebViewStepResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF0CB3881FD5C4C3002D838C /* ORKWebViewStepResult.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + FF0CB38B1FD5C4C3002D838C /* ORKWebViewStepResult.m in Sources */ = {isa = PBXBuildFile; fileRef = FF0CB3891FD5C4C3002D838C /* ORKWebViewStepResult.m */; platformFilter = ios; }; + FF5051ED1D668FF80065E677 /* ORKPageStep_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = FF5051EC1D668FF80065E677 /* ORKPageStep_Private.h */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; }; + FF5051F01D66908C0065E677 /* ORKNavigablePageStep.h in Headers */ = {isa = PBXBuildFile; fileRef = FF5051EE1D66908C0065E677 /* ORKNavigablePageStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + FF5051F11D66908C0065E677 /* ORKNavigablePageStep.m in Sources */ = {isa = PBXBuildFile; fileRef = FF5051EF1D66908C0065E677 /* ORKNavigablePageStep.m */; platformFilter = ios; }; + FF5CA6121D2C2670001660A3 /* ORKTableStep.h in Headers */ = {isa = PBXBuildFile; fileRef = FF5CA6101D2C2670001660A3 /* ORKTableStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + FF5CA6131D2C2670001660A3 /* ORKTableStep.m in Sources */ = {isa = PBXBuildFile; fileRef = FF5CA6111D2C2670001660A3 /* ORKTableStep.m */; platformFilter = ios; }; + FF5CA61B1D2C6453001660A3 /* ORKSignatureStep.h in Headers */ = {isa = PBXBuildFile; fileRef = FF5CA6191D2C6453001660A3 /* ORKSignatureStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + FF5CA61C1D2C6453001660A3 /* ORKSignatureStep.m in Sources */ = {isa = PBXBuildFile; fileRef = FF5CA61A1D2C6453001660A3 /* ORKSignatureStep.m */; platformFilter = ios; }; FF919A531E81BEB5005C2A1E /* ORKCollectionResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A511E81BEB5005C2A1E /* ORKCollectionResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; FF919A541E81BEB5005C2A1E /* ORKCollectionResult.m in Sources */ = {isa = PBXBuildFile; fileRef = FF919A521E81BEB5005C2A1E /* ORKCollectionResult.m */; }; FF919A561E81BEE0005C2A1E /* ORKCollectionResult_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A551E81BEDD005C2A1E /* ORKCollectionResult_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; FF919A591E81C628005C2A1E /* ORKQuestionResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A571E81C628005C2A1E /* ORKQuestionResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; FF919A5A1E81C628005C2A1E /* ORKQuestionResult.m in Sources */ = {isa = PBXBuildFile; fileRef = FF919A581E81C628005C2A1E /* ORKQuestionResult.m */; }; FF919A5C1E81C641005C2A1E /* ORKQuestionResult_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A5B1E81C63B005C2A1E /* ORKQuestionResult_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; - FF919A5F1E81CF07005C2A1E /* ORKVideoInstructionStepResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A5D1E81CF07005C2A1E /* ORKVideoInstructionStepResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; - FF919A601E81CF07005C2A1E /* ORKVideoInstructionStepResult.m in Sources */ = {isa = PBXBuildFile; fileRef = FF919A5E1E81CF07005C2A1E /* ORKVideoInstructionStepResult.m */; }; - FF919A631E81D04D005C2A1E /* ORKSignatureResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A611E81D04D005C2A1E /* ORKSignatureResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; - FF919A641E81D04D005C2A1E /* ORKSignatureResult.m in Sources */ = {isa = PBXBuildFile; fileRef = FF919A621E81D04D005C2A1E /* ORKSignatureResult.m */; }; - FF919A661E81D168005C2A1E /* ORKSignatureResult_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A651E81D164005C2A1E /* ORKSignatureResult_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; - FF919A691E81D255005C2A1E /* ORKConsentSignatureResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A671E81D255005C2A1E /* ORKConsentSignatureResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; - FF919A6A1E81D255005C2A1E /* ORKConsentSignatureResult.m in Sources */ = {isa = PBXBuildFile; fileRef = FF919A681E81D255005C2A1E /* ORKConsentSignatureResult.m */; }; - FF919A6D1E81D3B0005C2A1E /* ORKPasscodeResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A6B1E81D3B0005C2A1E /* ORKPasscodeResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; - FF919A6E1E81D3B0005C2A1E /* ORKPasscodeResult.m in Sources */ = {isa = PBXBuildFile; fileRef = FF919A6C1E81D3B0005C2A1E /* ORKPasscodeResult.m */; }; - FFDDD8491D3555EA00446806 /* ORKPageStep.h in Headers */ = {isa = PBXBuildFile; fileRef = FFDDD8471D3555EA00446806 /* ORKPageStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; - FFDDD84A1D3555EA00446806 /* ORKPageStep.m in Sources */ = {isa = PBXBuildFile; fileRef = FFDDD8481D3555EA00446806 /* ORKPageStep.m */; }; + FF919A5F1E81CF07005C2A1E /* ORKVideoInstructionStepResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A5D1E81CF07005C2A1E /* ORKVideoInstructionStepResult.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + FF919A601E81CF07005C2A1E /* ORKVideoInstructionStepResult.m in Sources */ = {isa = PBXBuildFile; fileRef = FF919A5E1E81CF07005C2A1E /* ORKVideoInstructionStepResult.m */; platformFilter = ios; }; + FF919A631E81D04D005C2A1E /* ORKSignatureResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A611E81D04D005C2A1E /* ORKSignatureResult.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + FF919A641E81D04D005C2A1E /* ORKSignatureResult.m in Sources */ = {isa = PBXBuildFile; fileRef = FF919A621E81D04D005C2A1E /* ORKSignatureResult.m */; platformFilter = ios; }; + FF919A661E81D168005C2A1E /* ORKSignatureResult_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A651E81D164005C2A1E /* ORKSignatureResult_Private.h */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; }; + FF919A691E81D255005C2A1E /* ORKConsentSignatureResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A671E81D255005C2A1E /* ORKConsentSignatureResult.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + FF919A6A1E81D255005C2A1E /* ORKConsentSignatureResult.m in Sources */ = {isa = PBXBuildFile; fileRef = FF919A681E81D255005C2A1E /* ORKConsentSignatureResult.m */; platformFilter = ios; }; + FF919A6D1E81D3B0005C2A1E /* ORKPasscodeResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A6B1E81D3B0005C2A1E /* ORKPasscodeResult.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + FF919A6E1E81D3B0005C2A1E /* ORKPasscodeResult.m in Sources */ = {isa = PBXBuildFile; fileRef = FF919A6C1E81D3B0005C2A1E /* ORKPasscodeResult.m */; platformFilter = ios; }; + FFDDD8491D3555EA00446806 /* ORKPageStep.h in Headers */ = {isa = PBXBuildFile; fileRef = FFDDD8471D3555EA00446806 /* ORKPageStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + FFDDD84A1D3555EA00446806 /* ORKPageStep.m in Sources */ = {isa = PBXBuildFile; fileRef = FFDDD8481D3555EA00446806 /* ORKPageStep.m */; platformFilter = ios; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 0BC672DF2BD9C541005798AC /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 3FFF18351829DB1D00167070 /* Project object */; + proxyType = 1; + remoteGlobalIDString = B183A4731A8535D100C76870; + remoteInfo = ResearchKit; + }; + 0BC672E12BD9C541005798AC /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 3FFF18351829DB1D00167070 /* Project object */; + proxyType = 1; + remoteGlobalIDString = CAD08966289DD747007B2A98; + remoteInfo = ResearchKitActiveTask; + }; + 0BC672E52BD9C541005798AC /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 3FFF18351829DB1D00167070 /* Project object */; + proxyType = 1; + remoteGlobalIDString = CA1C7A40288B0C68004DAB3A; + remoteInfo = "ResearchKitUI (iOS)"; + }; 86CC8EA11AC09332001CCD89 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 3FFF18351829DB1D00167070 /* Project object */; @@ -1070,6 +1138,7 @@ 0B9A990E2A8AC47500D64C40 /* NSObject+TestingSupport.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSObject+TestingSupport.m"; sourceTree = ""; }; 0B9CC5652A68C02C00080E29 /* UIImageView+ResearchKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIImageView+ResearchKit.h"; sourceTree = ""; }; 0B9CC5662A68C02C00080E29 /* UIImageView+ResearchKit.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIImageView+ResearchKit.m"; sourceTree = ""; }; + 0BA1D93A2BD1DF5A00BB79DB /* ResearchKit.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = ResearchKit.podspec; sourceTree = ""; }; 0BE9D5242947EA4900DA0625 /* ORKConsentDocument+ORKInstructionStep.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ORKConsentDocument+ORKInstructionStep.h"; sourceTree = ""; }; 0BE9D5252947EA4900DA0625 /* ORKConsentDocument+ORKInstructionStep.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "ORKConsentDocument+ORKInstructionStep.m"; sourceTree = ""; }; 106FF29C1B663FCE004EACF2 /* ORKHolePegTestPlaceStep.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ORKHolePegTestPlaceStep.h; sourceTree = ""; }; @@ -1341,10 +1410,38 @@ 5192BF832AE1BA47006E43FB /* ORKFrontFacingCameraStepResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ORKFrontFacingCameraStepResult.h; path = ResearchKit/Common/ORKFrontFacingCameraStepResult.h; sourceTree = SOURCE_ROOT; }; 5192BF882AE1BBB0006E43FB /* ORKSecondaryTaskStep.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ORKSecondaryTaskStep.m; path = ResearchKit/Common/ORKSecondaryTaskStep.m; sourceTree = SOURCE_ROOT; }; 5192BF892AE1BBB0006E43FB /* ORKSecondaryTaskStep.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ORKSecondaryTaskStep.h; path = ResearchKit/Common/ORKSecondaryTaskStep.h; sourceTree = SOURCE_ROOT; }; + 5192BF8D2AE1C051006E43FB /* ORKAgePicker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ORKAgePicker.h; sourceTree = ""; }; + 5192BF8E2AE1C051006E43FB /* ORKAgePicker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ORKAgePicker.m; sourceTree = ""; }; + 5192BF912AE1C2F7006E43FB /* ORKChoiceViewCell+ORKColorChoice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ORKChoiceViewCell+ORKColorChoice.h"; sourceTree = ""; }; + 5192BF922AE1C2F7006E43FB /* ORKChoiceViewCell+ORKColorChoice.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "ORKChoiceViewCell+ORKColorChoice.m"; sourceTree = ""; }; 5192BF952AE1C5A1006E43FB /* ORKAnswerFormat+FormStepViewControllerAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ORKAnswerFormat+FormStepViewControllerAdditions.h"; sourceTree = ""; }; 5192BF962AE1C5A1006E43FB /* ORKAnswerFormat+FormStepViewControllerAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "ORKAnswerFormat+FormStepViewControllerAdditions.m"; sourceTree = ""; }; + 5192BF9B2AE1DE62006E43FB /* ORKColorChoiceCellGroup.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ORKColorChoiceCellGroup.m; sourceTree = ""; }; + 5192BF9C2AE1DE62006E43FB /* ORKColorChoiceCellGroup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ORKColorChoiceCellGroup.h; sourceTree = ""; }; 519C298026D027AB00FD5F44 /* SwiftUIViewFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIViewFactory.swift; sourceTree = ""; }; 519C298D26D027E500FD5F44 /* TextChoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextChoiceView.swift; sourceTree = ""; }; + 519CE8162C6582BD003BB584 /* ORKHealthCondition.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ORKHealthCondition.m; sourceTree = ""; }; + 519CE8172C6582BD003BB584 /* ORKFamilyHistoryStep.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ORKFamilyHistoryStep.m; sourceTree = ""; }; + 519CE8182C6582BD003BB584 /* ORKHealthCondition.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ORKHealthCondition.h; sourceTree = ""; }; + 519CE8192C6582BD003BB584 /* ORKFamilyHistoryResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ORKFamilyHistoryResult.h; sourceTree = ""; }; + 519CE81A2C6582BD003BB584 /* ORKRelatedPerson.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ORKRelatedPerson.m; sourceTree = ""; }; + 519CE81B2C6582BD003BB584 /* ORKRelativeGroup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ORKRelativeGroup.h; sourceTree = ""; }; + 519CE81C2C6582BD003BB584 /* ORKRelativeGroup.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ORKRelativeGroup.m; sourceTree = ""; }; + 519CE81D2C6582BE003BB584 /* ORKConditionStepConfiguration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ORKConditionStepConfiguration.m; sourceTree = ""; }; + 519CE81E2C6582BE003BB584 /* ORKConditionStepConfiguration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ORKConditionStepConfiguration.h; sourceTree = ""; }; + 519CE81F2C6582BE003BB584 /* ORKFamilyHistoryStep.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ORKFamilyHistoryStep.h; sourceTree = ""; }; + 519CE8202C6582BE003BB584 /* ORKRelatedPerson.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ORKRelatedPerson.h; sourceTree = ""; }; + 519CE8212C6582BE003BB584 /* ORKFamilyHistoryResult.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ORKFamilyHistoryResult.m; sourceTree = ""; }; + 519CE8302C658617003BB584 /* ORKFamilyHistoryStepViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ORKFamilyHistoryStepViewController.h; sourceTree = ""; }; + 519CE8332C658617003BB584 /* ORKFamilyHistoryStepViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ORKFamilyHistoryStepViewController.m; sourceTree = ""; }; + 519CE83B2C658653003BB584 /* ORKFamilyHistoryRelatedPersonCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ORKFamilyHistoryRelatedPersonCell.h; sourceTree = ""; }; + 519CE83C2C658653003BB584 /* ORKFamilyHistoryRelatedPersonCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ORKFamilyHistoryRelatedPersonCell.m; sourceTree = ""; }; + 519CE83D2C658653003BB584 /* ORKFamilyHistoryTableFooterView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ORKFamilyHistoryTableFooterView.h; sourceTree = ""; }; + 519CE83E2C658654003BB584 /* ORKFamilyHistoryTableFooterView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ORKFamilyHistoryTableFooterView.m; sourceTree = ""; }; + 519CE83F2C658654003BB584 /* ORKFamilyHistoryTableHeaderView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ORKFamilyHistoryTableHeaderView.h; sourceTree = ""; }; + 519CE8402C658654003BB584 /* ORKFamilyHistoryTableHeaderView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ORKFamilyHistoryTableHeaderView.m; sourceTree = ""; }; + 519CE85B2C6AD858003BB584 /* ORKFamilyHistoryStepViewController_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ORKFamilyHistoryStepViewController_Private.h; sourceTree = ""; }; + 519CE85E2C6BC1DB003BB584 /* ORKFamilyHistoryResultTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ORKFamilyHistoryResultTests.swift; sourceTree = ""; }; 51A11F132BD08D5D0060C07E /* HKSample+ORKJSONDictionary.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "HKSample+ORKJSONDictionary.h"; sourceTree = ""; }; 51A11F142BD08D5D0060C07E /* CMMotionActivity+ORKJSONDictionary.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "CMMotionActivity+ORKJSONDictionary.m"; sourceTree = ""; }; 51A11F152BD08D5D0060C07E /* HKSample+ORKJSONDictionary.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "HKSample+ORKJSONDictionary.m"; sourceTree = ""; }; @@ -1366,6 +1463,8 @@ 51AF1B1F2B683C3400D3B399 /* ORKWebViewStepResult_Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ORKWebViewStepResult_Private.h; sourceTree = ""; }; 51AF1B212B69803A00D3B399 /* Window.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = Window.wav; sourceTree = ""; }; 51AF1B222B69803A00D3B399 /* Noise.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = Noise.wav; sourceTree = ""; }; + 51B76DFA2CB5B5A30061698A /* ResearchKitActiveTask.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = ResearchKitActiveTask.docc; sourceTree = ""; }; + 51B76DFC2CB5C73C0061698A /* ResearchKitUI.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = ResearchKitUI.docc; sourceTree = ""; }; 51B94DBC2B311E810039B0E7 /* CLLocationManager+ResearchKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CLLocationManager+ResearchKit.h"; sourceTree = ""; }; 51B94DC62B3254FD0039B0E7 /* CLLocationManager+ResearchKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "CLLocationManager+ResearchKit.m"; sourceTree = ""; }; 51BF30BC247B330900E2E669 /* ORKIconButton.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ORKIconButton.h; sourceTree = ""; }; @@ -1441,6 +1540,7 @@ 618DA04A1A93D0D600E63AA8 /* ORKAccessibilityFunctions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ORKAccessibilityFunctions.m; sourceTree = ""; }; 618DA04B1A93D0D600E63AA8 /* UIView+ORKAccessibility.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = "UIView+ORKAccessibility.h"; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 618DA04C1A93D0D600E63AA8 /* UIView+ORKAccessibility.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = "UIView+ORKAccessibility.m"; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 6227743F2C5CF7DC00F2E741 /* retspl_dBFS_AIRPODSPROV2.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = retspl_dBFS_AIRPODSPROV2.plist; sourceTree = ""; }; 7118AC6020BF6A3900D7A6BB /* Sentence7.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = Sentence7.wav; sourceTree = ""; }; 7118AC6120BF6A3A00D7A6BB /* Sentence4.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = Sentence4.wav; sourceTree = ""; }; 7118AC6220BF6A3A00D7A6BB /* Sentence6.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = Sentence6.wav; sourceTree = ""; }; @@ -2253,6 +2353,7 @@ 3FFF18341829DB1D00167070 = { isa = PBXGroup; children = ( + 0BA1D93A2BD1DF5A00BB79DB /* ResearchKit.podspec */, 05F3765923C797930068E166 /* ResearchKit.xctestplan */, 168EEAAE230B6F9E003FD2FA /* scripts */, 86B623AF19520B770074CD3C /* ResearchKit */, @@ -2472,6 +2573,57 @@ path = Normalized; sourceTree = ""; }; + 519CE8152C6582A0003BB584 /* Family History */ = { + isa = PBXGroup; + children = ( + 519CE81E2C6582BE003BB584 /* ORKConditionStepConfiguration.h */, + 519CE81D2C6582BE003BB584 /* ORKConditionStepConfiguration.m */, + 519CE8192C6582BD003BB584 /* ORKFamilyHistoryResult.h */, + 519CE8212C6582BE003BB584 /* ORKFamilyHistoryResult.m */, + 519CE81F2C6582BE003BB584 /* ORKFamilyHistoryStep.h */, + 519CE8172C6582BD003BB584 /* ORKFamilyHistoryStep.m */, + 519CE8182C6582BD003BB584 /* ORKHealthCondition.h */, + 519CE8162C6582BD003BB584 /* ORKHealthCondition.m */, + 519CE8202C6582BE003BB584 /* ORKRelatedPerson.h */, + 519CE81A2C6582BD003BB584 /* ORKRelatedPerson.m */, + 519CE81B2C6582BD003BB584 /* ORKRelativeGroup.h */, + 519CE81C2C6582BD003BB584 /* ORKRelativeGroup.m */, + ); + name = "Family History"; + sourceTree = ""; + }; + 519CE82F2C6585DF003BB584 /* Family History */ = { + isa = PBXGroup; + children = ( + 519CE83A2C65862E003BB584 /* Family History TableView Views */, + 519CE8302C658617003BB584 /* ORKFamilyHistoryStepViewController.h */, + 519CE8332C658617003BB584 /* ORKFamilyHistoryStepViewController.m */, + 519CE85B2C6AD858003BB584 /* ORKFamilyHistoryStepViewController_Private.h */, + ); + path = "Family History"; + sourceTree = ""; + }; + 519CE83A2C65862E003BB584 /* Family History TableView Views */ = { + isa = PBXGroup; + children = ( + 519CE83B2C658653003BB584 /* ORKFamilyHistoryRelatedPersonCell.h */, + 519CE83C2C658653003BB584 /* ORKFamilyHistoryRelatedPersonCell.m */, + 519CE83D2C658653003BB584 /* ORKFamilyHistoryTableFooterView.h */, + 519CE83E2C658654003BB584 /* ORKFamilyHistoryTableFooterView.m */, + 519CE83F2C658654003BB584 /* ORKFamilyHistoryTableHeaderView.h */, + 519CE8402C658654003BB584 /* ORKFamilyHistoryTableHeaderView.m */, + ); + path = "Family History TableView Views"; + sourceTree = ""; + }; + 519CE85D2C6BC1C5003BB584 /* Family History */ = { + isa = PBXGroup; + children = ( + 519CE85E2C6BC1DB003BB584 /* ORKFamilyHistoryResultTests.swift */, + ); + name = "Family History"; + sourceTree = ""; + }; 51A11F1B2BD094A00060C07E /* Custom View */ = { isa = PBXGroup; children = ( @@ -2689,6 +2841,7 @@ 0B0852752BD872D800149963 /* PrivacyInfo.xcprivacy */, 5D06633024FEF272005D9B40 /* Info.plist */, 51FBAC582BC86E6E009CA28F /* ResearchKitUI_Private.modulemap */, + 51B76DFC2CB5C73C0061698A /* ResearchKitUI.docc */, ); path = ResearchKitUI; sourceTree = ""; @@ -2773,6 +2926,7 @@ 86CC8EA61AC09383001CCD89 /* ResearchKitTests */ = { isa = PBXGroup; children = ( + 519CE85D2C6BC1C5003BB584 /* Family History */, 51EB9A592B8D1CB80064A515 /* FormatterTests */, 51AF19512B583B5B00D3B399 /* ORKESerializationTests */, 51CB80D42AFEBE4800A1F410 /* ConditionalFormItems */, @@ -2858,6 +3012,7 @@ B12EFF341AB2111200A80147 /* Step */ = { isa = PBXGroup; children = ( + 519CE8152C6582A0003BB584 /* Family History */, 86C40BB91A8D7C5C00081FAC /* ORKStep.h */, 86C40BBA1A8D7C5C00081FAC /* ORKStep.m */, 86C40BBB1A8D7C5C00081FAC /* ORKStep_Private.h */, @@ -3187,6 +3342,8 @@ CA1C7A69288B1093004DAB3A /* Choice Format Helpers */ = { isa = PBXGroup; children = ( + 5192BF9C2AE1DE62006E43FB /* ORKColorChoiceCellGroup.h */, + 5192BF9B2AE1DE62006E43FB /* ORKColorChoiceCellGroup.m */, 861D11B31AA7D073003C98A7 /* ORKTextChoiceCellGroup.h */, 861D11B41AA7D073003C98A7 /* ORKTextChoiceCellGroup.m */, ); @@ -3196,6 +3353,8 @@ CA1C7A6A288B1243004DAB3A /* Control Views */ = { isa = PBXGroup; children = ( + 5192BF8D2AE1C051006E43FB /* ORKAgePicker.h */, + 5192BF8E2AE1C051006E43FB /* ORKAgePicker.m */, BC1C032A1CA301E300869355 /* ORKHeightPicker.h */, BC1C032B1CA301E300869355 /* ORKHeightPicker.m */, 1B4B95B91F5F014E006B629F /* ORKWeightPicker.h */, @@ -3332,6 +3491,8 @@ isa = PBXGroup; children = ( 51FBAC5C2BC9CE4A009CA28F /* ORKBorderedButton_Internal.h */, + 5192BF912AE1C2F7006E43FB /* ORKChoiceViewCell+ORKColorChoice.h */, + 5192BF922AE1C2F7006E43FB /* ORKChoiceViewCell+ORKColorChoice.m */, 0B8444632A79C25000292DEA /* ORKChoiceViewCell+ORKTextChoice.h */, 0B8444642A79C25000292DEA /* ORKChoiceViewCell+ORKTextChoice.m */, 86C40B6B1A8D7C5B00081FAC /* ORKBodyLabel.h */, @@ -3407,6 +3568,7 @@ CA1C7A73288B1ADD004DAB3A /* Step */ = { isa = PBXGroup; children = ( + 519CE82F2C6585DF003BB584 /* Family History */, CA08053F28AD7CC8001695EF /* ORKViewControllerProviding.h */, CA08054328AD7EBA001695EF /* ORKStep+ResearchKitUI.m */, BAD65E4F2284F266008A5DCE /* ORKStepView_Private.h */, @@ -3808,6 +3970,7 @@ CAD089A7289DDEA8007B2A98 /* Walking */, 51FBAC7D2BC9EBA7009CA28F /* Info.plist */, 0B0852772BD872EA00149963 /* PrivacyInfo.xcprivacy */, + 51B76DFA2CB5B5A30061698A /* ResearchKitActiveTask.docc */, ); path = ResearchKitActiveTask; sourceTree = ""; @@ -3894,6 +4057,7 @@ children = ( 5192BF7E2AE1A9BA006E43FB /* volume_curve_AIRPODSPROV2.plist */, 5192BF7C2AE1A87D006E43FB /* retspl_AIRPODSPROV2.plist */, + 6227743F2C5CF7DC00F2E741 /* retspl_dBFS_AIRPODSPROV2.plist */, 5192BF5C2AE19036006E43FB /* frequency_dBSPL_AIRPODSPROV2.plist */, 71769E2F2088260B00A19914 /* ORKdBHLToneAudiometryOnboardingStepViewController.h */, 71769E302088260B00A19914 /* ORKdBHLToneAudiometryOnboardingStepViewController.m */, @@ -4482,6 +4646,7 @@ CA2B900928A17AD90025B773 /* ORKActiveStep_Internal.h in Headers */, BF5161501BE9C53D00174DDD /* ORKWaitStep.h in Headers */, 244EFAD21BCEFD83001850D9 /* ORKAnswerFormat_Private.h in Headers */, + 519CE8252C6582BE003BB584 /* ORKFamilyHistoryResult.h in Headers */, BCA5C0351AEC05F20092AC8D /* ORKStepNavigationRule.h in Headers */, 5192BF592AE09794006E43FB /* ORKFormItemVisibilityRule.h in Headers */, 5192BF872AE1BA47006E43FB /* ORKFrontFacingCameraStepResult.h in Headers */, @@ -4504,20 +4669,20 @@ 866DA5201D63D04700C9AF3F /* ORKCollector.h in Headers */, 51A11F1A2BD08D5E0060C07E /* CMMotionActivity+ORKJSONDictionary.h in Headers */, BF91559B1BDE8D7D007FA459 /* ORKReviewStep_Internal.h in Headers */, + FF5CA61B1D2C6453001660A3 /* ORKSignatureStep.h in Headers */, + 00C2668E23022CD400337E0B /* ORKCustomStep.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 */, - 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 */, 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 */, @@ -4539,8 +4704,19 @@ 86C40E181A8D7C5C00081FAC /* ORKConsentSection.h in Headers */, 866DA5251D63D04700C9AF3F /* ORKHealthSampleQueryOperation.h in Headers */, 51AF1B152B67F30500D3B399 /* ORKSignatureFormatter.h in Headers */, + 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 */, @@ -4561,27 +4737,21 @@ 2433C9E31B9A506F0052D375 /* ORKKeychainWrapper.h in Headers */, B11C54991A9EEF8800265E61 /* ORKConsentSharingStep.h in Headers */, 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 */, CA6A0D83288B5B370048C1EF /* ORKHTMLPDFWriter.h in Headers */, 86C40DC61A8D7C5C00081FAC /* ORKTask.h in Headers */, 86C40E1E1A8D7C5C00081FAC /* ORKConsentSignature.h in Headers */, @@ -4596,6 +4766,7 @@ 24BC5CEE1BC345D900846B43 /* ORKLoginStep.h in Headers */, BABBB1AE2097D97200CB29E5 /* ORKPDFViewerStep.h in Headers */, BA8C5021226FFB04001896D0 /* ORKLearnMoreItem.h in Headers */, + 519CE8242C6582BE003BB584 /* ORKHealthCondition.h in Headers */, 2489F7B11D65214D008DEF20 /* ORKVideoCaptureStep.h in Headers */, CA2B902328A186A80025B773 /* ORKDataLogger.h in Headers */, 861D11AD1AA7951F003C98A7 /* ORKChoiceAnswerFormatHelper.h in Headers */, @@ -4609,6 +4780,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 519CE8412C658654003BB584 /* ORKFamilyHistoryRelatedPersonCell.h in Headers */, CAA20E5B288B3E9900EDC764 /* UIImage+ResearchKit.h in Headers */, 51AEAAB52B744BC000F4D107 /* ORKQuestionStepView.h in Headers */, CAA20DB2288B3DB300EDC764 /* ORKTextFieldView.h in Headers */, @@ -4619,6 +4791,8 @@ CA6A0D81288B54650048C1EF /* ORKConsentReviewController.h in Headers */, CAA20F34288B3F6700EDC764 /* ORKVerificationStepViewController.h in Headers */, CAA20E1F288B3E8200EDC764 /* ORKPasscodeStepViewController.h in Headers */, + 519CE85C2C6AD858003BB584 /* ORKFamilyHistoryStepViewController_Private.h in Headers */, + 519CE8452C658654003BB584 /* ORKFamilyHistoryTableHeaderView.h in Headers */, CAA20E39288B3E8200EDC764 /* ORKFormStepViewController.h in Headers */, 51FBAC5D2BC9CE4A009CA28F /* ORKBorderedButton_Internal.h in Headers */, CAA20E37288B3E8200EDC764 /* ORKRequestPermissionsStepViewController.h in Headers */, @@ -4627,6 +4801,7 @@ CAA20F3E288B3F6D00EDC764 /* ORKAccessibilityFunctions.h in Headers */, CAA20D80288B3D9100EDC764 /* ORKFormSectionTitleLabel.h in Headers */, CAA20D9A288B3D9C00EDC764 /* ORKTableContainerView.h in Headers */, + 519CE8432C658654003BB584 /* ORKFamilyHistoryTableFooterView.h in Headers */, CAA20DFF288B3E8100EDC764 /* ORKCustomSignatureFooterView.h in Headers */, CAA20DAF288B3DB300EDC764 /* ORKDefaultFont.h in Headers */, CAA20F2A288B3F5B00EDC764 /* ORKConsentSharingStepViewController.h in Headers */, @@ -4672,6 +4847,7 @@ CAA20E42288B3E8200EDC764 /* ORKVideoCaptureView.h in Headers */, CAA20D50288B3D6400EDC764 /* ORKCheckmarkView.h in Headers */, CAA20D94288B3D9C00EDC764 /* ORKNavigationContainerView.h in Headers */, + 5192BF932AE1C2F8006E43FB /* ORKChoiceViewCell+ORKColorChoice.h in Headers */, CAA20DBD288B3DB300EDC764 /* ORKTableViewCell.h in Headers */, 51AEAAB32B744ACC00F4D107 /* ORKQuestionStepViewController_Private.h in Headers */, CAA20E05288B3E8200EDC764 /* ORKVideoInstructionStepViewController.h in Headers */, @@ -4703,6 +4879,7 @@ CAA20DD5288B3DB400EDC764 /* ORKTitleLabel.h in Headers */, CAA20D9D288B3D9C00EDC764 /* ORKVerticalContainerView.h in Headers */, CAA20DD8288B3DB400EDC764 /* ORKBodyLabel.h in Headers */, + 5192BF9E2AE1DE62006E43FB /* ORKColorChoiceCellGroup.h in Headers */, CAA20D6B288B3D9100EDC764 /* ORKSurveyAnswerCellForScale.h in Headers */, CAA20DC1288B3DB400EDC764 /* ORKCountdownLabel.h in Headers */, CAA20E28288B3E8200EDC764 /* ORKImageCaptureStepViewController.h in Headers */, @@ -4714,6 +4891,7 @@ CAA20D7C288B3D9100EDC764 /* ORKSurveyAnswerCellForSES.h in Headers */, CAA20F3B288B3F6D00EDC764 /* UIView+ORKAccessibility.h in Headers */, CAA20D61288B3D9100EDC764 /* ORKScaleSlider.h in Headers */, + E454438D2CC1C78E0004E385 /* ResearchKit_Prefix.pch in Headers */, CAA20DEF288B3DB400EDC764 /* ORKCaption1Label.h in Headers */, CAA20F31288B3F5B00EDC764 /* ORKConsentReviewStepViewController.h in Headers */, CAA20DC8288B3DB400EDC764 /* ORKRoundTappingButton.h in Headers */, @@ -4721,6 +4899,7 @@ CAA20D55288B3D6B00EDC764 /* ORKReviewIncompleteCell.h in Headers */, CAA20D63288B3D9100EDC764 /* ORKMultipleValuePicker.h in Headers */, CAA20E13288B3E8200EDC764 /* ORKWebViewStepViewController.h in Headers */, + 5192BF8F2AE1C051006E43FB /* ORKAgePicker.h in Headers */, CAA20E24288B3E8200EDC764 /* ORKCompletionStepViewController.h in Headers */, 51F716D1297E2CC400D8ACF7 /* ORKConsentLearnMoreViewController.h in Headers */, CAA20E5E288B3E9900EDC764 /* UIBarButtonItem+ORKBarButtonItem.h in Headers */, @@ -4739,6 +4918,7 @@ CAA20E22288B3E8200EDC764 /* ORKSignatureStepViewController.h in Headers */, CAA20DE7288B3DB400EDC764 /* ORKScaleRangeLabel.h in Headers */, CAA20DEA288B3DB400EDC764 /* ORKTagLabel.h in Headers */, + 519CE8352C658617003BB584 /* ORKFamilyHistoryStepViewController.h in Headers */, CAA20E29288B3E8200EDC764 /* ORKCustomStepView.h in Headers */, CAA20E31288B3E8200EDC764 /* ORKStepViewController.h in Headers */, CAA20DB1288B3DB300EDC764 /* ORKTapCountLabel.h in Headers */, @@ -5081,7 +5261,7 @@ }; }; buildConfigurationList = 3FFF18381829DB1D00167070 /* Build configuration list for PBXProject "ResearchKit" */; - compatibilityVersion = "Xcode 3.2"; + compatibilityVersion = "Xcode 15.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( @@ -5136,6 +5316,7 @@ 86CC8E991AC09332001CCD89 /* ResearchKitTests */, CA1C7A40288B0C68004DAB3A /* ResearchKitUI */, CAD08966289DD747007B2A98 /* ResearchKitActiveTask */, + 0BC672D82BD9C52D005798AC /* ResearchKitAllTargets */, ); }; /* End PBXProject section */ @@ -5197,6 +5378,7 @@ 51BD8FD429D60FB60001D54E /* volume_curve_WIRED.plist in Resources */, 0BD9B6132B75992F00A64EF9 /* Sentence7.wav in Resources */, 51BD8FD229D60FB60001D54E /* frequency_dBSPL_AIRPODSV3.plist in Resources */, + 622774402C5CF7DC00F2E741 /* retspl_dBFS_AIRPODSPROV2.plist in Resources */, 0BD9B6102B75992500A64EF9 /* Sentence4.wav in Resources */, 51BD8FC929D60FB60001D54E /* frequency_dBSPL_AIRPODSMAX.plist in Resources */, 0BD9B60E2B75991F00A64EF9 /* Sentence2.wav in Resources */, @@ -5269,6 +5451,7 @@ 0B9A990F2A8AC47500D64C40 /* NSObject+TestingSupport.m in Sources */, BC4D521F27B326EA0099DC18 /* ORKSecureCodingTests.swift in Sources */, 86D348021AC161B0006DB02B /* ORKRecorderTests.m in Sources */, + 519CE85F2C6BC1DB003BB584 /* ORKFamilyHistoryResultTests.swift in Sources */, 1490DCFC224D4867003FEEDA /* ORKEnvironmentSPLMeterResultTests.swift in Sources */, 51CB80D62AFEBE7E00A1F410 /* ORKFormStepViewControllerConditionalFormItemsTests.swift in Sources */, 51AF19582B583BBA00D3B399 /* ORKDataCollectionTests.m in Sources */, @@ -5311,6 +5494,7 @@ BF1D43861D4904C6007EE90B /* ORKVideoInstructionStep.m in Sources */, FF919A6E1E81D3B0005C2A1E /* ORKPasscodeResult.m in Sources */, BABBB1AF2097D97200CB29E5 /* ORKPDFViewerStep.m in Sources */, + 519CE82D2C6582BE003BB584 /* ORKFamilyHistoryResult.m in Sources */, 51AF1B162B67F30500D3B399 /* ORKSignatureFormatter.m in Sources */, FF919A641E81D04D005C2A1E /* ORKSignatureResult.m in Sources */, 8419D66F1FB73CC80088D7E5 /* ORKWebViewStep.m in Sources */, @@ -5320,6 +5504,7 @@ 5192BF862AE1BA47006E43FB /* ORKFrontFacingCameraStep.m in Sources */, 86C40D321A8D7C5C00081FAC /* ORKHealthAnswerFormat.m in Sources */, 03EDD58124CA6B1D006245E9 /* ORKNotificationPermissionType.m in Sources */, + 519CE8262C6582BE003BB584 /* ORKRelatedPerson.m in Sources */, 51E03D6424919711008F8406 /* ORKPermissionType.m in Sources */, FFDDD84A1D3555EA00446806 /* ORKPageStep.m in Sources */, 51EB9A542B8408D50064A515 /* ORKInstructionStepHTMLFormatter.m in Sources */, @@ -5337,26 +5522,30 @@ 86C40E001A8D7C5C00081FAC /* ORKConsentDocument.m in Sources */, D442397A1AF17F5100559D96 /* ORKImageCaptureStep.m in Sources */, 866DA5261D63D04700C9AF3F /* ORKHealthSampleQueryOperation.m in Sources */, + 519CE8222C6582BE003BB584 /* ORKHealthCondition.m in Sources */, CA6A0D86288B5B370048C1EF /* ORKHTMLPDFWriter.m in Sources */, FF5CA6131D2C2670001660A3 /* ORKTableStep.m in Sources */, 036B1E8E25351BAD008483DF /* ORKMotionActivityPermissionType.m in Sources */, CA2B902228A1867E0025B773 /* ORKRecorder.m in Sources */, 5D04885825F19A7A0006C68B /* ORKDevice.m in Sources */, CA2B902428A186AF0025B773 /* ORKDataLogger.m in Sources */, + 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 */, 86C40CE61A8D7C5C00081FAC /* ORKAnswerFormat.m in Sources */, CA6A0D7F288B51D30048C1EF /* ORKSkin.m in Sources */, @@ -5374,6 +5563,7 @@ 51A11F192BD08D5E0060C07E /* HKSample+ORKJSONDictionary.m in Sources */, 86C40D581A8D7C5C00081FAC /* ORKOrderedTask.m in Sources */, 86C40D601A8D7C5C00081FAC /* ORKQuestionStep.m in Sources */, + BCB080A11B83EFB900A3F400 /* ORKStepNavigationRule.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -5381,6 +5571,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 5192BF902AE1C051006E43FB /* ORKAgePicker.m in Sources */, CAA20D9E288B3D9C00EDC764 /* ORKVerticalContainerView.m in Sources */, CAA20E43288B3E8200EDC764 /* ORKVideoCaptureView.m in Sources */, CAA20D7E288B3D9100EDC764 /* ORKPicker.m in Sources */, @@ -5396,6 +5587,7 @@ CAA20E5C288B3E9900EDC764 /* UIImage+ResearchKit.m in Sources */, CAA20DB0288B3DB300EDC764 /* ORKSelectionSubTitleLabel.m in Sources */, CAA20E49288B3E8200EDC764 /* ORKLearnMoreStepViewController.m in Sources */, + 51B76DFD2CB5C73C0061698A /* ResearchKitUI.docc in Sources */, CAA20E2F288B3E8200EDC764 /* ORKWaitStepViewController.m in Sources */, CAA20DFC288B3E8100EDC764 /* ORKFreehandDrawingView.m in Sources */, CAA20D69288B3D9100EDC764 /* ORKMultipleValuePicker.m in Sources */, @@ -5415,7 +5607,9 @@ CAA20DA9288B3DA300EDC764 /* ORKObserver.m in Sources */, CA6A0D82288B54650048C1EF /* ORKConsentReviewController.m in Sources */, CAA20D8A288B3D9100EDC764 /* ORKScaleSliderView.m in Sources */, + 519CE8462C658654003BB584 /* ORKFamilyHistoryTableHeaderView.m in Sources */, CAA20F36288B3F6700EDC764 /* ORKLoginStepViewController.m in Sources */, + 519CE8422C658654003BB584 /* ORKFamilyHistoryRelatedPersonCell.m in Sources */, CAA20D4F288B3D5700EDC764 /* ORKRecordButton.m in Sources */, CAA20DE0288B3DB400EDC764 /* ORKCountdownLabel.m in Sources */, CAA20D7F288B3D9100EDC764 /* SwiftUIViewFactory.swift in Sources */, @@ -5461,10 +5655,12 @@ CAA20DB9288B3DB300EDC764 /* ORKLabel.m in Sources */, CAA20DD0288B3DB400EDC764 /* ORKSelectionTitleLabel.m in Sources */, CAA20DDA288B3DB400EDC764 /* ORKTableViewCell.m in Sources */, + 519CE8442C658654003BB584 /* ORKFamilyHistoryTableFooterView.m in Sources */, CAA20DBE288B3DB300EDC764 /* ORKFootnoteLabel.m in Sources */, CAA20F2E288B3F5B00EDC764 /* ORKConsentReviewStepViewController.m in Sources */, 51F716D3297E2FB600D8ACF7 /* ORKConsentLearnMoreViewController.m in Sources */, CAA20D90288B3D9100EDC764 /* ORKTextChoiceCellGroup.m in Sources */, + 5192BF9D2AE1DE62006E43FB /* ORKColorChoiceCellGroup.m in Sources */, CAA20D51288B3D6400EDC764 /* ORKCheckmarkView.m in Sources */, CAA20E20288B3E8200EDC764 /* ORKSignatureStepViewController.m in Sources */, CAA20DE2288B3DB400EDC764 /* ORKRoundTappingButton.m in Sources */, @@ -5479,6 +5675,7 @@ CAA20E12288B3E8200EDC764 /* ORKPasscodeStepView.m in Sources */, CAA20DC2288B3DB400EDC764 /* ORKIconButton.m in Sources */, CAA20D6D288B3D9100EDC764 /* ORKSurveyCardHeaderView.m in Sources */, + 5192BF942AE1C2F8006E43FB /* ORKChoiceViewCell+ORKColorChoice.m in Sources */, CAA20F30288B3F5B00EDC764 /* ORKConsentSharingStepViewController.m in Sources */, CAA20DC0288B3DB300EDC764 /* ORKImageChoiceLabel.m in Sources */, CAA20DD7288B3DB400EDC764 /* ORKChoiceViewCell.m in Sources */, @@ -5498,7 +5695,6 @@ CAA20F3A288B3F6D00EDC764 /* ORKAccessibilityFunctions.m in Sources */, 5192BF982AE1C5A1006E43FB /* ORKAnswerFormat+FormStepViewControllerAdditions.m in Sources */, CAA20E01288B3E8100EDC764 /* ORKRequestPermissionsStepContainerView.m in Sources */, - 51A11F242BD1548C0060C07E /* UIImageView+ResearchKit.m in Sources */, CAA20F38288B3F6700EDC764 /* ORKVerificationStepView.m in Sources */, CAA20D6C288B3D9100EDC764 /* ORKSurveyAnswerCellForScale.m in Sources */, CAA20E2A288B3E8200EDC764 /* ORKStepView.m in Sources */, @@ -5511,6 +5707,7 @@ CAA20E03288B3E8100EDC764 /* ORKImageCaptureStepViewController.m in Sources */, CAA20D77288B3D9100EDC764 /* ORKDateTimePicker.m in Sources */, CAA20D74288B3D9100EDC764 /* ORKImageSelectionView.m in Sources */, + 519CE8382C658617003BB584 /* ORKFamilyHistoryStepViewController.m in Sources */, CAA20E4B288B3E8200EDC764 /* ORKSecondaryTaskStepViewController.m in Sources */, CAA20D64288B3D9100EDC764 /* ORKTimeIntervalPicker.m in Sources */, CAA20E14288B3E8200EDC764 /* ORKInstructionStepContainerView.m in Sources */, @@ -5650,6 +5847,7 @@ CA2B8F9B28A16EF90025B773 /* ORKAmslerGridStepViewController.m in Sources */, 5156C9F32B7E437A00983535 /* ORKTouchAbilityTapTrial.m in Sources */, CAD08A99289DE7A4007B2A98 /* ORKTrailmakingResult.m in Sources */, + 51B76DFB2CB5B5A30061698A /* ResearchKitActiveTask.docc in Sources */, CA2B8F8D28A16E2E0025B773 /* ORKEnvironmentSPLMeterContentView.m in Sources */, 5156C9F62B7E437A00983535 /* ORKTouchAbilityTapStep.m in Sources */, CA2B8FD628A176D70025B773 /* ORKReactionTimeStimulusView.m in Sources */, @@ -5709,6 +5907,21 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 0BC672E02BD9C541005798AC /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = B183A4731A8535D100C76870 /* ResearchKit */; + targetProxy = 0BC672DF2BD9C541005798AC /* PBXContainerItemProxy */; + }; + 0BC672E22BD9C541005798AC /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = CAD08966289DD747007B2A98 /* ResearchKitActiveTask */; + targetProxy = 0BC672E12BD9C541005798AC /* PBXContainerItemProxy */; + }; + 0BC672E62BD9C541005798AC /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = CA1C7A40288B0C68004DAB3A /* ResearchKitUI */; + targetProxy = 0BC672E52BD9C541005798AC /* PBXContainerItemProxy */; + }; 86CC8EA21AC09332001CCD89 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = B183A4731A8535D100C76870 /* ResearchKit */; @@ -5811,11 +6024,29 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 0BC672DA2BD9C52D005798AC /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 0BC672DC2BD9C52D005798AC /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; 3FFF18691829DB1E00167070 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 5D000ED12620F27100E5442A /* Project-Debug.xcconfig */; buildSettings = { - ENABLE_MODULE_VERIFIER = YES; + ENABLE_MODULE_VERIFIER = NO; ENABLE_TESTING_SEARCH_PATHS = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; IPHONEOS_DEPLOYMENT_TARGET = 17.0; @@ -5883,6 +6114,7 @@ "$(ORK_GCC_PREPROCESSOR_DEFINITIONS)", ); MODULEMAP_PRIVATE_FILE = ResearchKit/ResearchKit_Private.modulemap; + "OTHER_SWIFT_FLAGS[arch=*]" = "$(inherited) -runtime-compatibility-version none"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -5911,6 +6143,7 @@ APPLICATION_EXTENSION_API_ONLY = NO; ENABLE_MODULE_VERIFIER = YES; ENABLE_TESTING_SEARCH_PATHS = YES; + GCC_PREFIX_HEADER = ResearchKit/ResearchKit_Prefix.pch; MODULEMAP_PRIVATE_FILE = ResearchKitUI/ResearchKitUI_Private.modulemap; MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++14"; "OTHER_SWIFT_FLAGS[arch=*]" = "$(inherited) -runtime-compatibility-version none"; @@ -5926,6 +6159,7 @@ APPLICATION_EXTENSION_API_ONLY = NO; ENABLE_MODULE_VERIFIER = YES; ENABLE_TESTING_SEARCH_PATHS = YES; + GCC_PREFIX_HEADER = ResearchKit/ResearchKit_Prefix.pch; MODULEMAP_PRIVATE_FILE = ResearchKitUI/ResearchKitUI_Private.modulemap; MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++14"; PRODUCT_NAME = ResearchKitUI; @@ -6083,6 +6317,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 0BC672D92BD9C52D005798AC /* Build configuration list for PBXAggregateTarget "ResearchKitAllTargets" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0BC672DA2BD9C52D005798AC /* Debug */, + 0BC672DC2BD9C52D005798AC /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; 3FFF18381829DB1D00167070 /* Build configuration list for PBXProject "ResearchKit" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -6090,7 +6333,7 @@ 3FFF186A1829DB1E00167070 /* Release */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = Debug; + defaultConfigurationName = Release; }; 86CC8EA31AC09332001CCD89 /* Build configuration list for PBXNativeTarget "ResearchKitTests" */ = { isa = XCConfigurationList; @@ -6099,7 +6342,7 @@ 86CC8EA51AC09332001CCD89 /* Release */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = Debug; + defaultConfigurationName = Release; }; B183A5921A8535D100C76870 /* Build configuration list for PBXNativeTarget "ResearchKit" */ = { isa = XCConfigurationList; @@ -6108,7 +6351,7 @@ B183A5941A8535D100C76870 /* Release */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = Debug; + defaultConfigurationName = Release; }; CA1C7A54288B0C68004DAB3A /* Build configuration list for PBXNativeTarget "ResearchKitUI" */ = { isa = XCConfigurationList; @@ -6117,7 +6360,7 @@ CA1C7A57288B0C68004DAB3A /* Release */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = Debug; + defaultConfigurationName = Release; }; CAD08970289DD747007B2A98 /* Build configuration list for PBXNativeTarget "ResearchKitActiveTask" */ = { isa = XCConfigurationList; @@ -6126,7 +6369,7 @@ CAD0896D289DD747007B2A98 /* Release */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = Debug; + defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; diff --git a/ResearchKit.xcodeproj/xcshareddata/xcschemes/ResearchKitActiveTask.xcscheme b/ResearchKit.xcodeproj/xcshareddata/xcschemes/ResearchKitActiveTask.xcscheme index 9645cc75d5..84edff00a6 100644 --- a/ResearchKit.xcodeproj/xcshareddata/xcschemes/ResearchKitActiveTask.xcscheme +++ b/ResearchKit.xcodeproj/xcshareddata/xcschemes/ResearchKitActiveTask.xcscheme @@ -39,6 +39,41 @@ BlueprintName = "ResearchKitTests" ReferencedContainer = "container:ResearchKit.xcodeproj"> + + + + + + + + + + + + + + + + + + + + + + + + @@ -77,4 +112,7 @@ buildConfiguration = "Release" revealArchiveInOrganizer = "YES"> + + diff --git a/ResearchKit.xcodeproj/xcshareddata/xcschemes/ResearchKitAllTargets.xcscheme b/ResearchKit.xcodeproj/xcshareddata/xcschemes/ResearchKitAllTargets.xcscheme new file mode 100644 index 0000000000..bfad3c4fd5 --- /dev/null +++ b/ResearchKit.xcodeproj/xcshareddata/xcschemes/ResearchKitAllTargets.xcscheme @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ResearchKit.xctestplan b/ResearchKit.xctestplan index e64d2a7f45..ca83daedf4 100644 --- a/ResearchKit.xctestplan +++ b/ResearchKit.xctestplan @@ -14,7 +14,11 @@ "testTargets" : [ { "skippedTests" : [ - "ORKDataCollectionTests" + "ORKDataCollectionTests", + "ORKJSONSerializationTests\/testEquality", + "ORKJSONSerializationTests\/testORKSampleDeserialization", + "ORKJSONSerializationTests\/testORKSerialization", + "ORKJSONSerializationTests\/testSecureCoding" ], "target" : { "containerPath" : "container:ResearchKit.xcodeproj", diff --git a/ResearchKit/Common/CLLocationManager+ResearchKit.h b/ResearchKit/Common/CLLocationManager+ResearchKit.h index e0c2ef9088..6a52ede9e6 100644 --- a/ResearchKit/Common/CLLocationManager+ResearchKit.h +++ b/ResearchKit/Common/CLLocationManager+ResearchKit.h @@ -28,6 +28,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION #import NS_ASSUME_NONNULL_BEGIN @@ -52,3 +53,4 @@ NS_ASSUME_NONNULL_BEGIN @end NS_ASSUME_NONNULL_END +#endif diff --git a/ResearchKit/Common/CLLocationManager+ResearchKit.m b/ResearchKit/Common/CLLocationManager+ResearchKit.m index 5f1fff6911..8de3408820 100644 --- a/ResearchKit/Common/CLLocationManager+ResearchKit.m +++ b/ResearchKit/Common/CLLocationManager+ResearchKit.m @@ -28,6 +28,7 @@ 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. */ +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION #import "CLLocationManager+ResearchKit.h" #import "ORKDefines.h" @@ -35,37 +36,22 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE @implementation CLLocationManager (ResearchKit) - (BOOL)ork_requestWhenInUseAuthorization { -#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION [self requestWhenInUseAuthorization]; return YES; -#else - return NO; -#endif } - (BOOL)ork_requestAlwaysAuthorization { -#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION [self requestAlwaysAuthorization]; return YES; -#else - return NO; -#endif } - (void)ork_startUpdatingLocation { -#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION [self startUpdatingLocation]; -#else - // noop -#endif } - (void)ork_stopUpdatingLocation { -#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION [self stopUpdatingLocation]; -#else - // noop -#endif } @end +#endif diff --git a/ResearchKit/Common/HKSample+ORKJSONDictionary.h b/ResearchKit/Common/HKSample+ORKJSONDictionary.h index 3008a62f12..fce4a30fe4 100644 --- a/ResearchKit/Common/HKSample+ORKJSONDictionary.h +++ b/ResearchKit/Common/HKSample+ORKJSONDictionary.h @@ -29,8 +29,10 @@ */ +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION #import + NS_ASSUME_NONNULL_BEGIN typedef NS_OPTIONS(NSInteger, ORKSampleJSONOptions) { @@ -59,3 +61,5 @@ typedef NS_OPTIONS(NSInteger, ORKSampleJSONOptions) { @end NS_ASSUME_NONNULL_END + +#endif diff --git a/ResearchKit/Common/HKSample+ORKJSONDictionary.m b/ResearchKit/Common/HKSample+ORKJSONDictionary.m index 0edddbac73..2a20e511b3 100644 --- a/ResearchKit/Common/HKSample+ORKJSONDictionary.m +++ b/ResearchKit/Common/HKSample+ORKJSONDictionary.m @@ -45,7 +45,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE static NSString *const HKCorrelatedObjectsKey = @"objects"; // static NSString *const HKSourceIdentifierKey = @"sourceBundleIdentifier"; - +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION @implementation HKSample (ORKJSONDictionary) - (NSMutableDictionary *)ork_JSONMutableDictionaryWithOptions:(ORKSampleJSONOptions)options unit:(HKUnit *)unit { @@ -167,3 +167,4 @@ - (NSDictionary *)ork_JSONDictionaryWithOptions:(ORKSampleJSONOptions)options sa } @end +#endif // ORK_FEATURE_HEALTHKIT_AUTHORIZATION diff --git a/ResearchKit/Common/ORKActiveStep.h b/ResearchKit/Common/ORKActiveStep.h index 5c82691e69..a51c0594e7 100644 --- a/ResearchKit/Common/ORKActiveStep.h +++ b/ResearchKit/Common/ORKActiveStep.h @@ -30,7 +30,6 @@ #import -#import #import diff --git a/ResearchKit/Common/ORKActiveStep.m b/ResearchKit/Common/ORKActiveStep.m index 05e1704d80..a58171b85e 100644 --- a/ResearchKit/Common/ORKActiveStep.m +++ b/ResearchKit/Common/ORKActiveStep.m @@ -164,6 +164,7 @@ - (BOOL)isEqual:(id)object { (self.shouldUseNextAsSkipButton == castObject.shouldUseNextAsSkipButton)); } +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION - (NSSet *)requestedHealthKitTypesForReading { NSMutableSet *set = [NSMutableSet set]; for (ORKRecorderConfiguration *config in self.recorderConfigurations) { @@ -174,6 +175,7 @@ - (BOOL)isEqual:(id)object { } return set; } +#endif - (ORKPermissionMask)requestedPermissions { ORKPermissionMask mask = [super requestedPermissions]; diff --git a/ResearchKit/Common/ORKAnswerFormat.h b/ResearchKit/Common/ORKAnswerFormat.h index d0eef709ec..9d9773c8f0 100644 --- a/ResearchKit/Common/ORKAnswerFormat.h +++ b/ResearchKit/Common/ORKAnswerFormat.h @@ -32,7 +32,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - #import @@ -56,11 +55,12 @@ @class ORKLocationAnswerFormat; @class ORKSESAnswerFormat; @class ORKImageChoice; -#endif NS_ASSUME_NONNULL_BEGIN @class ORKBooleanAnswerFormat; +@class ORKColorChoiceAnswerFormat; +@class ORKColorChoice; @class ORKTextChoiceAnswerFormat; @class ORKTextChoice; @@ -123,6 +123,8 @@ ORK_CLASS_AVAILABLE + (ORKTextChoiceAnswerFormat *)choiceAnswerFormatWithStyle:(ORKChoiceAnswerStyle)style textChoices:(NSArray *)textChoices; ++ (ORKColorChoiceAnswerFormat *)choiceAnswerFormatWithStyle:(ORKChoiceAnswerStyle)style + colorChoices:(NSArray *)colorChoices; /// @name Validation @@ -189,9 +191,29 @@ ORK_CLASS_AVAILABLE */ @property (copy, readonly) NSArray *textChoices; +/** + Returns YES if the answer is no longer valid, specifically used in the ORKFormStep Restoration + */ +- (BOOL)isAnswerInvalid:(id)answer; + @end +ORK_CLASS_AVAILABLE +@interface ORKColorChoiceAnswerFormat : ORKAnswerFormat + ++ (instancetype)new NS_UNAVAILABLE; +- (instancetype)init NS_UNAVAILABLE; + +- (instancetype)initWithStyle:(ORKChoiceAnswerStyle)style + colorChoices:(NSArray *)colorChoices NS_DESIGNATED_INITIALIZER; + +@property (readonly) ORKChoiceAnswerStyle style; + +@property (copy, readonly) NSArray *colorChoices; + +@end + /** The `ORKBooleanAnswerFormat` class behaves the same as the `ORKTextChoiceAnswerFormat` class, @@ -386,9 +408,34 @@ ORK_CLASS_AVAILABLE @end +ORK_CLASS_AVAILABLE +@interface ORKColorChoice: NSObject + ++ (instancetype)new NS_UNAVAILABLE; +- (instancetype)init NS_UNAVAILABLE; + +- (instancetype)initWithColor:(nullable UIColor *)color + text:(nullable NSString *)text + detailText:(nullable NSString *)detailText + value:(NSObject *)value; + +- (instancetype)initWithColor:(nullable UIColor *)color + text:(nullable NSString *)text + detailText:(nullable NSString *)detailText + value:(NSObject *)value + exclusive:(BOOL)exclusive; + +@property (nonatomic, copy, readonly, nullable) UIColor *color; + +@property (nonatomic, copy, readonly, nullable) NSString *text; +@property (nonatomic, copy, readonly, nullable) NSString *detailText; -#pragma mark - iOS +@property (nonatomic, copy, readonly) NSObject *value; + +@property (readonly) BOOL exclusive; + +@end #if TARGET_OS_IOS || TARGET_OS_VISION @interface ORKAnswerFormat() @@ -480,8 +527,7 @@ ORK_CLASS_AVAILABLE minimumValue:(double)minimumValue maximumValue:(double)maximumValue defaultValue:(double)defaultValue; - -#if !TARGET_OS_VISION +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && TARGET_OS_IOS && !TARGET_OS_VISION + (ORKLocationAnswerFormat *)locationAnswerFormat; #endif @@ -1739,6 +1785,7 @@ This By default, the value of this property is `NO`. */ @property (copy, nullable) NSString *placeholder; +#if !TARGET_OS_WATCH /** The autocapitalization type that applies to the user's input. @@ -1781,6 +1828,7 @@ This By default, the value of this property is `NO`. If specified, overrides the default password generation rules for fields with secureTextEntry. */ @property (nonatomic, copy, nullable) UITextInputPasswordRules *passwordRules API_AVAILABLE(ios(12)); +#endif @end @@ -2028,6 +2076,134 @@ ORK_CLASS_AVAILABLE @end +/** + The `ORKAgeAnswerFormat` class represents the answer format for questions that require users + to enter a weight. + + A weight answer format produces an `ORKNumericQuestionResult` object. The result is always reported + in the metric system using the `kg` unit. + */ +ORK_CLASS_AVAILABLE +@interface ORKAgeAnswerFormat : ORKAnswerFormat + +/** + Returns an initialized weight answer format using the measurement system specified in the current + locale. + + @return An initialized weight answer format. + */ +- (instancetype)init; + +- (instancetype)initWithMinimumAge:(NSInteger)minimumAge + maximumAge:(NSInteger)maximumAge; + +- (instancetype)initWithMinimumAge:(NSInteger)minimumAge + maximumAge:(NSInteger)maximumAge + minimumAgeCustomText:(nullable NSString *)minimumAgeCustomText + maximumAgeCustomText:(nullable NSString *)maximumAgeCustomText + showYear:(BOOL)showYear + useYearForResult:(BOOL)useYearForResult + defaultValue:(NSInteger)defaultValue; + +- (instancetype)initWithMinimumAge:(NSInteger)minimumAge + maximumAge:(NSInteger)maximumAge + minimumAgeCustomText:(nullable NSString *)minimumAgeCustomText + maximumAgeCustomText:(nullable NSString *)maximumAgeCustomText + showYear:(BOOL)showYear + useYearForResult:(BOOL)useYearForResult + treatMinAgeAsRange:(BOOL)treatMinAgeAsRange + treatMaxAgeAsRange:(BOOL)treatMaxAgeAsRange + defaultValue:(NSInteger)defaultValue; + ++ (int)minimumAgeSentinelValue; ++ (int)maximumAgeSentinelValue; + +/** + Minimum age value presented in the picker + + By default, the value of this property is 0. + */ +@property (readonly) NSInteger minimumAge; + + +/** + Maximum age value presented in the picker. + + By default, the value of this property is 125. + */ +@property (readonly) NSInteger maximumAge; + + +/** + Custom text that will replace the minimumAge value. + + By default, the value of this property is nil. + */ + +@property (readonly, nullable) NSString *minimumAgeCustomText; + +/** + Custom text that will replace the maximumAge value. + + By default, the value of this property is nil. + */ + +@property (readonly, nullable) NSString *maximumAgeCustomText; + + +/** + Boolean that determines if the year should be shown alongside the age value. + + By default, the value of this property is nil. + */ + +@property (readonly) BOOL showYear; + +/** + The year at which the picker will base all of its ages from. + + By default, the value of this property will be the current year. + */ + +@property (nonatomic) NSInteger relativeYear; + + +/** + Boolean that determines if the year for the selected age should be used in the result. + + By default, the value of this property is NO. + */ + +@property (readonly) BOOL useYearForResult; + +/** + Boolean that determines if the minimumAge property should be treated as range. + + -1 will be returned if minimumAge is selected + + By default, the value of this property is NO. + */ + +@property (readonly) BOOL treatMinAgeAsRange; + +/** + Boolean that determines if the maximumAge property should be treated as range. + + -2 will be returned if maximumAge is selected + + By default, the value of this property is NO. + */ + +@property (readonly) BOOL treatMaxAgeAsRange; + +/** + The default value for the picker. + */ + +@property (readonly) NSInteger defaultValue; + +@end + /** The `ORKLocationAnswerFormat` class represents the answer format for questions that collect a location response @@ -2035,6 +2211,7 @@ ORK_CLASS_AVAILABLE An `ORKLocationAnswerFormat` object produces an `ORKLocationQuestionResult` object. */ +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && TARGET_OS_IOS ORK_CLASS_AVAILABLE @interface ORKLocationAnswerFormat : ORKAnswerFormat @@ -2053,6 +2230,7 @@ ORK_CLASS_AVAILABLE @property (copy, nullable) NSString *placeholder; @end +#endif /** Socio-Economic Ladder Answer Format. @@ -2069,6 +2247,4 @@ ORK_CLASS_AVAILABLE @end -#endif - NS_ASSUME_NONNULL_END diff --git a/ResearchKit/Common/ORKAnswerFormat.m b/ResearchKit/Common/ORKAnswerFormat.m index f22ce34c9f..627df4f8bf 100644 --- a/ResearchKit/Common/ORKAnswerFormat.m +++ b/ResearchKit/Common/ORKAnswerFormat.m @@ -43,8 +43,10 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE #import "ORKHealthAnswerFormat.h" #endif +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION +#import +#endif -@import HealthKit; @import MapKit; @import Contacts; @@ -80,6 +82,8 @@ BOOL ORKIsAnswerEmpty(id answer) { SQT_CASE(Weight); SQT_CASE(Location); SQT_CASE(SES); + SQT_CASE(Age); + SQT_CASE(Year); } #undef SQT_CASE } @@ -90,10 +94,12 @@ static NSNumberFormatterStyle ORKNumberFormattingStyleConvert(ORKNumberFormattin return style == ORKNumberFormattingStylePercent ? NSNumberFormatterPercentStyle : NSNumberFormatterDecimalStyle; } +#if TARGET_OS_IOS @implementation ORKAnswerDefaultSource { NSMutableDictionary *_unitsTable; } +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION @synthesize healthStore=_healthStore; + (instancetype)sourceWithHealthStore:(HKHealthStore *)healthStore { @@ -202,8 +208,10 @@ - (void)fetchDefaultValueForQuantityType:(HKQuantityType *)quantityType unit:(HK [healthStore executeQuery:sampleQuery]; }); } +#endif // ORK_FEATURE_HEALTHKIT_AUTHORIZATION - (void)fetchDefaultValueForAnswerFormat:(ORKAnswerFormat *)answerFormat handler:(void(^)(id defaultValue, NSError *error))handler { +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION HKObjectType *objectType = [answerFormat healthKitObjectType]; BOOL handled = NO; if (objectType) { @@ -224,8 +232,12 @@ - (void)fetchDefaultValueForAnswerFormat:(ORKAnswerFormat *)answerFormat handler if (!handled) { handler(nil, nil); } +#else + handler(nil, nil); +#endif } +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION - (HKUnit *)defaultHealthKitUnitForAnswerFormat:(ORKAnswerFormat *)answerFormat { __block HKUnit *unit = [answerFormat healthKitUnit]; HKObjectType *objectType = [answerFormat healthKitObjectType]; @@ -267,10 +279,11 @@ - (void)updateHealthKitUnitForAnswerFormat:(ORKAnswerFormat *)answerFormat force [answerFormat setHealthKitUserUnit:healthKitDefault]; } } +#endif // ORK_FEATURE_HEALTHKIT_AUTHORIZATION @end -#endif +#endif // TARGET_OS_IOS #pragma mark - ORKAnswerFormat @@ -411,7 +424,7 @@ + (ORKDateAnswerFormat *)dateAnswerFormatWithStyle:(ORKDateAnswerStyle)style calendar:calendar]; [answerFormat setDaysBeforeCurrentDateToSetMinimumDate:daysBefore]; [answerFormat setDaysAfterCurrentDateToSetMinimumDate:daysAfter]; - + return answerFormat; } @@ -476,6 +489,12 @@ + (ORKWeightAnswerFormat *)weightAnswerFormatWithMeasurementSystem:(ORKMeasureme defaultValue:defaultValue]; } +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && TARGET_OS_IOS ++ (ORKLocationAnswerFormat *)locationAnswerFormat { + return [ORKLocationAnswerFormat new]; +} +#endif + + (ORKSESAnswerFormat *)socioEconomicAnswerFormatWithTopRungText:(NSString *)topRungText bottomRungText:(NSString *)bottomRungText { return [[ORKSESAnswerFormat alloc] initWithTopRungText:topRungText @@ -505,6 +524,10 @@ + (ORKTextChoiceAnswerFormat *)choiceAnswerFormatWithStyle:(ORKChoiceAnswerStyle return [[ORKTextChoiceAnswerFormat alloc] initWithStyle:style textChoices:textChoices]; } ++ (ORKColorChoiceAnswerFormat *)choiceAnswerFormatWithStyle:(ORKChoiceAnswerStyle)style + colorChoices:(NSArray *)colorChoices { + return [[ORKColorChoiceAnswerFormat alloc] initWithStyle:style colorChoices:colorChoices]; +} - (void)validateParameters { } @@ -560,6 +583,7 @@ - (BOOL)isHealthKitAnswerFormat { return NO; } +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION - (HKObjectType *)healthKitObjectType { return nil; } @@ -579,6 +603,7 @@ - (HKUnit *)healthKitUserUnit { - (void)setHealthKitUserUnit:(HKUnit *)unit { } +#endif - (ORKQuestionType)questionType { return ORKQuestionTypeNone; @@ -825,7 +850,7 @@ - (id)copyWithZone:(NSZone *)zone { - (BOOL)isEqual:(id)object { BOOL isParentSame = [super isEqual:object]; - + __typeof(self) castObject = object; return (isParentSame && ORKEqualObjects(self.valuePickers, castObject.valuePickers)); @@ -874,7 +899,7 @@ - (NSString *)stringForAnswer:(id)answer { if (![answer isKindOfClass:[NSArray class]] || ([(NSArray*)answer count] != self.valuePickers.count)) { return nil; } - + NSArray *answers = (NSArray*)answer; __block NSMutableArray *answerTexts = [NSMutableArray new]; [answers enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { @@ -885,11 +910,11 @@ - (NSString *)stringForAnswer:(id)answer { *stop = YES; } }]; - + if (answerTexts.count != self.valuePickers.count) { return nil; } - + return [answerTexts componentsJoinedByString:self.separator]; } @@ -1011,8 +1036,6 @@ - (NSArray *)choices { } @end -#endif - #pragma mark - ORKTextChoiceAnswerFormat @@ -1110,9 +1133,117 @@ - (NSArray *)choices { return self.textChoices; } +- (BOOL)isAnswerInvalid:(id)answer { + @try + { + [_helper selectedIndexesForAnswer: answer]; + return NO; + } @catch(id anException) { + ORK_Log_Error("%@ exception thrown for isAnswerInvalid: for answer:%@", anException, answer); + return YES; + } +} + +@end + + +#pragma mark - ORKColorChoiceAnswerFormat + +@interface ORKColorChoiceAnswerFormat () { + + ORKChoiceAnswerFormatHelper *_helper; +} + @end +@implementation ORKColorChoiceAnswerFormat + ++ (instancetype)new { + ORKThrowMethodUnavailableException(); +} + +- (instancetype)init { + ORKThrowMethodUnavailableException(); +} + +- (instancetype)initWithStyle:(ORKChoiceAnswerStyle)style + colorChoices:(NSArray *)colorChoices { + self = [super init]; + if (self) { + _style = style; + _colorChoices = [colorChoices copy]; + _helper = [[ORKChoiceAnswerFormatHelper alloc] initWithAnswerFormat:self]; + } + return self; +} + +- (void)validateParameters { + [super validateParameters]; +} + +- (instancetype)copyWithZone:(NSZone *)zone { + ORKColorChoiceAnswerFormat *answerFormat = [[[self class] allocWithZone:zone] initWithStyle:_style + colorChoices:[_colorChoices copy]]; + return answerFormat; +} + +- (BOOL)isEqual:(id)object { + BOOL isParentSame = [super isEqual:object]; + + __typeof(self) castObject = object; + return (isParentSame && + ORKEqualObjects(_colorChoices, castObject.colorChoices) && + (_style == castObject.style)); +} + +- (NSUInteger)hash { + return super.hash ^ _colorChoices.hash ^ _style; +} + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wobjc-designated-initializers" +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + self = [super initWithCoder:aDecoder]; + if (self) { + ORK_DECODE_OBJ_ARRAY(aDecoder, colorChoices, ORKColorChoice); + ORK_DECODE_ENUM(aDecoder, style); + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [super encodeWithCoder:aCoder]; + ORK_ENCODE_OBJ(aCoder, colorChoices); + ORK_ENCODE_ENUM(aCoder, style); + +} + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (ORKQuestionType)questionType { + return (_style == ORKChoiceAnswerStyleSingleChoice) ? ORKQuestionTypeSingleChoice : ORKQuestionTypeMultipleChoice; +} + +- (Class)questionResultClass { + return [ORKChoiceQuestionResult class]; +} + +- (NSString *)stringForAnswer:(id)answer { + return [_helper stringForChoiceAnswer:answer]; +} + +- (BOOL)shouldShowDontKnowButton { + return NO; +} + +- (NSArray *)choices { + return self.colorChoices; +} + +@end #pragma mark - ORKTextChoice @@ -1274,6 +1405,93 @@ - (BOOL)shouldShowDontKnowButton { @end +#pragma mark - ORKColorChoice + +@implementation ORKColorChoice + ++ (instancetype)new { + ORKThrowMethodUnavailableException(); +} + +- (instancetype)init { + ORKThrowMethodUnavailableException(); +} + +- (instancetype)initWithColor:(UIColor *)color + text:(NSString *)text + detailText:(NSString *)detailText + value:(NSObject *)value + exclusive:(BOOL)exclusive { + self = [super init]; + + if (self) { + _color = [color copy]; + _text = [text copy]; + _detailText = [detailText copy]; + _value = [value copy]; + _exclusive = exclusive; + } + + return self; +} + +- (instancetype)initWithColor:(UIColor *)color + text:(NSString *)text + detailText:(NSString *)detailText + value:(NSObject *)value { + return [self initWithColor:color text:text detailText:detailText value:value exclusive:NO]; +} + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (instancetype)copyWithZone:(NSZone *)zone { + return self; +} + +- (BOOL)isEqual:(id)object { + if ([self class] != [object class]) { + return NO; + } + + __typeof(self) castObject = object; + return (ORKEqualObjects(self.text, castObject.text) + && ORKEqualObjects(self.detailText, castObject.detailText) + && ORKEqualObjects(self.value, castObject.value) + && ORKEqualObjects(self.color, castObject.color) + && self.exclusive == castObject.exclusive); +} + +- (NSUInteger)hash { + return _text.hash ^ _detailText.hash ^ _value.hash ^ _color.hash; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + self = [super init]; + if (self) { + ORK_DECODE_OBJ_CLASS(aDecoder, text, NSString); + ORK_DECODE_OBJ_CLASS(aDecoder, color, UIColor); + ORK_DECODE_OBJ_CLASS(aDecoder, detailText, NSString); + ORK_DECODE_OBJ_CLASSES(aDecoder, value, ORKAllowableValueClasses()); + ORK_DECODE_BOOL(aDecoder, exclusive); + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + ORK_ENCODE_OBJ(aCoder, text); + ORK_ENCODE_OBJ(aCoder, color); + ORK_ENCODE_OBJ(aCoder, detailText); + ORK_ENCODE_OBJ(aCoder, value); + ORK_ENCODE_BOOL(aCoder, exclusive); +} + +- (BOOL)shouldShowDontKnowButton { + return NO; +} + +@end #pragma mark - ORKTextChoiceOther @@ -1501,7 +1719,6 @@ - (BOOL)shouldShowDontKnowButton { } @end -#endif #pragma mark - ORKBooleanAnswerFormat @@ -2827,19 +3044,15 @@ - (NSArray *)choices { @end -#endif - #pragma mark - ORKTextAnswerFormat @interface ORKTextAnswerFormat() - @end @implementation ORKTextAnswerFormat - - (Class)questionResultClass { return [ORKTextQuestionResult class]; } @@ -2858,7 +3071,6 @@ - (void)commonInit { } - - (instancetype)initWithMaximumLength:(NSInteger)maximumLength { self = [super init]; if (self) { @@ -2920,6 +3132,7 @@ - (instancetype)copyWithZone:(NSZone *)zone { answerFormat->_spellCheckingType = _spellCheckingType; answerFormat->_keyboardType = _keyboardType; answerFormat->_textContentType = _textContentType; + if (@available(iOS 12.0, *)) { answerFormat->_passwordRules = _passwordRules; } @@ -2996,6 +3209,7 @@ - (ORKAnswerFormat *)confirmationAnswerFormatWithOriginalItemIdentifier:(NSStrin answerFormat->_keyboardType = _keyboardType; answerFormat->_autocapitalizationType = _autocapitalizationType; answerFormat->_textContentType = _textContentType; + if (@available(iOS 12.0, *)) { answerFormat->_passwordRules = _passwordRules; } @@ -3108,7 +3322,6 @@ - (NSString *)stringForAnswer:(id)answer { return answerString; } - - (ORKQuestionResult *)resultWithIdentifier:(NSString *)identifier answer:(id)answer { ORKQuestionResult *questionResult = nil; questionResult = (ORKQuestionResult *)[super resultWithIdentifier:identifier answer:answer]; @@ -3575,7 +3788,237 @@ - (NSString *)stringForAnswer:(id)answer { @end -#if !TARGET_OS_VISION +#pragma mark - ORKAgeAnswerFormat + +static const NSInteger ORKAgeAnswerDefaultMinAge = 1; +static const NSInteger ORKAgeAnswerDefaultMaxAge = 125; + +@implementation ORKAgeAnswerFormat + +- (Class)questionResultClass { + return [ORKNumericQuestionResult class]; +} + +- (instancetype)init { + + return [self initWithMinimumAge:ORKAgeAnswerDefaultMinAge + maximumAge:ORKAgeAnswerDefaultMaxAge + minimumAgeCustomText:nil + maximumAgeCustomText:nil + showYear:NO + useYearForResult:NO + defaultValue:ORKAgeAnswerDefaultMinAge]; +} + +- (instancetype)initWithMinimumAge:(NSInteger)minimumAge maximumAge:(NSInteger)maximumAge { + return [self initWithMinimumAge:minimumAge + maximumAge:maximumAge + minimumAgeCustomText:nil + maximumAgeCustomText:nil + showYear:NO + useYearForResult:NO + defaultValue:minimumAge]; + +} + +- (instancetype)initWithMinimumAge:(NSInteger)minimumAge + maximumAge:(NSInteger)maximumAge + minimumAgeCustomText:(nullable NSString *)minimumAgeCustomText + maximumAgeCustomText:(nullable NSString *)maximumAgeCustomText + showYear:(BOOL)showYear + useYearForResult:(BOOL)useYearForResult + defaultValue:(NSInteger)defaultValue { + return [self initWithMinimumAge:minimumAge + maximumAge:maximumAge + minimumAgeCustomText:minimumAgeCustomText + maximumAgeCustomText:maximumAgeCustomText + showYear:showYear + useYearForResult:useYearForResult + treatMinAgeAsRange:NO + treatMaxAgeAsRange:NO + defaultValue:defaultValue]; +} + +- (instancetype)initWithMinimumAge:(NSInteger)minimumAge + maximumAge:(NSInteger)maximumAge + minimumAgeCustomText:(nullable NSString *)minimumAgeCustomText + maximumAgeCustomText:(nullable NSString *)maximumAgeCustomText + showYear:(BOOL)showYear + useYearForResult:(BOOL)useYearForResult + treatMinAgeAsRange:(BOOL)treatMinAgeAsRange + treatMaxAgeAsRange:(BOOL)treatMaxAgeAsRange + defaultValue:(NSInteger)defaultValue { + if (minimumAge < 0) { + @throw [NSException exceptionWithName:NSInvalidArgumentException + reason: [NSString stringWithFormat:@"minimumAge must be greater than 0. (%li) - (%li)", minimumAge, maximumAge] + userInfo:nil]; + + } + + if (maximumAge > 150) { + @throw [NSException exceptionWithName:NSInvalidArgumentException + reason:@"maximumAge must be lower than 150." + userInfo:nil]; + } + + if (minimumAge >= maximumAge) { + @throw [NSException exceptionWithName:NSInvalidArgumentException + reason:[NSString stringWithFormat:@"minimumAge must be less than maximumAge. (%li) - (%li)", minimumAge, maximumAge] + userInfo:nil]; + } + + + self = [super init]; + + if (self) { + _minimumAge = minimumAge; + _maximumAge = maximumAge; + _minimumAgeCustomText = [minimumAgeCustomText copy]; + _maximumAgeCustomText = [maximumAgeCustomText copy]; + _showYear = showYear; + _useYearForResult = useYearForResult; + _treatMinAgeAsRange = treatMinAgeAsRange; + _treatMaxAgeAsRange = treatMaxAgeAsRange; + _relativeYear = [self currentYear]; + _defaultValue = defaultValue; + } + + return self; +} + ++ (int)minimumAgeSentinelValue { + return -1; +} + ++ (int)maximumAgeSentinelValue { + return -2; +} + +- (NSInteger)currentYear { + NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; + NSDateComponents *components = [calendar components:NSCalendarUnitYear fromDate:[NSDate date]]; + return [components year]; +} + +- (NSString *)stringForAnswer:(id)answer { + NSString *answerString = nil; + + if (!ORKIsAnswerEmpty(answer)) { + NSNumberFormatter *formatter = ORKDecimalNumberFormatter(); + answerString = [formatter stringFromNumber:(NSNumber *)answer]; + } + + if (answerString) { + NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; + NSDateComponents *components = [calendar components:NSCalendarUnitYear fromDate:[NSDate date]]; + NSInteger currentYear = [components year]; + + int maxYear = currentYear - _minimumAge; + int minYear = currentYear - _maximumAge; + + NSString *minAgeText = _minimumAgeCustomText ? : [NSString stringWithFormat:ORKLocalizedString(@"AGEPICKER_OR_YOUNGER", ""), (long)_minimumAge]; + NSString *maxAgeText = _maximumAgeCustomText ? : [NSString stringWithFormat:ORKLocalizedString(@"AGEPICKER_OR_OLDER", ""), (long)_maximumAge]; + + int value = [answerString intValue]; + + if (value < 0) { + // pass back necessary text if sentinel value is selected + answerString = value == [ORKAgeAnswerFormat minimumAgeSentinelValue] ? minAgeText : maxAgeText; + } else if (_useYearForResult) { + // pass back necessary text if min or max year is selected + if ((value >= maxYear || value <= minYear)) { + answerString = value <= minYear ? maxAgeText : minAgeText; + } + } else if ((value == _minimumAge && _minimumAgeCustomText ) || (value == _maximumAge && _maximumAgeCustomText)) { + answerString = value == _minimumAge ? _minimumAgeCustomText : _maximumAgeCustomText; + } else if ((value == minYear && _minimumAgeCustomText) || (value == maxYear && _maximumAgeCustomText)) { + answerString = value == minYear ? _minimumAgeCustomText : _maximumAgeCustomText; + } + } + + return answerString; +} + +- (ORKQuestionType)questionType { + return _useYearForResult ? ORKQuestionTypeYear : ORKQuestionTypeAge; +} + + +- (instancetype)copyWithZone:(NSZone *)zone { + ORKAgeAnswerFormat *ageAnswerFormat = [super copyWithZone:zone]; + ageAnswerFormat->_minimumAge = _minimumAge; + ageAnswerFormat->_maximumAge = _maximumAge; + ageAnswerFormat->_minimumAgeCustomText = [_minimumAgeCustomText copy]; + ageAnswerFormat->_maximumAgeCustomText = [_maximumAgeCustomText copy]; + ageAnswerFormat->_showYear = _showYear; + ageAnswerFormat->_useYearForResult = _useYearForResult; + ageAnswerFormat->_treatMinAgeAsRange = _treatMinAgeAsRange; + ageAnswerFormat->_treatMaxAgeAsRange = _treatMaxAgeAsRange; + ageAnswerFormat->_relativeYear = _relativeYear; + ageAnswerFormat->_defaultValue = _defaultValue; + return ageAnswerFormat; +} + +- (BOOL)isEqual:(id)object { + BOOL isParentSame = [super isEqual:object]; + + __typeof(self) castObject = object; + return (isParentSame && + (_minimumAge == castObject->_minimumAge) && + (_maximumAge == castObject->_maximumAge) && + (_relativeYear == castObject->_relativeYear) && + ORKEqualObjects(_minimumAgeCustomText, castObject->_minimumAgeCustomText) && + ORKEqualObjects(_maximumAgeCustomText, castObject->_maximumAgeCustomText) && + (_showYear == castObject->_showYear) && + (_useYearForResult == castObject->_useYearForResult) && + (_treatMinAgeAsRange == castObject->_treatMinAgeAsRange) && + (_treatMaxAgeAsRange == castObject->_treatMaxAgeAsRange) && + (_defaultValue == castObject->_defaultValue)); +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + self = [super initWithCoder:aDecoder]; + if (self) { + ORK_DECODE_INTEGER(aDecoder, minimumAge); + ORK_DECODE_INTEGER(aDecoder, maximumAge); + ORK_DECODE_OBJ_CLASS(aDecoder, minimumAgeCustomText, NSString); + ORK_DECODE_OBJ_CLASS(aDecoder, maximumAgeCustomText, NSString); + ORK_DECODE_BOOL(aDecoder, showYear); + ORK_DECODE_BOOL(aDecoder, useYearForResult); + ORK_DECODE_BOOL(aDecoder, treatMinAgeAsRange); + ORK_DECODE_BOOL(aDecoder, treatMaxAgeAsRange); + ORK_DECODE_BOOL(aDecoder, useYearForResult); + ORK_DECODE_INTEGER(aDecoder, defaultValue); + ORK_DECODE_INTEGER(aDecoder, relativeYear); + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [super encodeWithCoder:aCoder]; + ORK_ENCODE_INTEGER(aCoder, minimumAge); + ORK_ENCODE_INTEGER(aCoder, maximumAge); + ORK_ENCODE_OBJ(aCoder, minimumAgeCustomText); + ORK_ENCODE_OBJ(aCoder, maximumAgeCustomText); + ORK_ENCODE_BOOL(aCoder, showYear); + ORK_ENCODE_BOOL(aCoder, useYearForResult); + ORK_ENCODE_BOOL(aCoder, treatMinAgeAsRange); + ORK_ENCODE_BOOL(aCoder, treatMaxAgeAsRange); + ORK_ENCODE_INTEGER(aCoder, defaultValue); + ORK_ENCODE_INTEGER(aCoder, relativeYear); +} + +- (NSUInteger)hash { + return super.hash ^ _minimumAgeCustomText.hash ^ _maximumAgeCustomText.hash ^ _minimumAge ^ _maximumAge ^ _showYear ^ _useYearForResult ^ _treatMinAgeAsRange ^ _treatMaxAgeAsRange ^ _relativeYear ^ _defaultValue; +} + ++ (BOOL)supportsSecureCoding { + return YES; +} +@end + + +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && TARGET_OS_IOS && !TARGET_OS_VISION #pragma mark - ORKLocationAnswerFormat @implementation ORKLocationAnswerFormat @@ -3709,4 +4152,3 @@ - (NSString *)stringForAnswer:(id)answer { } @end -#endif diff --git a/ResearchKit/Common/ORKAnswerFormat_Internal.h b/ResearchKit/Common/ORKAnswerFormat_Internal.h index ffd4a71b9f..dfdca08194 100644 --- a/ResearchKit/Common/ORKAnswerFormat_Internal.h +++ b/ResearchKit/Common/ORKAnswerFormat_Internal.h @@ -29,11 +29,15 @@ */ +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION #import +#endif + #if TARGET_OS_IOS || TARGET_OS_VISION + #import #import -#endif + @class ORKChoiceAnswerFormatHelper; NS_ASSUME_NONNULL_BEGIN @@ -41,9 +45,11 @@ NS_ASSUME_NONNULL_BEGIN BOOL ORKIsAnswerEmpty(_Nullable id answer); #if TARGET_OS_IOS || TARGET_OS_VISION +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION NSString *ORKHKBiologicalSexString(HKBiologicalSex biologicalSex); NSString *ORKHKBloodTypeString(HKBloodType bloodType); -#endif +#endif // ORK_FEATURE_HEALTHKIT_AUTHORIZATION +#endif // TARGET_OS_IOS NSString *ORKQuestionTypeString(ORKQuestionType questionType); // Need to mark these as designated initializers to avoid warnings once we designate the others. @@ -69,6 +75,7 @@ ORK_DESIGNATE_CODING_AND_SERIALIZATION_INITIALIZERS(ORKTextAnswerFormat) ORK_DESIGNATE_CODING_AND_SERIALIZATION_INITIALIZERS(ORKTimeIntervalAnswerFormat) ORK_DESIGNATE_CODING_AND_SERIALIZATION_INITIALIZERS(ORKHeightAnswerFormat) ORK_DESIGNATE_CODING_AND_SERIALIZATION_INITIALIZERS(ORKWeightAnswerFormat) +ORK_DESIGNATE_CODING_AND_SERIALIZATION_INITIALIZERS(ORKAgeAnswerFormat) #endif ORK_DESIGNATE_CODING_AND_SERIALIZATION_INITIALIZERS(ORKTextChoiceAnswerFormat) ORK_DESIGNATE_CODING_AND_SERIALIZATION_INITIALIZERS(ORKTextChoice) @@ -83,13 +90,15 @@ ORK_DESIGNATE_CODING_AND_SERIALIZATION_INITIALIZERS(ORKTextChoice) #if TARGET_OS_IOS || TARGET_OS_VISION - (BOOL)isHealthKitAnswerFormat; +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION - (nullable HKObjectType *)healthKitObjectType; - (nullable HKObjectType *)healthKitObjectTypeForAuthorization; @property (nonatomic, strong, readonly, nullable) HKUnit *healthKitUnit; @property (nonatomic, strong, nullable) HKUnit *healthKitUserUnit; -#endif +#endif // ORK_FEATURE_HEALTHKIT_AUTHORIZATION +#endif // TARGET_OS_IOS - (nullable NSString *)localizedInvalidValueStringWithAnswerString:(nullable NSString *)text; @@ -127,6 +136,11 @@ ORK_DESIGNATE_CODING_AND_SERIALIZATION_INITIALIZERS(ORKTextChoice) @end +@interface ORKDateAnswerFormat () { + NSDate *_currentDateOverride; +} +@end + #if TARGET_OS_IOS || TARGET_OS_VISION @protocol ORKScaleAnswerFormatProvider @@ -190,6 +204,7 @@ NSArray *ORKAllowableValueClasses(void); @end #if TARGET_OS_IOS || TARGET_OS_VISION + @interface ORKValuePickerAnswerFormat () - (instancetype)initWithTextChoices:(NSArray *)textChoices nullChoice:(ORKTextChoice *)nullChoice NS_DESIGNATED_INITIALIZER; @@ -198,7 +213,6 @@ NSArray *ORKAllowableValueClasses(void); @end - @interface ORKImageChoice () @end @@ -210,11 +224,7 @@ NSArray *ORKAllowableValueClasses(void); @end - -@interface ORKDateAnswerFormat () { - NSDate *_currentDateOverride; -} - +@interface ORKDateAnswerFormat () - (NSDate *)pickerDefaultDate; - (nullable NSDate *)pickerMinimumDate; - (nullable NSDate *)pickerMaximumDate; @@ -254,16 +264,16 @@ NSArray *ORKAllowableValueClasses(void); @interface ORKAnswerDefaultSource : NSObject +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION + (instancetype)sourceWithHealthStore:(HKHealthStore *)healthStore; - (instancetype)initWithHealthStore:(HKHealthStore *)healthStore NS_DESIGNATED_INITIALIZER; @property (nonatomic, strong, readonly, nullable) HKHealthStore *healthStore; - -- (void)fetchDefaultValueForAnswerFormat:(nullable ORKAnswerFormat *)answerFormat handler:(void(^)(id defaultValue, NSError *error))handler; - - (nullable HKUnit *)defaultHealthKitUnitForAnswerFormat:(ORKAnswerFormat *)answerFormat; - (void)updateHealthKitUnitForAnswerFormat:(ORKAnswerFormat *)answerFormat force:(BOOL)force; +#endif +- (void)fetchDefaultValueForAnswerFormat:(nullable ORKAnswerFormat *)answerFormat handler:(void(^)(id defaultValue, NSError *error))handler; @end @interface ORKTextChoiceOther() diff --git a/ResearchKit/Common/ORKAnswerFormat_Private.h b/ResearchKit/Common/ORKAnswerFormat_Private.h index 69b8d57d8e..a6bd847408 100644 --- a/ResearchKit/Common/ORKAnswerFormat_Private.h +++ b/ResearchKit/Common/ORKAnswerFormat_Private.h @@ -32,8 +32,6 @@ #if TARGET_OS_IOS || TARGET_OS_VISION #import -#endif - NS_ASSUME_NONNULL_BEGIN @@ -95,7 +93,6 @@ ORK_CLASS_AVAILABLE @property (nonatomic, copy, readonly) NSString *errorMessage; @end -#endif @protocol ORKAnswerFormatPlatterPresentable diff --git a/ResearchKit/Common/ORKBodyItem.m b/ResearchKit/Common/ORKBodyItem.m index 14244d010b..4ae57909bf 100644 --- a/ResearchKit/Common/ORKBodyItem.m +++ b/ResearchKit/Common/ORKBodyItem.m @@ -30,16 +30,20 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE #import "ORKBodyItem.h" -#import "ORKBodyItem_Internal.h" #import "ORKLearnMoreInstructionStep.h" #import "ORKHelpers_Internal.h" +#if !TARGET_OS_WATCH +#import "ORKBodyItem_Internal.h" +#endif + @implementation ORKBodyItem { // For our internal custom button type BOOL _isCustomButtonType; } +#if !TARGET_OS_WATCH - (instancetype)initWithCustomButtonConfigurationHandler:(void(^)(UIButton *button))configurationHandler { self = [super init]; @@ -50,6 +54,7 @@ - (instancetype)initWithCustomButtonConfigurationHandler:(void(^)(UIButton *butt } return self; } +#endif - (BOOL)isCustomButtonItemType { diff --git a/ResearchKit/Common/ORKBodyItem_Internal.h b/ResearchKit/Common/ORKBodyItem_Internal.h index a1dd41cc3c..6102d6ee31 100644 --- a/ResearchKit/Common/ORKBodyItem_Internal.h +++ b/ResearchKit/Common/ORKBodyItem_Internal.h @@ -34,9 +34,11 @@ NS_ASSUME_NONNULL_BEGIN @interface ORKBodyItem () +#if !TARGET_OS_WATCH @property (nonatomic, copy, nullable) void (^customButtonConfigurationHandler)(UIButton *button); - (instancetype)initWithCustomButtonConfigurationHandler:(void(^)(UIButton *button))configurationHandler; +#endif - (BOOL)isCustomButtonItemType; diff --git a/ResearchKit/Common/ORKChoiceAnswerFormatHelper.h b/ResearchKit/Common/ORKChoiceAnswerFormatHelper.h index 0bd4fa4fff..1acb7f2688 100644 --- a/ResearchKit/Common/ORKChoiceAnswerFormatHelper.h +++ b/ResearchKit/Common/ORKChoiceAnswerFormatHelper.h @@ -35,7 +35,7 @@ NS_ASSUME_NONNULL_BEGIN @class ORKAnswerFormat; @class ORKImageChoice; @class ORKTextChoice; - +@class ORKColorChoice; @protocol ORKAnswerOption; @@ -74,6 +74,7 @@ NS_ASSUME_NONNULL_BEGIN - (nullable ORKImageChoice *)imageChoiceAtIndex:(NSUInteger)index; +- (nullable ORKColorChoice *)colorChoiceAtIndex:(NSUInteger)index; @end #endif diff --git a/ResearchKit/Common/ORKChoiceAnswerFormatHelper.m b/ResearchKit/Common/ORKChoiceAnswerFormatHelper.m index e3d2da16d1..7d5be677f3 100644 --- a/ResearchKit/Common/ORKChoiceAnswerFormatHelper.m +++ b/ResearchKit/Common/ORKChoiceAnswerFormatHelper.m @@ -69,7 +69,10 @@ - (ORKImageChoice *)imageChoiceAtIndex:(NSUInteger)index { return option && [option isKindOfClass:[ORKImageChoice class]] ? (ORKImageChoice *) option : nil; } - +- (ORKColorChoice *)colorChoiceAtIndex:(NSUInteger)index { + id option = [self answerOptionAtIndex:index]; + return option && [option isKindOfClass:[ORKColorChoice class]] ? (ORKColorChoice *) option : nil; +} #endif - (ORKTextChoice *)textChoiceAtIndex:(NSUInteger)index { @@ -139,6 +142,8 @@ - (NSArray *)selectedIndexesForAnswer:(nullable id)answer { for (id answerValue in (NSArray *)answer) { id matchedChoice = nil; + BOOL isTextChoiceOtherResult = [self _isTextChoiceOtherResult:answerValue choices:_choices]; + for ( id choice in _choices) { #if TARGET_OS_IOS || TARGET_OS_VISION if ([choice isKindOfClass:[ORKTextChoiceOther class]]) { @@ -149,7 +154,12 @@ - (NSArray *)selectedIndexesForAnswer:(nullable id)answer { } else if (textChoiceOther.textViewInputOptional && textChoiceOther.textViewText.length <= 0 && [textChoiceOther.value isEqual:answerValue]) { matchedChoice = choice; break; + } else if (isTextChoiceOtherResult) { + textChoiceOther.textViewText = answerValue; + matchedChoice = choice; + break; } + } else if ([choice.value isEqual:answerValue]) { matchedChoice = choice; break; @@ -192,6 +202,20 @@ - (NSArray *)selectedIndexesForAnswer:(nullable id)answer { } +- (BOOL)_isTextChoiceOtherResult:(id)answerValue choices:(NSArray *)choices { + if (answerValue == nil) { + return NO; + } + + for (id choice in _choices) { + if ([choice.value isEqual:answerValue]){ + return NO; + } + } + + return YES; +} + - (NSString *)stringForChoiceAnswer:(id)answer { NSMutableArray *answerStrings = [[NSMutableArray alloc] init]; NSArray *indexes = [self selectedIndexesForAnswer:answer]; diff --git a/ResearchKit/Common/ORKCollectionResult.h b/ResearchKit/Common/ORKCollectionResult.h index 580ea7c94d..1d9a03ddff 100644 --- a/ResearchKit/Common/ORKCollectionResult.h +++ b/ResearchKit/Common/ORKCollectionResult.h @@ -30,8 +30,6 @@ #if TARGET_OS_IOS || TARGET_OS_VISION #import -#endif - NS_ASSUME_NONNULL_BEGIN diff --git a/ResearchKit/Common/ORKCollectionResult_Private.h b/ResearchKit/Common/ORKCollectionResult_Private.h index 840f8b396a..2a7e788e7a 100644 --- a/ResearchKit/Common/ORKCollectionResult_Private.h +++ b/ResearchKit/Common/ORKCollectionResult_Private.h @@ -33,7 +33,6 @@ #if TARGET_OS_IOS || TARGET_OS_VISION #import -#endif NS_ASSUME_NONNULL_BEGIN diff --git a/ResearchKit/Common/ORKCollector.h b/ResearchKit/Common/ORKCollector.h index daa58c85ed..22659a5701 100644 --- a/ResearchKit/Common/ORKCollector.h +++ b/ResearchKit/Common/ORKCollector.h @@ -32,9 +32,11 @@ #import -#import #import +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION +#import +#endif NS_ASSUME_NONNULL_BEGIN @@ -87,6 +89,8 @@ ORK_CLASS_AVAILABLE It cannot be initiated directly. Use `addHealthCollectorWithSampleType:`to add one to a `ORKDataCollectionManager`. */ + +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION ORK_CLASS_AVAILABLE @interface ORKHealthCollector : ORKCollector @@ -148,7 +152,7 @@ ORK_CLASS_AVAILABLE @property (copy, readonly) HKQueryAnchor *lastAnchor; @end - +#endif /** An object that collects CMMotionActivity. diff --git a/ResearchKit/Common/ORKCollector.m b/ResearchKit/Common/ORKCollector.m index a9a05b0308..b496f891c4 100644 --- a/ResearchKit/Common/ORKCollector.m +++ b/ResearchKit/Common/ORKCollector.m @@ -114,7 +114,7 @@ - (BOOL)isEqual:(id)object { @end - +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION @implementation ORKHealthCollector : ORKCollector - (instancetype)initWithSampleType:(HKSampleType*)sampleType unit:(HKUnit*)unit startDate:(NSDate*)startDate { @@ -290,7 +290,7 @@ - (BOOL)isEqual:(id)object { } @end - +#endif @implementation ORKMotionActivityCollector : ORKCollector diff --git a/ResearchKit/Common/ORKCollector_Internal.h b/ResearchKit/Common/ORKCollector_Internal.h index 7ade798130..d6ad5dce4f 100644 --- a/ResearchKit/Common/ORKCollector_Internal.h +++ b/ResearchKit/Common/ORKCollector_Internal.h @@ -45,7 +45,7 @@ @end - +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION @protocol ORKHealthCollectable - (HKSampleType *)sampleType; @@ -73,7 +73,7 @@ @property (copy) HKQueryAnchor *lastAnchor; @end - +#endif @interface ORKMotionActivityCollector() diff --git a/ResearchKit/Common/ORKCompletionStep.h b/ResearchKit/Common/ORKCompletionStep.h index e81cdbf5ac..b382eac100 100644 --- a/ResearchKit/Common/ORKCompletionStep.h +++ b/ResearchKit/Common/ORKCompletionStep.h @@ -30,7 +30,7 @@ #import - +#import #if TARGET_OS_IOS || TARGET_OS_VISION #import diff --git a/ResearchKit/Common/ORKConditionStepConfiguration.h b/ResearchKit/Common/ORKConditionStepConfiguration.h new file mode 100644 index 0000000000..070244a901 --- /dev/null +++ b/ResearchKit/Common/ORKConditionStepConfiguration.h @@ -0,0 +1,74 @@ +/* + Copyright (c) 2023, 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 + +#import + +@class ORKTaskResult; +@class ORKHealthCondition; +@class ORKFormItem; + +NS_ASSUME_NONNULL_BEGIN + +/** + The base object for configuring the displayed health conditions + for the family history step. + */ + +ORK_CLASS_AVAILABLE +@interface ORKConditionStepConfiguration : NSObject + ++ (instancetype)new NS_UNAVAILABLE; +- (instancetype)init NS_UNAVAILABLE; + +/** + Creates a new conditions step configuration. + + This method is the primary designated initializer. + + @param stepIdentifier The identifier attached to the form step that displays the health conditions. + @param conditionsFormItemIdentifier The identifier attached to the form item used from the health conditions list. + @param conditions List of health conditions displayed to participants. + @param formItems List of form items that are presented below the health conditions text choice. This is optional. + */ +- (instancetype)initWithStepIdentifier:(NSString *)stepIdentifier + conditionsFormItemIdentifier:(NSString *)conditionsFormItemIdentifier + conditions:(NSArray *)conditions + formItems:(NSArray *)formItems NS_DESIGNATED_INITIALIZER; + +@property (nonatomic, readonly, copy) NSString *stepIdentifier; +@property (nonatomic, readonly, copy) NSString *conditionsFormItemIdentifier; +@property (nonatomic, readonly, copy) NSArray *conditions; +@property (nonatomic, copy) NSArray *formItems; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ResearchKit/Common/ORKConditionStepConfiguration.m b/ResearchKit/Common/ORKConditionStepConfiguration.m new file mode 100644 index 0000000000..a79ff2a43f --- /dev/null +++ b/ResearchKit/Common/ORKConditionStepConfiguration.m @@ -0,0 +1,107 @@ +/* + Copyright (c) 2023, 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 "ORKConditionStepConfiguration.h" + +#import "ORKCollectionResult.h" +#import "ORKFormStep.h" +#import "ORKHealthCondition.h" +#import "ORKHelpers_Internal.h" + + +@implementation ORKConditionStepConfiguration + +- (instancetype)initWithStepIdentifier:(NSString *)stepIdentifier + conditionsFormItemIdentifier:(NSString *)conditionsFormItemIdentifier + conditions:(NSArray *)conditions + formItems:(nonnull NSArray *)formItems { + self = [super init]; + + if (self) { + _stepIdentifier = [stepIdentifier copy]; + _conditionsFormItemIdentifier = [conditionsFormItemIdentifier copy]; + _conditions = [conditions copy]; + _formItems = [formItems copy]; + } + + return self; +} + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + ORK_ENCODE_OBJ(aCoder, stepIdentifier); + ORK_ENCODE_OBJ(aCoder, conditionsFormItemIdentifier); + ORK_ENCODE_OBJ(aCoder, conditions); + ORK_ENCODE_OBJ(aCoder, formItems); +} + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wobjc-designated-initializers" +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + self = [super init]; + if (self) { + ORK_DECODE_OBJ_CLASS(aDecoder, stepIdentifier, NSString); + ORK_DECODE_OBJ_CLASS(aDecoder, conditionsFormItemIdentifier, NSString); + ORK_DECODE_OBJ_ARRAY(aDecoder, conditions, ORKHealthCondition); + ORK_DECODE_OBJ_ARRAY(aDecoder, formItems, ORKFormItem); + } + return self; +} + +- (instancetype)copyWithZone:(nullable NSZone *)zone { + ORKConditionStepConfiguration *conditionStepConfiguration = [[[self class] alloc] init]; + conditionStepConfiguration->_stepIdentifier = [_stepIdentifier copy]; + conditionStepConfiguration->_conditionsFormItemIdentifier = [_conditionsFormItemIdentifier copy]; + conditionStepConfiguration->_conditions = [_conditions copy]; + conditionStepConfiguration->_formItems = [_formItems copy]; + + return conditionStepConfiguration; +} + +- (BOOL)isEqual:(id)object { + if ([self class] != [object class]) { + return NO; + } + + __typeof(self) castObject = object; + return (ORKEqualObjects(_stepIdentifier, castObject->_stepIdentifier) + && ORKEqualObjects(_conditionsFormItemIdentifier, castObject->_conditionsFormItemIdentifier) + && ORKEqualObjects(_conditions, castObject->_conditions) + && ORKEqualObjects(_formItems, castObject->_formItems)); +} + +- (NSUInteger)hash { + return super.hash ^ _stepIdentifier.hash ^ _conditionsFormItemIdentifier.hash ^ _conditions.hash ^ _formItems.hash; +} + +@end diff --git a/ResearchKit/Common/ORKDataCollectionManager.h b/ResearchKit/Common/ORKDataCollectionManager.h index 8a424816cd..212c579df0 100644 --- a/ResearchKit/Common/ORKDataCollectionManager.h +++ b/ResearchKit/Common/ORKDataCollectionManager.h @@ -28,8 +28,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#if TARGET_OS_IOS - +#if TARGET_OS_IOS && !TARGET_OS_VISION #import #import @@ -62,6 +61,7 @@ NS_ASSUME_NONNULL_BEGIN If NO is returned or this method is not implemented, the manager will stop the collection for the collector and repeat this same collection next time, until the data is accepted. */ +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION - (BOOL)healthCollector:(ORKHealthCollector *)collector didCollectSamples:(NSArray *)samples; /** @@ -75,7 +75,7 @@ NS_ASSUME_NONNULL_BEGIN until the data is accepted. */ - (BOOL)healthCorrelationCollector:(ORKHealthCorrelationCollector *)collector didCollectCorrelations:(NSArray *)correlations; - +#endif /** Method for delivering the collected motion activities. @@ -147,6 +147,7 @@ ORK_CLASS_AVAILABLE @return Initiated health collector. */ +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION - (ORKHealthCollector *)addHealthCollectorWithSampleType:(HKSampleType *)sampleType unit:(HKUnit *)unit startDate:(NSDate *)startDate @@ -168,7 +169,7 @@ ORK_CLASS_AVAILABLE units:(NSArray *)units startDate:(NSDate *)startDate error:(NSError * _Nullable *)error; - +#endif /** Add a collector for motion activity. diff --git a/ResearchKit/Common/ORKDataCollectionManager.m b/ResearchKit/Common/ORKDataCollectionManager.m index b0fb8b7940..4c7fb9ec81 100644 --- a/ResearchKit/Common/ORKDataCollectionManager.m +++ b/ResearchKit/Common/ORKDataCollectionManager.m @@ -37,6 +37,9 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE #import "ORKHelpers_Internal.h" #import +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION +#import +#endif static NSString *const ORKDataCollectionPersistenceFileName = @".dataCollection.ork.data"; @@ -45,8 +48,10 @@ @implementation ORKDataCollectionManager { NSOperationQueue *_operationQueue; NSString * _Nonnull _managedDirectory; NSArray *_collectors; - HKHealthStore *_healthStore; CMMotionActivityManager *_activityManager; +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION + HKHealthStore *_healthStore; +#endif NSMutableArray *_completionHandlers; } @@ -119,12 +124,14 @@ - (void)onWorkQueueAsync:(BOOL (^)(ORKDataCollectionManager *manager))block { }); } +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION - (HKHealthStore *)healthStore { if (!_healthStore && [HKHealthStore isHealthDataAvailable]){ _healthStore = [[HKHealthStore alloc] init]; } return _healthStore; } +#endif - (CMMotionActivityManager *)activityManager { if (!_activityManager && [CMMotionActivityManager isActivityAvailable]) { @@ -169,6 +176,7 @@ - (void)addCollector:(ORKCollector *)collector { _collectors = [collectors copy]; } +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION - (ORKHealthCollector *)addHealthCollectorWithSampleType:(HKSampleType*)sampleType unit:(HKUnit *)unit startDate:(NSDate *)startDate error:(NSError**)error { if (!sampleType) { @@ -221,6 +229,7 @@ - (ORKHealthCorrelationCollector *)addHealthCorrelationCollectorWithCorrelationT return healthCorrelationCollector; } +#endif - (ORKMotionActivityCollector *)addMotionActivityCollectorWithStartDate:(NSDate *)startDate error:(NSError* __autoreleasing *)error { @@ -321,13 +330,16 @@ - (void)startCollection { if (_delegate && [_delegate respondsToSelector:@selector(dataCollectionManagerDidCompleteCollection:)]) { [_delegate dataCollectionManagerDidCompleteCollection:self]; } - +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION for (HKObserverQueryCompletionHandler handler in _completionHandlers) { handler(); } [_completionHandlers removeAllObjects]; return NO; +#else + return NO; +#endif }]; }]; @@ -342,7 +354,6 @@ - (void)startCollection { // No need to persist collectors return NO; }]; - } @end diff --git a/ResearchKit/Common/ORKDataCollectionManager_Internal.h b/ResearchKit/Common/ORKDataCollectionManager_Internal.h index a29a0eff65..9ace00d05d 100644 --- a/ResearchKit/Common/ORKDataCollectionManager_Internal.h +++ b/ResearchKit/Common/ORKDataCollectionManager_Internal.h @@ -37,7 +37,9 @@ @interface ORKDataCollectionManager () +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION @property (nonatomic, strong, readonly) HKHealthStore *healthStore; +#endif @property (nonatomic, strong, readonly) CMMotionActivityManager *activityManager; diff --git a/ResearchKit/Common/ORKDefines.h b/ResearchKit/Common/ORKDefines.h index f821a4759c..ee242f71e9 100644 --- a/ResearchKit/Common/ORKDefines.h +++ b/ResearchKit/Common/ORKDefines.h @@ -40,14 +40,5 @@ #define ORK_IOS_10_WATCHOS_3_AVAILABLE (NSClassFromString(@"HKWorkoutConfiguration") != nil) -// Some CLLocationManager API calls would trigger authorization to use location. The presence of those -// API calls in ResearchKit **at compile time** mean apps that link ResearchKit also need Info.plist entries -// for NSLocationAlwaysAndWhenInUseUsageDescription and NSLocationWhenInUseUsageDescription. -// If your app doesn't use ORKLocationRecorder and doesn't specify these Info.plist strings, disable -// ResearchKit's CLLocationManager authorization -#ifndef ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION -#define ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION 1 -#endif - #define ORK_TO_BE_DEPRECATED(message) \ __deprecated_msg(message) diff --git a/ResearchKit/Common/ORKDevice.h b/ResearchKit/Common/ORKDevice.h index 19542807be..13f3e45bc4 100644 --- a/ResearchKit/Common/ORKDevice.h +++ b/ResearchKit/Common/ORKDevice.h @@ -31,8 +31,6 @@ #import #if TARGET_OS_IOS || TARGET_OS_VISION #import -#endif - NS_ASSUME_NONNULL_BEGIN diff --git a/ResearchKit/Common/ORKDevice_Private.h b/ResearchKit/Common/ORKDevice_Private.h index 3a23917de0..8456c38251 100644 --- a/ResearchKit/Common/ORKDevice_Private.h +++ b/ResearchKit/Common/ORKDevice_Private.h @@ -30,8 +30,6 @@ #if TARGET_OS_IOS || TARGET_OS_VISION #import -#endif - NS_ASSUME_NONNULL_BEGIN diff --git a/ResearchKit/Common/ORKEarlyTerminationConfiguration.h b/ResearchKit/Common/ORKEarlyTerminationConfiguration.h index f97b9d22fc..623a439637 100644 --- a/ResearchKit/Common/ORKEarlyTerminationConfiguration.h +++ b/ResearchKit/Common/ORKEarlyTerminationConfiguration.h @@ -32,8 +32,6 @@ #if TARGET_OS_IOS || TARGET_OS_VISION #import -#endif - NS_ASSUME_NONNULL_BEGIN diff --git a/ResearchKit/Common/ORKErrors.h b/ResearchKit/Common/ORKErrors.h index 3ba68845e0..cbf73f1d88 100644 --- a/ResearchKit/Common/ORKErrors.h +++ b/ResearchKit/Common/ORKErrors.h @@ -33,9 +33,6 @@ #if TARGET_OS_IOS || TARGET_OS_VISION #import -#endif - - NS_ASSUME_NONNULL_BEGIN diff --git a/ResearchKit/Common/ORKFamilyHistoryResult.h b/ResearchKit/Common/ORKFamilyHistoryResult.h new file mode 100644 index 0000000000..d5bcdc1760 --- /dev/null +++ b/ResearchKit/Common/ORKFamilyHistoryResult.h @@ -0,0 +1,49 @@ +/* + Copyright (c) 2023, 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 + +@class ORKRelatedPerson; + +NS_ASSUME_NONNULL_BEGIN + +/** + A result object produced by the family history step. + */ + +ORK_CLASS_AVAILABLE +@interface ORKFamilyHistoryResult : ORKResult + +@property (nonatomic, nullable, copy) NSArray *relatedPersons; +@property (nonatomic, nullable, copy) NSArray *displayedConditions; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ResearchKit/Common/ORKFamilyHistoryResult.m b/ResearchKit/Common/ORKFamilyHistoryResult.m new file mode 100644 index 0000000000..a08e8e4566 --- /dev/null +++ b/ResearchKit/Common/ORKFamilyHistoryResult.m @@ -0,0 +1,81 @@ +/* + Copyright (c) 2023, 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 "ORKFamilyHistoryResult.h" + +#import "ORKHelpers_Internal.h" +#import "ORKRelatedPerson.h" +#import "ORKResult_Private.h" + + +@implementation ORKFamilyHistoryResult + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [super encodeWithCoder:aCoder]; + ORK_ENCODE_OBJ(aCoder, relatedPersons); + ORK_ENCODE_OBJ(aCoder, displayedConditions); +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + self = [super initWithCoder:aDecoder]; + if (self) { + ORK_DECODE_OBJ_ARRAY(aDecoder, relatedPersons, ORKRelatedPerson); + ORK_DECODE_OBJ_ARRAY(aDecoder, displayedConditions, NSString); + } + return self; +} + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (BOOL)isEqual:(id)object { + BOOL isParentSame = [super isEqual:object]; + + __typeof(self) castObject = object; + return (isParentSame && + ORKEqualObjects(self.relatedPersons, castObject.relatedPersons) && + ORKEqualObjects(self.displayedConditions, castObject.displayedConditions)); +} + +- (instancetype)copyWithZone:(NSZone *)zone { + ORKFamilyHistoryResult *result = [super copyWithZone:zone]; + + result->_relatedPersons = ORKArrayCopyObjects(_relatedPersons); + result->_displayedConditions = [_displayedConditions copy]; + + return result; +} + +- (NSUInteger)hash { + return super.hash ^ self.relatedPersons.hash ^ self.displayedConditions.hash; +} + +@end diff --git a/ResearchKit/Common/ORKFamilyHistoryStep.h b/ResearchKit/Common/ORKFamilyHistoryStep.h new file mode 100644 index 0000000000..c57a5b2e53 --- /dev/null +++ b/ResearchKit/Common/ORKFamilyHistoryStep.h @@ -0,0 +1,53 @@ +/* + Copyright (c) 2023, 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 + +#import + +@class ORKConditionStepConfiguration; +@class ORKRelativeGroup; + +NS_ASSUME_NONNULL_BEGIN + +/** + A concrete subclass that will provide a participant with a + family history survey. + */ + +ORK_CLASS_AVAILABLE +@interface ORKFamilyHistoryStep : ORKStep + +@property (nonatomic, copy) ORKConditionStepConfiguration *conditionStepConfiguration; +@property (nonatomic, copy) NSArray *relativeGroups; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ResearchKit/Common/ORKFamilyHistoryStep.m b/ResearchKit/Common/ORKFamilyHistoryStep.m new file mode 100644 index 0000000000..685a87758c --- /dev/null +++ b/ResearchKit/Common/ORKFamilyHistoryStep.m @@ -0,0 +1,115 @@ +/* + Copyright (c) 2023, 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 "ORKFamilyHistoryStep.h" + +#import "ORKAnswerFormat_Internal.h" +#import "ORKCollectionResult.h" +#import "ORKConditionStepConfiguration.h" +#import "ORKFormStep.h" +#import "ORKHelpers_Internal.h" +#import "ORKRelativeGroup.h" + + +@implementation ORKFamilyHistoryStep + +- (instancetype)initWithIdentifier:(NSString *)identifier { + self = [super initWithIdentifier:identifier]; + + return self; +} + +- (void)validateParameters { + [super validateParameters]; + + // validate that atleast one condition has been provided + if (self.conditionStepConfiguration.conditions.count == 0) { + @throw [NSException exceptionWithName:NSInvalidArgumentException + reason:@"At least one ORKHealthCondition must be added to the ORKConditionStepConfiguration object" + userInfo:nil]; + } + + // validate that atleast one relative group has been provided + if (self.relativeGroups.count == 0) { + @throw [NSException exceptionWithName:NSInvalidArgumentException + reason:@"At least one ORKRelativeGroup must be provided" + userInfo:nil]; + } + + // validate that the identifiers for each relative group is unique + NSMutableSet *identifiers = [NSMutableSet new]; + for (ORKRelativeGroup *relativeGroup in self.relativeGroups) { + if ([identifiers containsObject:relativeGroup.identifier]) { + @throw [NSException exceptionWithName:NSInvalidArgumentException + reason:@"Each ORKRelativeGroup must have a unique identifier" + userInfo:nil]; + } else { + [identifiers addObject:relativeGroup.identifier]; + } + } + +} + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [super encodeWithCoder:aCoder]; + ORK_ENCODE_OBJ(aCoder, conditionStepConfiguration); + ORK_ENCODE_OBJ(aCoder, relativeGroups); +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + self = [super initWithCoder:aDecoder]; + if (self) { + ORK_DECODE_OBJ_CLASS(aDecoder, conditionStepConfiguration, ORKConditionStepConfiguration); + ORK_DECODE_OBJ_ARRAY(aDecoder, relativeGroups, ORKRelativeGroup); + } + return self; +} + +- (nonnull id)copyWithZone:(nullable NSZone *)zone { + ORKFamilyHistoryStep *step = [super copyWithZone:zone]; + step->_conditionStepConfiguration = [_conditionStepConfiguration copy]; + step->_relativeGroups = [_relativeGroups copy]; + + return step; +} + +- (BOOL)isEqual:(id)object { + BOOL isParentSame = [super isEqual:object]; + + __typeof(self) castObject = object; + return (isParentSame && ORKEqualObjects(_conditionStepConfiguration, castObject->_conditionStepConfiguration) + && ORKEqualObjects(_relativeGroups, castObject->_relativeGroups)); +} + +@end diff --git a/ResearchKit/Common/ORKFormStep.h b/ResearchKit/Common/ORKFormStep.h index 35175af950..3962ec6af0 100644 --- a/ResearchKit/Common/ORKFormStep.h +++ b/ResearchKit/Common/ORKFormStep.h @@ -35,7 +35,7 @@ #if TARGET_OS_IOS || TARGET_OS_VISION #import #import -#endif +#import /** Values that determine the style @@ -176,6 +176,7 @@ ORK_CLASS_AVAILABLE @param learnMoreItem The `ORKLearnMoreItem` to be presented when button is pressed. @param showsProgress A Boolean that determines if the formItem will display a progress indicator @param answerFormat The answer format for the form item. + @param tagText The tag text to be presented in the card header view. @param optional A Boolean that determines whether the item is optional @return An initialized form item. diff --git a/ResearchKit/Common/ORKFormStep.m b/ResearchKit/Common/ORKFormStep.m index 0a9be8541b..943503c166 100644 --- a/ResearchKit/Common/ORKFormStep.m +++ b/ResearchKit/Common/ORKFormStep.m @@ -39,7 +39,10 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE #import "ORKStep_Private.h" #import "ORKHelpers_Internal.h" + +#if !TARGET_OS_WATCH #import "ORKFormItemVisibilityRule.h" +#endif @implementation ORKFormStep @@ -182,6 +185,7 @@ - (void)setFormItems:(NSArray *)formItems { } } +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION && TARGET_OS_IOS - (NSSet *)requestedHealthKitTypesForReading { NSMutableSet *healthTypes = [NSMutableSet set]; @@ -195,6 +199,7 @@ - (void)setFormItems:(NSArray *)formItems { return healthTypes.count ? healthTypes : nil; } +#endif @end @@ -260,6 +265,7 @@ - (instancetype)initWithSectionTitle:(nullable NSString *)sectionTitle detailTex return self; } +#if TARGET_OS_IOS - (ORKFormItem *)confirmationAnswerFormItemWithIdentifier:(NSString *)identifier text:(nullable NSString *)text errorMessage:(NSString *)errorMessage { @@ -279,6 +285,7 @@ - (ORKFormItem *)confirmationAnswerFormItemWithIdentifier:(NSString *)identifier optional:self.optional]; return item; } +#endif + (BOOL)supportsSecureCoding { return YES; @@ -292,7 +299,9 @@ - (instancetype)copyWithZone:(NSZone *)zone { item->_learnMoreItem = [_learnMoreItem copy]; item->_showsProgress = _showsProgress; item->_tagText = [_tagText copy]; +#if !TARGET_OS_WATCH item->_visibilityRule = [_visibilityRule copy]; +#endif return item; } @@ -309,7 +318,9 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder { 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; } @@ -348,8 +359,12 @@ - (BOOL)isEqual:(id)object { } - (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 { diff --git a/ResearchKit/Common/ORKHealthAnswerFormat.h b/ResearchKit/Common/ORKHealthAnswerFormat.h index f44229be80..78368865a3 100644 --- a/ResearchKit/Common/ORKHealthAnswerFormat.h +++ b/ResearchKit/Common/ORKHealthAnswerFormat.h @@ -29,9 +29,12 @@ */ -#import #import +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION && TARGET_OS_IOS +#import +#endif + NS_ASSUME_NONNULL_BEGIN /** @@ -70,6 +73,8 @@ 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 ORK_CLASS_AVAILABLE @interface ORKHealthKitCharacteristicTypeAnswerFormat : ORKAnswerFormat @@ -222,5 +227,6 @@ included in the question result generated by form items or question steps @end -NS_ASSUME_NONNULL_END +#endif +NS_ASSUME_NONNULL_END diff --git a/ResearchKit/Common/ORKHealthAnswerFormat.m b/ResearchKit/Common/ORKHealthAnswerFormat.m index 3e9a84a877..e75df79e7d 100644 --- a/ResearchKit/Common/ORKHealthAnswerFormat.m +++ b/ResearchKit/Common/ORKHealthAnswerFormat.m @@ -38,7 +38,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE #import "ORKQuestionResult_Private.h" #import "ORKResult.h" - +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION #pragma mark - ORKHealthAnswerFormat ORKBiologicalSexIdentifier const ORKBiologicalSexIdentifierFemale = @"HKBiologicalSexFemale"; @@ -420,3 +420,4 @@ - (NSString *)localizedUnitString { } @end +#endif diff --git a/ResearchKit/Common/ORKHealthCondition.h b/ResearchKit/Common/ORKHealthCondition.h new file mode 100644 index 0000000000..be33dd111e --- /dev/null +++ b/ResearchKit/Common/ORKHealthCondition.h @@ -0,0 +1,70 @@ +/* + Copyright (c) 2023, 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 + +#import + +@class ORKTaskResult; + +NS_ASSUME_NONNULL_BEGIN + +/** + A base class that represents a single health condition displayed + by the family history step. + */ + +ORK_CLASS_AVAILABLE +@interface ORKHealthCondition : NSObject + ++ (instancetype)new NS_UNAVAILABLE; +- (instancetype)init NS_UNAVAILABLE; + +/** + Creates a new health condition with the specified identifier. + + This method is the primary designated initializer. + + @param identifier The unique identifier of the health condition. + @param name The name displayed to the participant for selection. + @param value The value stored to the result if the health condition is selected. + */ + +- (instancetype)initWithIdentifier:(NSString *)identifier + displayName:(NSString *)name + value:(NSObject *)value NS_DESIGNATED_INITIALIZER; + +@property (nonatomic, readonly, copy) NSString *identifier; +@property (nonatomic, readonly, copy) NSString *displayName; +@property (nonatomic, readonly, copy) NSObject *value; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ResearchKit/Common/ORKHealthCondition.m b/ResearchKit/Common/ORKHealthCondition.m new file mode 100644 index 0000000000..ce9196d0c1 --- /dev/null +++ b/ResearchKit/Common/ORKHealthCondition.m @@ -0,0 +1,98 @@ +/* + Copyright (c) 2023, 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 "ORKHealthCondition.h" + +#import "ORKAnswerFormat_Internal.h" +#import "ORKCollectionResult.h" +#import "ORKHelpers_Internal.h" + + +@implementation ORKHealthCondition + +- (instancetype)initWithIdentifier:(NSString *)identifier + displayName:(NSString *)name + value:(NSObject *)value { + self = [super init]; + + if (self) { + _identifier = [identifier copy]; + _displayName = [name copy]; + _value = [value copy]; + } + + return self; +} + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + ORK_ENCODE_OBJ(aCoder, identifier); + ORK_ENCODE_OBJ(aCoder, displayName); + ORK_ENCODE_OBJ(aCoder, value); +} + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wobjc-designated-initializers" +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + self = [super init]; + if (self) { + ORK_DECODE_OBJ_CLASS(aDecoder, identifier, NSString); + ORK_DECODE_OBJ_CLASS(aDecoder, displayName, NSString); + ORK_DECODE_OBJ_CLASSES(aDecoder, value, ORKAllowableValueClasses()); + } + return self; +} + +- (nonnull id)copyWithZone:(nullable NSZone *)zone { + ORKHealthCondition *healthCondition = [[[self class] allocWithZone:zone] initWithIdentifier:[_identifier copy] + displayName:[_displayName copy] + value:[_value copy]]; + return healthCondition; +} + +- (BOOL)isEqual:(id)object { + if ([self class] != [object class]) { + return NO; + } + + __typeof(self) castObject = object; + return (ORKEqualObjects(self.identifier, castObject.identifier) + && ORKEqualObjects(self.displayName, castObject.displayName) + && ORKEqualObjects(self.value, castObject.value)); +} + +- (NSUInteger)hash { + return super.hash ^ self.identifier.hash ^ self.displayName.hash ^ self.value.hash; +} + +@end diff --git a/ResearchKit/Common/ORKHealthKitPermissionType.m b/ResearchKit/Common/ORKHealthKitPermissionType.m index 29ee6d6bdc..9a013ab37e 100644 --- a/ResearchKit/Common/ORKHealthKitPermissionType.m +++ b/ResearchKit/Common/ORKHealthKitPermissionType.m @@ -33,7 +33,10 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE #import "ORKHealthKitPermissionType.h" #import "ORKHelpers_Internal.h" + +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION #import +#endif static NSString *const Symbol = @"heart.fill"; static uint32_t const IconTintColor = 0xFF5E5E; @@ -83,6 +86,7 @@ - (ORKRequestPermissionsState) permissionState { } - (void)checkHealthKitAuthorizationStatus { +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION if (![HKHealthStore isHealthDataAvailable]) { _permissionState = ORKRequestPermissionsStateNotSupported; if (self.permissionsStatusUpdateCallback != nil) { @@ -111,6 +115,8 @@ - (void)checkHealthKitAuthorizationStatus { }); }]; +#endif // ORK_FEATURE_HEALTHKIT_AUTHORIZATION + } - (BOOL)canContinue { @@ -121,6 +127,7 @@ - (BOOL)canContinue { } - (void)requestPermission { +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION [[HKHealthStore new] requestAuthorizationToShareTypes:_sampleTypesToWrite readTypes:_objectTypesToRead completion:^(BOOL success, NSError * _Nullable error) { dispatch_async(dispatch_get_main_queue(), ^{ if (error) { @@ -133,6 +140,7 @@ - (void)requestPermission { } }); }]; +#endif // ORK_FEATURE_HEALTHKIT_AUTHORIZATION } - (BOOL)isEqual:(id)object { diff --git a/ResearchKit/Common/ORKHealthSampleQueryOperation.h b/ResearchKit/Common/ORKHealthSampleQueryOperation.h index b70c31c4c6..a086afd385 100644 --- a/ResearchKit/Common/ORKHealthSampleQueryOperation.h +++ b/ResearchKit/Common/ORKHealthSampleQueryOperation.h @@ -31,8 +31,9 @@ #import #import "ORKOperation.h" +#import "ORKDefines.h" - +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION @class ORKDataCollectionManager; @class ORKCollector; @protocol ORKHealthCollectable; @@ -47,3 +48,4 @@ - (instancetype)initWithCollector:(ORKCollector *)collector mananger:(ORKDataCollectionManager *)manager; @end +#endif diff --git a/ResearchKit/Common/ORKHealthSampleQueryOperation.m b/ResearchKit/Common/ORKHealthSampleQueryOperation.m index 5427b761bf..b53dd41148 100644 --- a/ResearchKit/Common/ORKHealthSampleQueryOperation.m +++ b/ResearchKit/Common/ORKHealthSampleQueryOperation.m @@ -37,7 +37,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE #import "ORKCollector_Internal.h" #import "ORKDataCollectionManager_Internal.h" - +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION static NSUInteger const QueryLimitSize = 1000; @implementation ORKHealthSampleQueryOperation { @@ -247,5 +247,4 @@ - (void)handleResults:(NSArray *)results } @end - #endif diff --git a/ResearchKit/Common/ORKHelpers_Internal.h b/ResearchKit/Common/ORKHelpers_Internal.h index a4df35056f..5af352a3f3 100644 --- a/ResearchKit/Common/ORKHelpers_Internal.h +++ b/ResearchKit/Common/ORKHelpers_Internal.h @@ -41,7 +41,11 @@ #import #import +#import +#import +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -374,6 +378,13 @@ ORK_INLINE double ORKPoundsToKilograms(double pounds) { return ORKPoundsAndOuncesToKilograms(pounds, 0); } +ORK_INLINE double ORKForceDoubleToLimits(double value) { + if (value == NAN || value == INFINITY) { + return DBL_MAX; + } + return fmin(fmax(value, -DBL_MAX), DBL_MAX); +} + ORK_INLINE UIColor *ORKOpaqueColorWithReducedAlphaFromBaseColor(UIColor *baseColor, NSUInteger colorIndex, NSUInteger totalColors) { UIColor *color = baseColor; if (totalColors > 1) { diff --git a/ResearchKit/Common/ORKHelpers_Private.h b/ResearchKit/Common/ORKHelpers_Private.h index ccbb8ca6e8..5516799b4b 100644 --- a/ResearchKit/Common/ORKHelpers_Private.h +++ b/ResearchKit/Common/ORKHelpers_Private.h @@ -32,9 +32,6 @@ #import #if TARGET_OS_IOS || TARGET_OS_VISION #import -#endif - - NS_ASSUME_NONNULL_BEGIN diff --git a/ResearchKit/Common/ORKInstructionStep.h b/ResearchKit/Common/ORKInstructionStep.h index 28447f5202..443ca57e42 100644 --- a/ResearchKit/Common/ORKInstructionStep.h +++ b/ResearchKit/Common/ORKInstructionStep.h @@ -28,14 +28,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - #import #if TARGET_OS_IOS || TARGET_OS_VISION #import -#endif NS_ASSUME_NONNULL_BEGIN diff --git a/ResearchKit/Common/ORKLocationPermissionType.h b/ResearchKit/Common/ORKLocationPermissionType.h index 6c261eff20..8864b926e7 100644 --- a/ResearchKit/Common/ORKLocationPermissionType.h +++ b/ResearchKit/Common/ORKLocationPermissionType.h @@ -40,10 +40,12 @@ NS_ASSUME_NONNULL_BEGIN A permission type object that requests access for location data. */ +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION ORK_CLASS_AVAILABLE @interface ORKLocationPermissionType : ORKPermissionType @end +#endif NS_ASSUME_NONNULL_END diff --git a/ResearchKit/Common/ORKLocationPermissionType.m b/ResearchKit/Common/ORKLocationPermissionType.m index a8dfb363ed..d9f67f5316 100644 --- a/ResearchKit/Common/ORKLocationPermissionType.m +++ b/ResearchKit/Common/ORKLocationPermissionType.m @@ -34,6 +34,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE #import "ORKLocationPermissionType.h" #import "ORKHelpers_Internal.h" +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION #import #import @@ -119,5 +120,4 @@ - (BOOL)isEqual:(id)object { } @end - #endif diff --git a/ResearchKit/Common/ORKOrderedTask.h b/ResearchKit/Common/ORKOrderedTask.h index f07f223019..2857ab8e8c 100644 --- a/ResearchKit/Common/ORKOrderedTask.h +++ b/ResearchKit/Common/ORKOrderedTask.h @@ -34,8 +34,6 @@ #if TARGET_OS_IOS || TARGET_OS_VISION #import -#endif - NS_ASSUME_NONNULL_BEGIN diff --git a/ResearchKit/Common/ORKOrderedTask.m b/ResearchKit/Common/ORKOrderedTask.m index e1a91636c5..5e30c707f7 100644 --- a/ResearchKit/Common/ORKOrderedTask.m +++ b/ResearchKit/Common/ORKOrderedTask.m @@ -30,8 +30,8 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE */ #import "ORKOrderedTask.h" + #import "ORKAnswerFormat.h" -#import "ORKInstructionStep.h" #import "ORKCompletionStep.h" #import "ORKStep_Private.h" #import "ORKHelpers_Internal.h" @@ -40,6 +40,14 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE #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 @@ -277,14 +285,6 @@ - (ORKTaskTotalProgress)totalProgressOfCurrentStep:(ORKStep *)currentStep { } totalQuestions += 1; } -#else - if ([step isKindOfClass:[ORKQuestionStep class]]) { - if (step.identifier == currentStep.identifier) { - currentStepStartingProgressNumber = (totalQuestions + 1); - } - totalQuestions += 1; - } -#endif } totalProgress.currentStepStartingProgressPosition = currentStepStartingProgressNumber; @@ -376,6 +376,7 @@ - (BOOL)doesItemRequireSingleSection:(ORKFormItem *)item { #endif +#if TARGET_OS_IOS - (BOOL)providesBackgroundAudioPrompts { BOOL providesAudioPrompts = NO; diff --git a/ResearchKit/Common/ORKPageStep.h b/ResearchKit/Common/ORKPageStep.h index 9d06a95430..b11a847885 100644 --- a/ResearchKit/Common/ORKPageStep.h +++ b/ResearchKit/Common/ORKPageStep.h @@ -32,8 +32,6 @@ #if TARGET_OS_IOS || TARGET_OS_VISION #import #import -#endif - NS_ASSUME_NONNULL_BEGIN diff --git a/ResearchKit/Common/ORKPageStep.m b/ResearchKit/Common/ORKPageStep.m index e22027289e..5701945beb 100644 --- a/ResearchKit/Common/ORKPageStep.m +++ b/ResearchKit/Common/ORKPageStep.m @@ -73,12 +73,14 @@ - (ORKPermissionMask)requestedPermissions { return ORKPermissionNone; } +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION - (NSSet *)requestedHealthKitTypesForReading { if ([self.pageTask respondsToSelector:@selector(requestedHealthKitTypesForReading)]) { return [self.pageTask requestedHealthKitTypesForReading]; } return nil; } +#endif #pragma mark - NSCopying diff --git a/ResearchKit/Common/ORKPermissionType.h b/ResearchKit/Common/ORKPermissionType.h index e30595da1c..2c1ecad724 100644 --- a/ResearchKit/Common/ORKPermissionType.h +++ b/ResearchKit/Common/ORKPermissionType.h @@ -35,6 +35,7 @@ #import #import +#import NS_ASSUME_NONNULL_BEGIN @@ -81,7 +82,9 @@ ORK_CLASS_AVAILABLE + (ORKMotionActivityPermissionType *) deviceMotionPermissionType; +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION + (ORKLocationPermissionType *) locationPermissionType; +#endif @end diff --git a/ResearchKit/Common/ORKPermissionType.m b/ResearchKit/Common/ORKPermissionType.m index c2ee3981cf..cf4bc16aac 100644 --- a/ResearchKit/Common/ORKPermissionType.m +++ b/ResearchKit/Common/ORKPermissionType.m @@ -85,9 +85,11 @@ + (ORKMotionActivityPermissionType *)deviceMotionPermissionType { return [[ORKMotionActivityPermissionType alloc] init]; } +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION + (ORKLocationPermissionType *) locationPermissionType { return [[ORKLocationPermissionType alloc] init]; } +#endif @end diff --git a/ResearchKit/Common/ORKQuestionResult.h b/ResearchKit/Common/ORKQuestionResult.h index 643f7404ef..4abfde2de6 100644 --- a/ResearchKit/Common/ORKQuestionResult.h +++ b/ResearchKit/Common/ORKQuestionResult.h @@ -35,6 +35,11 @@ #import #import +#import + +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && TARGET_OS_IOS +#import +#endif NS_ASSUME_NONNULL_BEGIN @@ -150,8 +155,9 @@ ORK_CLASS_AVAILABLE @end +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && TARGET_OS_IOS /** - The `ORKLocation` class represents the location addess obtained from a locaton question. + The `ORKLocation` class represents the location addess obtained from a location question. */ ORK_CLASS_AVAILABLE @interface ORKLocation : NSObject @@ -182,7 +188,7 @@ ORK_CLASS_AVAILABLE @property (nonatomic, copy, readonly, nullable) CNPostalAddress *postalAddress; @end - +#endif /** A result object from a location answer format. @@ -194,6 +200,8 @@ ORK_CLASS_AVAILABLE completes, it may be appropriate to serialize it for transmission to a server, or to immediately perform analysis on it. */ + +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && TARGET_OS_IOS ORK_CLASS_AVAILABLE @interface ORKLocationQuestionResult : ORKQuestionResult @@ -203,7 +211,7 @@ ORK_CLASS_AVAILABLE @property (nonatomic, copy, nullable) ORKLocation *locationAnswer; @end - +#endif /** A result object from a multiple-component picker-style choice-based answer format. diff --git a/ResearchKit/Common/ORKQuestionResult.m b/ResearchKit/Common/ORKQuestionResult.m index ca019c5a00..81fdf85495 100644 --- a/ResearchKit/Common/ORKQuestionResult.m +++ b/ResearchKit/Common/ORKQuestionResult.m @@ -28,6 +28,14 @@ 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 +#import +#import +#import +#import +#import +#import +#import #import "ORKQuestionResult_Private.h" #import "ORKResult_Private.h" @@ -38,6 +46,10 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE #import "ORKHelpers_Internal.h" #import "ORKAnswerFormat_Internal.h" #endif +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && TARGET_OS_IOS +#import +#import +#endif @implementation ORKQuestionResult { @protected @@ -327,7 +339,7 @@ - (NSDate *)dateAnswer { @end -#if !TARGET_OS_VISION +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && TARGET_OS_IOS && !TARGET_OS_VISION #pragma mark - ORKLocationQuestionResult @implementation ORKLocation diff --git a/ResearchKit/Common/ORKQuestionResult_Private.h b/ResearchKit/Common/ORKQuestionResult_Private.h index e2a6bb8f88..5efa1d7a67 100644 --- a/ResearchKit/Common/ORKQuestionResult_Private.h +++ b/ResearchKit/Common/ORKQuestionResult_Private.h @@ -32,11 +32,9 @@ #if TARGET_OS_IOS || TARGET_OS_VISION #import -#endif #import - NS_ASSUME_NONNULL_BEGIN @interface ORKQuestionResult () @@ -49,7 +47,7 @@ NS_ASSUME_NONNULL_BEGIN @end - +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && TARGET_OS_IOS @interface ORKLocation () #if TARGET_OS_IOS @@ -62,5 +60,6 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithPlacemark:(CLPlacemark *)placemark userInput:(NSString *)userInput; @end +#endif NS_ASSUME_NONNULL_END diff --git a/ResearchKit/Common/ORKRecorder.h b/ResearchKit/Common/ORKRecorder.h index 3b0b962ef7..c57b2b2101 100644 --- a/ResearchKit/Common/ORKRecorder.h +++ b/ResearchKit/Common/ORKRecorder.h @@ -31,10 +31,12 @@ #import -#import #import #import +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION +#import +#endif NS_ASSUME_NONNULL_BEGIN @@ -105,8 +107,9 @@ ORK_CLASS_AVAILABLE If your recorder requires or would benefit from read access to HealthKit at runtime during the task, return the appropriate set of `HKSampleType` objects. */ +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION - (nullable NSSet *)requestedHealthKitTypesForReading; - +#endif @end @@ -309,6 +312,8 @@ ORK_CLASS_AVAILABLE No additional parameters besides the identifier are required. */ + +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION ORK_CLASS_AVAILABLE @interface ORKLocationRecorderConfiguration : ORKRecorderConfiguration @@ -333,7 +338,7 @@ ORK_CLASS_AVAILABLE - (instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER; @end - +#endif /** A configuration object that records streaming audio data during an active step. diff --git a/ResearchKit/Common/ORKRecorder.m b/ResearchKit/Common/ORKRecorder.m index bfc0d2a78d..def1d0eddc 100644 --- a/ResearchKit/Common/ORKRecorder.m +++ b/ResearchKit/Common/ORKRecorder.m @@ -89,9 +89,12 @@ - (ORKRecorder *)recorderForStep:(ORKStep *)step outputDirectory:(NSURL *)output return nil; } +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION - (NSSet *)requestedHealthKitTypesForReading { return nil; } +#endif + - (ORKPermissionMask)requestedPermissionMask { return ORKPermissionNone; } diff --git a/ResearchKit/Common/ORKRelatedPerson.h b/ResearchKit/Common/ORKRelatedPerson.h new file mode 100644 index 0000000000..0fa20dfd7d --- /dev/null +++ b/ResearchKit/Common/ORKRelatedPerson.h @@ -0,0 +1,87 @@ +/* + Copyright (c) 2023, 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 + +#import +#import +#import + + +@class ORKTaskResult; + + +NS_ASSUME_NONNULL_BEGIN + +/** + An object that represents a relative added during + a family health history survey. + */ + +ORK_CLASS_AVAILABLE +@interface ORKRelatedPerson : NSObject + ++ (instancetype)new NS_UNAVAILABLE; +- (instancetype)init NS_UNAVAILABLE; + +/** + Creates a new related person with the specified identifier. + + This method is the primary designated initializer. + + @param identifier The unique identifier of the related person. + @param groupIdentifier The identifier of the relative group to which the person belongs. + @param identifierForCellTitle The identifier of the result value to be used for the relative's cell title. + @param result The task result generated for the relative's health history survey. + */ + +- (instancetype)initWithIdentifier:(NSString *)identifier + groupIdentifier:(NSString *)groupIdentifier + identifierForCellTitle:(NSString *)identifierForCellTitle + taskResult:(ORKTaskResult *)result NS_DESIGNATED_INITIALIZER; + +@property (nonatomic, readonly, copy) NSString *identifier; +@property (nonatomic, readonly, copy) NSString *groupIdentifier; +@property (nonatomic, readonly, copy) NSString *identifierForCellTitle; +@property (nonatomic, copy) ORKTaskResult *taskResult; + +- (nullable NSString *)getTitleValueWithIdentifier:(NSString *)identifier; + +- (NSArray *)getDetailListValuesWithIdentifiers:(NSArray *)identifiers + displayInfoKeyAndValues:(NSDictionary *> *)displayInfoKeyAndValues; + +- (NSArray *)getConditionsListWithStepIdentifier:(NSString *)stepIdentifier + formItemIdentifier:(NSString *)formItemIdentifier + conditionsKeyValues:(NSDictionary *)conditionsKeyValues; + + +@end + +NS_ASSUME_NONNULL_END diff --git a/ResearchKit/Common/ORKRelatedPerson.m b/ResearchKit/Common/ORKRelatedPerson.m new file mode 100644 index 0000000000..31d92fd3b6 --- /dev/null +++ b/ResearchKit/Common/ORKRelatedPerson.m @@ -0,0 +1,190 @@ +/* + Copyright (c) 2023, 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 "ORKRelatedPerson.h" + +#import +#import +#import +#import +#import +#import +#import +#import + + + +@implementation ORKRelatedPerson { +} + +- (instancetype)initWithIdentifier:(NSString *)identifier + groupIdentifier:(NSString *)groupIdentifier + identifierForCellTitle:(NSString *)identifierForCellTitle + taskResult:(ORKTaskResult *)result { + self = [super init]; + + if (self) { + _identifier = [identifier copy]; + _groupIdentifier = [groupIdentifier copy]; + _identifierForCellTitle = [identifierForCellTitle copy]; + _taskResult = [result copy]; + } + return self; +} + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + ORK_ENCODE_OBJ(aCoder, identifier); + ORK_ENCODE_OBJ(aCoder, groupIdentifier); + ORK_ENCODE_OBJ(aCoder, identifierForCellTitle); + ORK_ENCODE_OBJ(aCoder, taskResult); +} + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wobjc-designated-initializers" +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + self = [super init]; + if (self) { + ORK_DECODE_OBJ_CLASS(aDecoder, identifier, NSString); + ORK_DECODE_OBJ_CLASS(aDecoder, groupIdentifier, NSString); + ORK_DECODE_OBJ_CLASS(aDecoder, identifierForCellTitle, NSString); + ORK_DECODE_OBJ_CLASS(aDecoder, taskResult, ORKTaskResult); + } + return self; +} + +- (instancetype)copyWithZone:(nullable NSZone *)zone { + ORKRelatedPerson *relatedPerson = [[[self class] allocWithZone:zone] initWithIdentifier:[_identifier copy] + groupIdentifier:[_groupIdentifier copy] + identifierForCellTitle: [_identifierForCellTitle copy] + taskResult:[_taskResult copy]]; + return relatedPerson; +} + +- (BOOL)isEqual:(id)object { + if ([self class] != [object class]) { + return NO; + } + + __typeof(self) castObject = object; + return (ORKEqualObjects(self.identifier, castObject.identifier) + && ORKEqualObjects(self.groupIdentifier, castObject.groupIdentifier) + && ORKEqualObjects(self.identifierForCellTitle, castObject.identifierForCellTitle) + && ORKEqualObjects(self.taskResult, castObject.taskResult)); +} + +- (nullable NSString *)getTitleValueWithIdentifier:(NSString *)identifier { + return [self getResultValueWithIdentifier:identifier]; +} + +- (NSArray *)getDetailListValuesWithIdentifiers:(NSArray *)identifiers + displayInfoKeyAndValues:(nonnull NSDictionary *> *)displayInfoKeyAndValues { + NSMutableArray *detailListValues = [NSMutableArray new]; + + for (NSString *identifier in identifiers) { + NSString *result = [self getResultValueWithIdentifier:identifier]; + + if ([result isKindOfClass:[ORKDontKnowAnswer class]]) { + continue; + } + + NSString *value = ![result isKindOfClass:[NSString class]] ? [NSString stringWithFormat:@"%i", result.intValue] : result; + if (value && ![value isEqual:@"0"]) { + NSString *displayText = displayInfoKeyAndValues[identifier][value]; + if (![self shouldSkipListValue:displayText] && ![self shouldSkipListValue:value]) { + [detailListValues addObject: displayText != nil ? displayText : value]; + } + } + } + + return [detailListValues copy]; +} + +- (NSArray *)getConditionsListWithStepIdentifier:(NSString *)stepIdentifier + formItemIdentifier:(NSString *)formItemIdentifier + conditionsKeyValues:(nonnull NSDictionary *)conditionsKeyValues { + ORKStepResult *stepResult = (ORKStepResult *)[self.taskResult resultForIdentifier:stepIdentifier]; + + ORKChoiceQuestionResult *choiceQuestionResult = (ORKChoiceQuestionResult *)[stepResult resultForIdentifier:formItemIdentifier]; + NSArray *conditionsList = (NSArray *)choiceQuestionResult.choiceAnswers; + + NSMutableArray *conditionListDisplayValues = [NSMutableArray new]; + + BOOL didSkipValue = NO; + for (NSString *condition in conditionsList) { + NSString *value = [conditionsKeyValues valueForKey:condition]; + + if ([self shouldSkipListValue:value]) { + didSkipValue = YES; + } else { + NSString *displayString = [[value lowercaseString] isEqual:@"none of the above"] ? ORKLocalizedString(@"FAMILY_HISTORY_NONE_SELECTED", @"") : value; + [conditionListDisplayValues addObject:displayString]; + } + } + + if (didSkipValue && conditionListDisplayValues.count == 0) { + [conditionListDisplayValues addObject:@""]; + } + + return [conditionListDisplayValues copy]; +} + +- (nullable NSString *)getResultValueWithIdentifier:(NSString *)identifier { + + for (ORKStepResult *result in _taskResult.results) { + ORKQuestionResult *questionResult = (ORKQuestionResult *)[result resultForIdentifier:identifier]; + + if (questionResult) { + if ([questionResult isKindOfClass:[ORKChoiceQuestionResult class]]) { + ORKChoiceQuestionResult *choiceQuestionResult = (ORKChoiceQuestionResult *)questionResult; + return (NSString *)choiceQuestionResult.choiceAnswers.firstObject; + } else { + NSString *answer = (NSString *)questionResult.answer; + + + return answer; + } + } else { + break; + } + } + + return nil; +} + +- (BOOL)shouldSkipListValue:(NSString *)value { + return ([[value lowercaseString] isEqual:@"i don't know"] || [[value lowercaseString] isEqual:@"i don’t know"] || [[value lowercaseString] isEqual:@"i prefer not to answer"]); +} + + +@end diff --git a/ResearchKit/Common/ORKRelativeGroup.h b/ResearchKit/Common/ORKRelativeGroup.h new file mode 100644 index 0000000000..3aa9889cc4 --- /dev/null +++ b/ResearchKit/Common/ORKRelativeGroup.h @@ -0,0 +1,89 @@ +/* + Copyright (c) 2023, 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 + +#import + + +@class ORKTaskResult; +@class ORKFormStep; + +NS_ASSUME_NONNULL_BEGIN + +/** + An object to represent a relative type displayed + during a family health history survey. + + Example relative groups could be parents, children, or siblings. + */ + +ORK_CLASS_AVAILABLE +@interface ORKRelativeGroup : NSObject + ++ (instancetype)new NS_UNAVAILABLE; +- (instancetype)init NS_UNAVAILABLE; + +/** + Creates a new relative group with the specified identifier. + + This method is the primary designated initializer. + + @param identifier The unique identifier of the relative group. + @param name The name of the relative group. This should be the singular representation. + @param title The table section title for the relative group. + @param detailText The detail text displayed in the table section header for the relative group. + @param identifierForCellTitle The identifier of the result value to be used for the relative's cell title. + @param maxAllowed The maximum amount of relatives that are allowed to be added by the participant. + @param formSteps The form steps that will precede the health conditions step during the survey. + @param detailTextIdentifiers The identifiers of each result value that will be displayed in the relative's card view. + */ + +- (instancetype)initWithIdentifier:(NSString *)identifier + name:(NSString *)name + sectionTitle:(NSString *)title + sectionDetailText:(NSString *)detailText + identifierForCellTitle:(NSString *)identifierForCellTitle + maxAllowed:(NSUInteger)maxAllowed + formSteps:(NSArray *)formSteps + detailTextIdentifiers:(NSArray *)detailTextIdentifiers NS_DESIGNATED_INITIALIZER; + +@property (nonatomic, readonly, copy) NSString *identifier; +@property (nonatomic, readonly, copy) NSString *name; +@property (nonatomic, readonly, copy) NSString *sectionTitle; +@property (nonatomic, readonly, copy) NSString *sectionDetailText; +@property (nonatomic, readonly, copy) NSString *identifierForCellTitle; +@property (nonatomic, readonly) NSUInteger maxAllowed; +@property (nonatomic, readonly, copy) NSArray *formSteps; +@property (nonatomic, readonly, copy) NSArray *detailTextIdentifiers; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ResearchKit/Common/ORKRelativeGroup.m b/ResearchKit/Common/ORKRelativeGroup.m new file mode 100644 index 0000000000..6cb43cb644 --- /dev/null +++ b/ResearchKit/Common/ORKRelativeGroup.m @@ -0,0 +1,129 @@ +/* + Copyright (c) 2023, 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 "ORKRelativeGroup.h" + +#import "ORKAnswerFormat_Private.h" +#import "ORKCollectionResult.h" +#import "ORKFormStep.h" +#import "ORKHelpers_Internal.h" + + +@implementation ORKRelativeGroup + +- (instancetype)initWithIdentifier:(NSString *)identifier + name:(NSString *)name + sectionTitle:(NSString *)title + sectionDetailText:(NSString *)detailText + identifierForCellTitle:(NSString *)identifierForCellTitle + maxAllowed:(NSUInteger)maxAllowed + formSteps:(NSArray *)formSteps + detailTextIdentifiers:(NSArray *)detailTextIdentifiers { + self = [super init]; + + if (self) { + _identifier = identifier; + _name = name; + _sectionTitle = title; + _sectionDetailText = detailText; + _identifierForCellTitle = identifierForCellTitle; + _maxAllowed = maxAllowed; + _formSteps = formSteps; + _detailTextIdentifiers = detailTextIdentifiers; + } + + return self; +} + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + ORK_ENCODE_OBJ(aCoder, identifier); + ORK_ENCODE_OBJ(aCoder, name); + ORK_ENCODE_OBJ(aCoder, sectionTitle); + ORK_ENCODE_OBJ(aCoder, sectionDetailText); + ORK_ENCODE_OBJ(aCoder, identifierForCellTitle); + ORK_ENCODE_INTEGER(aCoder, maxAllowed); + ORK_ENCODE_OBJ(aCoder, formSteps); + ORK_ENCODE_OBJ(aCoder, detailTextIdentifiers); +} + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wobjc-designated-initializers" +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + self = [super init]; + if (self) { + ORK_DECODE_OBJ_CLASS(aDecoder, identifier, NSString); + ORK_DECODE_OBJ_CLASS(aDecoder, name, NSString); + ORK_DECODE_OBJ_CLASS(aDecoder, sectionTitle, NSString); + ORK_DECODE_OBJ_CLASS(aDecoder, sectionDetailText, NSString); + ORK_DECODE_OBJ_CLASS(aDecoder, identifierForCellTitle, NSString); + ORK_DECODE_INTEGER(aDecoder, maxAllowed); + ORK_DECODE_OBJ_ARRAY(aDecoder, formSteps, ORKFormStep); + ORK_DECODE_OBJ_ARRAY(aDecoder, detailTextIdentifiers, NSString); + } + return self; +} + +- (nonnull id)copyWithZone:(nullable NSZone *)zone { + ORKRelativeGroup *relativeGroup = [[[self class] allocWithZone:zone] initWithIdentifier:[_identifier copy] + name:[_name copy] + sectionTitle:[_sectionTitle copy] + sectionDetailText:[_sectionDetailText copy] + identifierForCellTitle:[_identifierForCellTitle copy] + maxAllowed:_maxAllowed + formSteps:[_formSteps copy] + detailTextIdentifiers:[_detailTextIdentifiers copy]]; + return relativeGroup; +} + +- (BOOL)isEqual:(id)object { + if ([self class] != [object class]) { + return NO; + } + + __typeof(self) castObject = object; + return (ORKEqualObjects(self.identifier, castObject.identifier) + && ORKEqualObjects(self.name, castObject.name) + && ORKEqualObjects(self.sectionTitle, castObject.sectionTitle) + && ORKEqualObjects(self.sectionDetailText, castObject.sectionDetailText) + && ORKEqualObjects(self.identifierForCellTitle, castObject.identifierForCellTitle) + && ORKEqualObjects(self.formSteps, castObject.formSteps) + && ORKEqualObjects(self.detailTextIdentifiers, castObject.detailTextIdentifiers) + && self.maxAllowed == castObject.maxAllowed); +} + +- (NSUInteger)hash { + return super.hash ^ _identifier.hash ^ _name.hash ^ _sectionTitle.hash ^ _sectionDetailText.hash ^ _identifierForCellTitle.hash ^ _formSteps.hash ^ _detailTextIdentifiers.hash; +} + +@end diff --git a/ResearchKit/Common/ORKResult.h b/ResearchKit/Common/ORKResult.h index fe5990f706..7063946d93 100644 --- a/ResearchKit/Common/ORKResult.h +++ b/ResearchKit/Common/ORKResult.h @@ -32,8 +32,6 @@ #if TARGET_OS_IOS || TARGET_OS_VISION #import -#endif - NS_ASSUME_NONNULL_BEGIN diff --git a/ResearchKit/Common/ORKResultPredicate.h b/ResearchKit/Common/ORKResultPredicate.h index a9a4f54c99..04f868861a 100644 --- a/ResearchKit/Common/ORKResultPredicate.h +++ b/ResearchKit/Common/ORKResultPredicate.h @@ -540,6 +540,7 @@ within the specified `NSTimeInterval` values. refer to the contained `ORKConsentSignatureResult` corresponding to the signature collected by the consent review step. + @param didConsent Boolean to indicate if the user consented. @return A result predicate. */ + (NSPredicate *)predicateForConsentWithResultSelector:(ORKResultSelector *)resultSelector diff --git a/ResearchKit/Common/ORKResult_Private.h b/ResearchKit/Common/ORKResult_Private.h index f7626d3ba5..62b4e36b6f 100644 --- a/ResearchKit/Common/ORKResult_Private.h +++ b/ResearchKit/Common/ORKResult_Private.h @@ -30,8 +30,6 @@ #if TARGET_OS_IOS || TARGET_OS_VISION #import -#endif - NS_ASSUME_NONNULL_BEGIN diff --git a/ResearchKit/Common/ORKSkin.h b/ResearchKit/Common/ORKSkin.h index 1f196026ab..26a9241dbe 100644 --- a/ResearchKit/Common/ORKSkin.h +++ b/ResearchKit/Common/ORKSkin.h @@ -28,12 +28,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - #import #if TARGET_OS_IOS || TARGET_OS_VISION #import -#endif - NS_ASSUME_NONNULL_BEGIN diff --git a/ResearchKit/Common/ORKSkin_Private.h b/ResearchKit/Common/ORKSkin_Private.h index c57c0c04b7..28e8e4c62c 100644 --- a/ResearchKit/Common/ORKSkin_Private.h +++ b/ResearchKit/Common/ORKSkin_Private.h @@ -33,8 +33,6 @@ #if TARGET_OS_IOS || TARGET_OS_VISION #import -#endif - #if TARGET_OS_IOS || TARGET_OS_VISION diff --git a/ResearchKit/Common/ORKStep.h b/ResearchKit/Common/ORKStep.h index 7e040423a1..e3f9855b08 100644 --- a/ResearchKit/Common/ORKStep.h +++ b/ResearchKit/Common/ORKStep.h @@ -35,6 +35,7 @@ #import #import +#import @class HKObjectType; @class ORKResult; @@ -51,19 +52,19 @@ ORK_EXTERN NSString *const ORKNullStepIdentifier ORK_AVAILABLE_DECL; The base object for composing a task. ``ORKStep`` is the base class for the steps that can compose a task for presentation - in an ``ORKTaskViewController`` object. Each ``ORKStep`` object represents one logical piece of data + in an ORKTaskViewController object. Each ``ORKStep`` object represents one logical piece of data entry or activity in a larger task. A step can be a question, an active test, or a simple instruction. Pair an ``ORKStep`` - subclass with an ``ORKStepViewController`` subclass to display the step. + subclass with an ORKStepViewController subclass to display the step. To use a step, instantiate an ``ORKStep`` object and populate its properties. Add the step to a task, - such as an ``ORKOrderedTask`` object, then present the task using ``ORKTaskViewController``. + such as an ``ORKOrderedTask`` object, then present the task using ORKTaskViewController. To implement a new type of step, subclass ``ORKStep`` and add your additional - properties.Then subclass ``ORKStepViewController`` and implement + properties.Then subclass ORKStepViewController and implement your user interface. If your step is timed, or requires sensor data collection, - subclass ``ORKActiveStep`` and ``ORKActiveStepViewController``. + subclass ``ORKActiveStep`` and ORKActiveStepViewController. */ ORK_CLASS_AVAILABLE API_AVAILABLE(ios(11.0), watchos(6.0)) @@ -180,14 +181,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; @@ -240,7 +241,7 @@ ORK_CLASS_AVAILABLE API_AVAILABLE(ios(11.0), watchos(6.0)) Checks the parameters of the step and throws exceptions on invalid parameters. This method is called when there is a need to validate the step's parameters, which is typically - the case when adding a step to an ``ORKStepViewController`` object, and when presenting the + the case when adding a step to an ORKStepViewController object, and when presenting the step view controller. Subclasses should override this method to provide validation of their additional @@ -272,7 +273,7 @@ API_AVAILABLE(ios(11)) /** A Boolean value indicating if the body items of the step should build in. - Default value is ``NO`` resulting in all body items being displayed. Set to ``YES`` to + Default value is NO resulting in all body items being displayed. Set to YES to only show the first item and subsequent items will build in on continue. */ @property (nonatomic, assign) BOOL buildInBodyItems API_AVAILABLE(ios(11)) API_UNAVAILABLE(watchos); @@ -303,7 +304,7 @@ API_AVAILABLE(ios(11)) /** A view controller that positions an image inside an image view that the step uses. - Depending on the subclass of the step, ``ORKStepView`` uses a specific ``UIImageView``, and + Depending on the subclass of the step, ORKStepView uses a specific UIImageView, and ``imageContentMode`` sets the content mode of used image view. */ @@ -313,4 +314,7 @@ API_AVAILABLE(ios(11)) #endif +#pragma mark - watchOS / VisionOS + + NS_ASSUME_NONNULL_END diff --git a/ResearchKit/Common/ORKStep_Private.h b/ResearchKit/Common/ORKStep_Private.h index a2bb23850c..0d85ca8ad8 100644 --- a/ResearchKit/Common/ORKStep_Private.h +++ b/ResearchKit/Common/ORKStep_Private.h @@ -31,7 +31,6 @@ #if TARGET_OS_IOS || TARGET_OS_VISION #import -#endif NS_ASSUME_NONNULL_BEGIN diff --git a/ResearchKit/Common/ORKTableStep.h b/ResearchKit/Common/ORKTableStep.h index 7753204cbc..247a1cb6de 100644 --- a/ResearchKit/Common/ORKTableStep.h +++ b/ResearchKit/Common/ORKTableStep.h @@ -182,6 +182,11 @@ ORK_CLASS_AVAILABLE */ @property (nonatomic) BOOL pinNavigationContainer; +/** + A property that manually overrides the bottomPadding of the content view + */ +@property (nonatomic, copy, nullable) NSNumber *bottomPadding; + /** Returns the number of sections in the tableview used to display this step. Default = `1`. diff --git a/ResearchKit/Common/ORKTableStep.m b/ResearchKit/Common/ORKTableStep.m index 01c7c719ba..7fa8771133 100644 --- a/ResearchKit/Common/ORKTableStep.m +++ b/ResearchKit/Common/ORKTableStep.m @@ -169,6 +169,8 @@ - (instancetype)copyWithZone:(NSZone *)zone { step->_bulletType = _bulletType; step->_bulletIconNames = ORKArrayCopyObjects(_bulletIconNames); step->_allowsSelection = _allowsSelection; + step->_bottomPadding = _bottomPadding; + return step; } @@ -186,6 +188,7 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder { ORK_DECODE_OBJ_ARRAY(aDecoder, bulletIconNames, NSString); ORK_DECODE_BOOL(aDecoder, allowsSelection); ORK_DECODE_BOOL(aDecoder, pinNavigationContainer); + ORK_DECODE_OBJ_CLASS(aDecoder, bottomPadding, NSNumber); } return self; } @@ -197,6 +200,7 @@ - (void)encodeWithCoder:(NSCoder *)aCoder { ORK_ENCODE_OBJ(aCoder, bulletIconNames); ORK_ENCODE_BOOL(aCoder, allowsSelection); ORK_ENCODE_BOOL(aCoder, pinNavigationContainer); + ORK_ENCODE_OBJ(aCoder, bottomPadding); } #pragma mark - Equality @@ -210,11 +214,12 @@ - (BOOL)isEqual:(id)object { && (self.bulletType == castObject.bulletType) && (self.allowsSelection == castObject.allowsSelection) && ORKEqualObjects(self.bulletIconNames, castObject.bulletIconNames) - && self.pinNavigationContainer == castObject.pinNavigationContainer); + && self.pinNavigationContainer == castObject.pinNavigationContainer) + && ORKEqualObjects(self.bottomPadding, castObject.bottomPadding); } - (NSUInteger)hash { - return super.hash ^ self.items.hash ^ self.bulletIconNames.hash ^ (_bulletType ? 0xf : 0x0) ^ (_allowsSelection ? 0xf : 0x0) ^ (_pinNavigationContainer ? 0xf : 0x0); + return super.hash ^ self.items.hash ^ self.bulletIconNames.hash ^ self.bottomPadding.hash ^ (_bulletType ? 0xf : 0x0) ^ (_allowsSelection ? 0xf : 0x0) ^ (_pinNavigationContainer ? 0xf : 0x0); } @end diff --git a/ResearchKit/Common/ORKTask.h b/ResearchKit/Common/ORKTask.h index 144d976acf..e13dcbbf47 100644 --- a/ResearchKit/Common/ORKTask.h +++ b/ResearchKit/Common/ORKTask.h @@ -30,6 +30,8 @@ #import + +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION #import @@ -37,6 +39,7 @@ #import #endif +#import NS_ASSUME_NONNULL_BEGIN @@ -271,6 +274,7 @@ requests access to these HealthKit types. See also: `requestedHealthKitTypesForWriting`. */ +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION @property (nonatomic, copy, readonly, nullable) NSSet *requestedHealthKitTypesForReading; /** @@ -283,7 +287,7 @@ requests access to these HealthKit types. See also: `requestedHealthKitTypesForReading`. */ @property (nonatomic, copy, readonly, nullable) NSSet *requestedHealthKitTypesForWriting; - +#endif /** The set of permissions requested by the task. diff --git a/ResearchKit/Common/ORKTypes.h b/ResearchKit/Common/ORKTypes.h index 47fcfec3e4..43596cc599 100644 --- a/ResearchKit/Common/ORKTypes.h +++ b/ResearchKit/Common/ORKTypes.h @@ -34,7 +34,6 @@ #if TARGET_OS_IOS || TARGET_OS_VISION #import -#endif NS_ASSUME_NONNULL_BEGIN @@ -131,6 +130,15 @@ typedef NS_ENUM(NSInteger, ORKQuestionType) { */ ORKQuestionTypeSES, + /** + In an age question, the participant can enter an age by using an age picker. + */ + ORKQuestionTypeAge, + + /** + In an age question, the participant can enter an age by using an age picker. ORKQuestionTypeYear will be used if the useYearForResult property of the question's ORKAgeAnswerFormat is set to true. + */ + ORKQuestionTypeYear } ORK_ENUM_AVAILABLE; @@ -391,7 +399,6 @@ ORK_EXTERN ORKHeadphoneTypeIdentifier const ORKHeadphoneTypeIdentifierAirPodsPro /// AirPods Max ORK_EXTERN ORKHeadphoneTypeIdentifier const ORKHeadphoneTypeIdentifierAirPodsMax; - /// Lightning and Audio Jack Earpods ORK_EXTERN ORKHeadphoneTypeIdentifier const ORKHeadphoneTypeIdentifierEarPods; diff --git a/ResearchKit/Common/ORKTypes.m b/ResearchKit/Common/ORKTypes.m index 7e587fc7de..402563bb4c 100644 --- a/ResearchKit/Common/ORKTypes.m +++ b/ResearchKit/Common/ORKTypes.m @@ -46,7 +46,6 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE ORKHeadphoneTypeIdentifier const ORKHeadphoneTypeIdentifierAirPodsProGen2 = @"AIRPODSPROV2"; ORKHeadphoneTypeIdentifier const ORKHeadphoneTypeIdentifierAirPodsMax = @"AIRPODSMAX"; - ORKHeadphoneTypeIdentifier const ORKHeadphoneTypeIdentifierEarPods = @"EARPODS"; ORKHeadphoneTypeIdentifier const ORKHeadphoneTypeIdentifierUnknown = @"UNKNOWN"; diff --git a/ResearchKit/Common/ORKVideoCaptureStep.h b/ResearchKit/Common/ORKVideoCaptureStep.h index b1d5992465..16d91b1e84 100644 --- a/ResearchKit/Common/ORKVideoCaptureStep.h +++ b/ResearchKit/Common/ORKVideoCaptureStep.h @@ -89,12 +89,14 @@ ORK_CLASS_AVAILABLE */ @property (nonatomic, getter=isAudioMute) BOOL audioMute; +#if !TARGET_OS_VISION /** Constants indicating the desired torch mode to use The default value is `AVCaptureTorchModeAuto` (see `AVCaptureTorchMode`). */ @property (nonatomic) AVCaptureTorchMode torchMode; +#endif /** Constants indicating the physical position of an AVCaptureDevice's hardware on the system. diff --git a/ResearchKit/Common/ORKWebViewStep.h b/ResearchKit/Common/ORKWebViewStep.h index 348af349ab..57e6cdf7fd 100644 --- a/ResearchKit/Common/ORKWebViewStep.h +++ b/ResearchKit/Common/ORKWebViewStep.h @@ -63,7 +63,7 @@ NS_ASSUME_NONNULL_BEGIN @optional - (UIView * _Nullable)customHeaderViewForSignatureContent; -#if TARGET_OS_IOS +#if TARGET_OS_IOS && !TARGET_OS_VISION @optional - (UIScrollViewKeyboardDismissMode)keyboardDismissModeForCustomView; #endif diff --git a/ResearchKit/Configuration/ResearchKit/ResearchKit-Debug.xcconfig b/ResearchKit/Configuration/ResearchKit/ResearchKit-Debug.xcconfig index 13b6242c58..b78344cbcf 100644 --- a/ResearchKit/Configuration/ResearchKit/ResearchKit-Debug.xcconfig +++ b/ResearchKit/Configuration/ResearchKit/ResearchKit-Debug.xcconfig @@ -8,3 +8,4 @@ RUN_CLANG_STATIC_ANALYZER = YES SUPPORTS_MACCATALYST = NO TARGETED_DEVICE_FAMILY = 1 +DEBUG_INFORMATION_FORMAT = dwarf diff --git a/ResearchKit/Configuration/ResearchKit/ResearchKit-Shared.xcconfig b/ResearchKit/Configuration/ResearchKit/ResearchKit-Shared.xcconfig index 4acb124bda..c1a7ec539d 100644 --- a/ResearchKit/Configuration/ResearchKit/ResearchKit-Shared.xcconfig +++ b/ResearchKit/Configuration/ResearchKit/ResearchKit-Shared.xcconfig @@ -23,6 +23,8 @@ PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*] = SWIFT_VERSION = 5.0 CLANG_STATIC_ANALYZER_MODE = deep +ORK_FRAMEWORK_VERSION_NUMBER = 3.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) ORK_FRAMEWORK_BUILD_NUMBER_CI_FALSE = $(ORK_FRAMEWORK_VERSION_NUMBER) // When not building in CI, assume build and version number are the same @@ -35,6 +37,12 @@ ORK_FRAMEWORK_VERSION_NUMBER_CI_FALSE = $(ORK_FRAMEWORK_DEFAULT_VERSION) // When ORK_FRAMEWORK_VERSION_NUMBER_CI_ = $(ORK_FRAMEWORK_VERSION_NUMBER_CI_FALSE) CURRENT_PROJECT_VERSION = $(VERSION_NUMBER) +// Some CLLocationManager API calls would trigger authorization to use location. The presence of those +// API calls in ResearchKit **at compile time** mean apps that link ResearchKit also need Info.plist entries +// for NSLocationAlwaysAndWhenInUseUsageDescription and NSLocationWhenInUseUsageDescription. +// If your app doesn't use ORKLocationRecorder and doesn't specify these Info.plist strings, disable +// ResearchKit's CLLocationManager authorization +ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION = 1 // Control whether ResearchKit compiles out CLLocationManager calls that would request authorization for location // ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION=0 compiles out the feature @@ -43,6 +51,27 @@ ORK_FEATURE_CLLOCATIONMANAGER_DEFINITIONS = $(ORK_FEATURE_CLLOCATIONMANAGER_DEFI ORK_FEATURE_CLLOCATIONMANAGER_DEFINITIONS_0 = ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION=0 ORK_FEATURE_CLLOCATIONMANAGER_DEFINITIONS_1 = ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION=1 + +// Some HealthKit API calls used within the framework require your app to disclose using HealthKit and provide a reason during app store review. +// If your app does not use HealthKit or any of the ResearchKit HealthKit APIs, disable ResearchKit's HealthKit authorization. +ORK_FEATURE_HEALTHKIT_AUTHORIZATION = 1 + +// GCC Preprocessor definitions +ORK_FEATURE_HEALTHKIT_DEFINITIONS_GCC = $(ORK_FEATURE_HEALTHKIT_DEFINITIONS_GCC_$(ORK_FEATURE_HEALTHKIT_AUTHORIZATION)) +ORK_FEATURE_HEALTHKIT_DEFINITIONS_GCC_0 = ORK_FEATURE_HEALTHKIT_AUTHORIZATION=0 +ORK_FEATURE_HEALTHKIT_DEFINITIONS_GCC_1 = ORK_FEATURE_HEALTHKIT_AUTHORIZATION=1 + +// Swift active compilation conditions +ORK_FEATURE_HEALTHKIT_DEFINITIONS_SWIFT = $(ORK_FEATURE_HEALTHKIT_DEFINITIONS_SWIFT_$(ORK_FEATURE_HEALTHKIT_AUTHORIZATION)) +ORK_FEATURE_HEALTHKIT_DEFINITIONS_SWIFT_0 = +ORK_FEATURE_HEALTHKIT_DEFINITIONS_SWIFT_1 = ORK_FEATURE_HEALTHKIT_AUTHORIZATION + +#include? "../../../../xcconfig/ResearchKit-Shared.xcconfig" + + +#include? "samples/ORKCatalog/ResearchKit-Shared.xcconfig" #include? "../xcconfig/ResearchKit-Shared.xcconfig" -GCC_PREPROCESSOR_DEFINITIONS = $(inherited) GLES_SILENCE_DEPRECATION=1 $(ORK_GCC_PREPROCESSOR_DEFINITIONS) $(ORK_FEATURE_CLLOCATIONMANAGER_DEFINITIONS) +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) GLES_SILENCE_DEPRECATION=1 $(ORK_GCC_PREPROCESSOR_DEFINITIONS) $(ORK_FEATURE_CLLOCATIONMANAGER_DEFINITIONS) $(ORK_FEATURE_HEALTHKIT_DEFINITIONS_GCC) + +SWIFT_ACTIVE_COMPILATION_CONDITIONS = $(inherited) $(ORK_FEATURE_HEALTHKIT_DEFINITIONS_SWIFT) diff --git a/ResearchKit/Configuration/ResearchKitActiveTask/ResearchKitActiveTask-Debug.xcconfig b/ResearchKit/Configuration/ResearchKitActiveTask/ResearchKitActiveTask-Debug.xcconfig index d6f6ae32f2..3c375c75c3 100644 --- a/ResearchKit/Configuration/ResearchKitActiveTask/ResearchKitActiveTask-Debug.xcconfig +++ b/ResearchKit/Configuration/ResearchKitActiveTask/ResearchKitActiveTask-Debug.xcconfig @@ -3,3 +3,5 @@ // #include "ResearchKitActiveTask-Shared.xcconfig" + +DEBUG_INFORMATION_FORMAT = dwarf diff --git a/ResearchKit/Configuration/ResearchKitTests/ResearchKitTests-Debug.xcconfig b/ResearchKit/Configuration/ResearchKitTests/ResearchKitTests-Debug.xcconfig index 8a8f4b7dc2..bd57c86d49 100644 --- a/ResearchKit/Configuration/ResearchKitTests/ResearchKitTests-Debug.xcconfig +++ b/ResearchKit/Configuration/ResearchKitTests/ResearchKitTests-Debug.xcconfig @@ -5,4 +5,4 @@ #include "ResearchKitTests-Shared.xcconfig" SWIFT_OBJC_BRIDGING_HEADER = ResearchKitTests/ResearchKitTests-Bridging-Header.h - +DEBUG_INFORMATION_FORMAT = dwarf diff --git a/ResearchKit/Configuration/ResearchKitTests/ResearchKitTests-Shared.xcconfig b/ResearchKit/Configuration/ResearchKitTests/ResearchKitTests-Shared.xcconfig index 408185266b..0d21f3484d 100644 --- a/ResearchKit/Configuration/ResearchKitTests/ResearchKitTests-Shared.xcconfig +++ b/ResearchKit/Configuration/ResearchKitTests/ResearchKitTests-Shared.xcconfig @@ -20,6 +20,41 @@ ORK_FEATURE_CLLOCATIONMANAGER_DEFINITIONS = $(ORK_FEATURE_CLLOCATIONMANAGER_DEFI ORK_FEATURE_CLLOCATIONMANAGER_DEFINITIONS_0 = ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION=0 ORK_FEATURE_CLLOCATIONMANAGER_DEFINITIONS_1 = ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION=1 -GCC_PREPROCESSOR_DEFINITIONS = $(inherited) $(ORK_FEATURE_CLLOCATIONMANAGER_DEFINITIONS) + +// Some CLLocationManager API calls would trigger authorization to use location. The presence of those +// API calls in ResearchKit **at compile time** mean apps that link ResearchKit also need Info.plist entries +// for NSLocationAlwaysAndWhenInUseUsageDescription and NSLocationWhenInUseUsageDescription. +// If your app doesn't use ORKLocationRecorder and doesn't specify these Info.plist strings, disable +// ResearchKit's CLLocationManager authorization +ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION = 1 + +// Some HealthKit API calls used within the framework require your app to disclose using HealthKit and provide a reason during app store review. +// If your app does not use HealthKit or any of the ResearchKit HealthKit APIs, disable ResearchKit's HealthKit authorization. +ORK_FEATURE_HEALTHKIT_AUTHORIZATION = 1 + +// GCC Preprocessor definitions +ORK_FEATURE_HEALTHKIT_DEFINITIONS_GCC = $(ORK_FEATURE_HEALTHKIT_DEFINITIONS_GCC_$(ORK_FEATURE_HEALTHKIT_AUTHORIZATION)) +ORK_FEATURE_HEALTHKIT_DEFINITIONS_GCC_0 = ORK_FEATURE_HEALTHKIT_AUTHORIZATION=0 +ORK_FEATURE_HEALTHKIT_DEFINITIONS_GCC_1 = ORK_FEATURE_HEALTHKIT_AUTHORIZATION=1 + +ORK_FEATURE_CLLOCATIONMANAGER_DEFINITIONS_GCC = $(ORK_FEATURE_CLLOCATIONMANAGER_DEFINITIONS_GCC_$(ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION)) +ORK_FEATURE_CLLOCATIONMANAGER_DEFINITIONS_GCC_0 = ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION=0 +ORK_FEATURE_CLLOCATIONMANAGER_DEFINITIONS_GCC_1 = ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION=1 + +// Swift active compilation conditions +ORK_FEATURE_HEALTHKIT_DEFINITIONS_SWIFT = $(ORK_FEATURE_HEALTHKIT_DEFINITIONS_SWIFT_$(ORK_FEATURE_HEALTHKIT_AUTHORIZATION)) +ORK_FEATURE_HEALTHKIT_DEFINITIONS_SWIFT_0 = + +ORK_FEATURE_HEALTHKIT_DEFINITIONS_SWIFT_1 = ORK_FEATURE_HEALTHKIT_AUTHORIZATION + +ORK_FEATURE_CLLOCATIONMANAGER_DEFINITIONS_SWIFT = $(ORK_FEATURE_CLLOCATIONMANAGER_DEFINITIONS_SWIFT_$(ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION)) +ORK_FEATURE_CLLOCATIONMANAGER_DEFINITIONS_SWIFT_0 = +ORK_FEATURE_CLLOCATIONMANAGER_DEFINITIONS_SWIFT_1 = ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION + +#include? "../xcconfig/ResearchKit-Shared.xcconfig" + +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) GLES_SILENCE_DEPRECATION=1 $(ORK_FEATURE_HEALTHKIT_DEFINITIONS_GCC) $(ORK_FEATURE_CLLOCATIONMANAGER_DEFINITIONS_GCC) + +SWIFT_ACTIVE_COMPILATION_CONDITIONS = $(inherited) $(ORK_FEATURE_HEALTHKIT_DEFINITIONS_SWIFT) $(ORK_FEATURE_CLLOCATIONMANAGER_DEFINITIONS_SWIFT) diff --git a/ResearchKit/Configuration/ResearchKitUI-iOS/ResearchKitUI-iOS-Debug.xcconfig b/ResearchKit/Configuration/ResearchKitUI-iOS/ResearchKitUI-iOS-Debug.xcconfig new file mode 100644 index 0000000000..52a28ec614 --- /dev/null +++ b/ResearchKit/Configuration/ResearchKitUI-iOS/ResearchKitUI-iOS-Debug.xcconfig @@ -0,0 +1,7 @@ +// +// ResearchKitUI-iOS-Debug.xcconfig +// + +#include "ResearchKitUI-iOS-Shared.xcconfig" + +DEBUG_INFORMATION_FORMAT = dwarf diff --git a/ResearchKit/Localized/en.lproj/ResearchKit.strings b/ResearchKit/Localized/en.lproj/ResearchKit.strings index 2fd10cd9c3..1a1a0b3bd0 100644 --- a/ResearchKit/Localized/en.lproj/ResearchKit.strings +++ b/ResearchKit/Localized/en.lproj/ResearchKit.strings @@ -609,7 +609,7 @@ "ENVIRONMENTSPL_UNIT" = "dBA"; "ENVIRONMENTSPL_CALCULATING" = "Measuring…"; "ENVIRONMENTSPL_THRESHOLD" = "Threshold: %@ dBA"; -"ENVIRONMENTSPL_OK" = "Background Noise OK"; +"ENVIRONMENTSPL_OK" = "Good"; "ENVIRONMENTSPL_NOISE" = "Too Loud"; "ENVIRONMENTSPL_TITLE_2" = "Find a Quiet Place"; "ENVIRONMENTSPL_INTRO_TEXT_2" = "For accurate results, this task should be completed in a quiet location. Once the background noise measurement reading is completed, you may continue."; @@ -794,3 +794,21 @@ "KNEE_RANGE_OF_MOTION_TOUCH_ANYWHERE_STEP_INSTRUCTION_RIGHT" = "Place your device on your right knee.\n\nTap the screen and extend your right knee as far as you can.\n"; "KNEE_RANGE_OF_MOTION_SPOKEN_INSTRUCTION_LEFT" = "When you are done, return your left knee to the start position.\n\nThen tap anywhere."; "KNEE_RANGE_OF_MOTION_SPOKEN_INSTRUCTION_RIGHT" = "When you are done, return your right knee to the start position.\n\nThen tap anywhere."; + +/* Family History */ +"FAMILY_HISTORY_I_DONT_KNOW" = "I don't know"; +"FAMILY_HISTORY_CONDITIONS_FORM_ITEM_TEXT" = "Have they been diagnosed with any of the following diseases or health conditions?"; +"FAMILY_HISTORY_CONDITIONS_STEP_TITLE" = "Health Conditions"; +"FAMILY_HISTORY_CONDITIONS_STEP_DESCRIPTION" = "Answer these questions to the best of your ability"; +"FAMILY_HISTORY_COMPLETION_STEP_TITLE" = "Task Complete"; +"FAMILY_HISTORY_NONE_OF_THE_ABOVE" = "None of the above"; +"FAMILY_HISTORY_PREFER_NOT_TO_ANSWER" = "I prefer not to answer"; +"FAMILY_HISTORY_CONDITIONS" = "Conditions"; +"FAMILY_HISTORY_NONE_SELECTED" = "None selected"; +"FAMILY_HISTORY_EDIT_ENTRY" = "Edit Entry"; +"FAMILY_HISTORY_DELETE_ENTRY_TITLE" = "Are you sure you want to delete this entry from your Family Health History?"; +"FAMILY_HISTORY_DELETE_ENTRY" = "Delete Entry"; +"FAMILY_HISTORY_CANCEL" = "Cancel"; +"FAMILY_HISTORY_ADD" = "Add %@"; +"FAMILY_HISTORY_CONDITIONS_STEP_DESCRIPTION_TEMP" = "Confirm any diagnosed diseases or health conditions."; +"AX_FAMILY_HISTORY_EDIT_BUTTON" = "Edit button"; diff --git a/ResearchKit/ResearchKit.docc/API Collections/ActiveTasks.md b/ResearchKit/ResearchKit.docc/API Collections/ActiveTasks.md deleted file mode 100644 index 3859e23908..0000000000 --- a/ResearchKit/ResearchKit.docc/API Collections/ActiveTasks.md +++ /dev/null @@ -1,19 +0,0 @@ -# Active tasks - -Present a specific question and collect an answer. - -## Topics - -### Essentials - -- ``ORKActiveStep`` -- ``ORKActiveStepViewController`` - -### Audio - -- ``ORKInvalidDBHLValue`` -- ``ORKAudiometryTimestampProvider`` -- ``ORKAudioChannel`` -- ``ORKSpeechRecognizerLocale`` -- ``ORKAudiometryProtocol`` - diff --git a/ResearchKit/ResearchKit.docc/API Collections/AnswerFormats.md b/ResearchKit/ResearchKit.docc/API Collections/AnswerFormats.md index fad0f0a63b..3ee34b98c9 100644 --- a/ResearchKit/ResearchKit.docc/API Collections/AnswerFormats.md +++ b/ResearchKit/ResearchKit.docc/API Collections/AnswerFormats.md @@ -4,12 +4,21 @@ Present a specific question and collect an answer. ## Topics +### Age + +- ``ORKAgeAnswerFormat`` + +### Color Choice + +- ``ORKColorChoiceAnswerFormat`` +- ``ORKColorChoice`` + ### Essentials - ``ORKAnswerFormat`` - ``ORKNumericPrecision`` -### height, and weight +### Height, and Weight - ``ORKHeightAnswerFormat`` - ``ORKWeightAnswerFormat`` diff --git a/ResearchKit/ResearchKit.docc/API Collections/Consent.md b/ResearchKit/ResearchKit.docc/API Collections/Consent.md index 0333d42605..5b2514ae41 100644 --- a/ResearchKit/ResearchKit.docc/API Collections/Consent.md +++ b/ResearchKit/ResearchKit.docc/API Collections/Consent.md @@ -11,11 +11,10 @@ Obtain a participant's consent to collect research data. - ``ORKCustomSignatureAccessoryViewDelegate`` - ``ORKCustomSignatureAccessoryViewProtocol`` - ``ORKCustomSignatureAccessoryViewProvider`` -- ``ORKCustomSignatureFooterViewStatusDelegate`` - ``ORKConsentSectionType`` ### Signature acquisition -- ``ORKSignatureView`` - ``ORKConsentSignature`` -- ``ORKSignatureViewDelegate`` +- ``ORKSignatureFormatter`` +- ``ORKHTMLPDFWriter`` diff --git a/ResearchKit/ResearchKit.docc/API Collections/Constants.md b/ResearchKit/ResearchKit.docc/API Collections/Constants.md new file mode 100644 index 0000000000..6f2053d923 --- /dev/null +++ b/ResearchKit/ResearchKit.docc/API Collections/Constants.md @@ -0,0 +1,46 @@ +# Constants + +Constants utilized throughout ResearchKit. + +## Topics + +### Constants + +- ``CheckmarkViewDimension`` +- ``ORKAuxiliaryImageTintColorKey`` +- ``ORKBackgroundColorKey`` +- ``ORKBlueHighlightColorKey`` +- ``ORKBodyToBodyPaddingStandard`` +- ``ORKBodyToBodyParagraphPaddingStandard`` +- ``ORKBulletItemTextColorKey`` +- ``ORKCaptionTextColorKey`` +- ``ORKCardDefaultBorderWidth`` +- ``ORKCardDefaultCornerRadii`` +- ``ORKCardDefaultFontSize`` +- ``ORKConsentBackgroundColorKey`` +- ``ORKDarkTintColorKey`` +- ``ORKDoneButtonPressedKey`` +- ``ORKEffectViewOpacityHidden`` +- ``ORKEffectViewOpacityVisible`` +- ``ORKFormStepLargeTextMinimumHeaderHeight`` +- ``ORKFormStepMinimumHeaderHeight`` +- ``ORKImageChoiceButtonCornerRadii`` +- ``ORKLightTintColorKey`` +- ``ORKNavigationContainerColorKey`` +- ``ORKNavigationContainerShadowColorKey`` +- ``ORKProgressLabelColorKey`` +- ``ORKQuestionStepMinimumHeaderHeight`` +- ``ORKResetDoneButtonKey`` +- ``ORKScreenMetricMaxDimension`` +- ``ORKSignatureColorKey`` +- ``ORKStepContainerTitleToBodyTopPaddingStandard`` +- ``ORKStepContentIconImageViewDimension`` +- ``ORKStepTopContentImageChangedKey`` +- ``ORKSurveyItemMargin`` +- ``ORKSurveyTableContainerLeftRightPadding`` +- ``ORKToolBarTintColorKey`` +- ``ORKTopContentImageViewBackgroundColorKey`` +- ``ORKiPadBackgroundViewBottomPadding`` +- ``ORKiPadBackgroundViewColorKey`` +- ``ORKiPadBackgroundViewCornerRadius`` +- ``ORKiPadBackgroundViewLeftRightPadding`` diff --git a/ResearchKit/ResearchKit.docc/API Collections/Enumerations.md b/ResearchKit/ResearchKit.docc/API Collections/Enumerations.md new file mode 100644 index 0000000000..e071b36c68 --- /dev/null +++ b/ResearchKit/ResearchKit.docc/API Collections/Enumerations.md @@ -0,0 +1,17 @@ +# Enumerations + +Enumerations utilized throughout ResearchKit. + +## Topics + +### Enumerations + +- ``ORKAudioChannel`` +- ``ORKBodySagittal`` +- ``ORKHeadphoneTypeIdentifier`` +- ``ORKRequestPermissionsState`` +- ``ORKSampleJSONOptions`` +- ``ORKScreenMetric`` +- ``ORKScreenType`` +- ``ORKSpeechRecognizerLocale`` +- ``ORKTaskFinishReason`` diff --git a/ResearchKit/ResearchKit.docc/API Collections/Helper Functions.md b/ResearchKit/ResearchKit.docc/API Collections/Helper Functions.md new file mode 100644 index 0000000000..3ff116ab7f --- /dev/null +++ b/ResearchKit/ResearchKit.docc/API Collections/Helper Functions.md @@ -0,0 +1,31 @@ +# Helper Functions + +Helper Functions utilized throughout ResearchKit. + +## Topics + +### Helper Functions + +- ``ORKCardLeftRightMarginForWindow`` +- ``ORKColor`` +- ``ORKColorSetColorForKey`` +- ``ORKGetMetricForWindow`` +- ``ORKGetVerticalScreenTypeForWindow`` +- ``ORKNeedWideScreenDesign`` +- ``ORKScrollIndicatorInsetsForScrollView`` +- ``ORKStandardFullScreenLayoutMarginsForView`` +- ``ORKStandardHorizontalMarginForView`` +- ``ORKStandardLayoutMarginsForTableViewCell`` +- ``ORKStandardLeftMarginForTableViewCell`` +- ``ORKStepContainerExtendedLeftRightPaddingForWindow`` +- ``ORKStepContainerFirstItemTopPaddingForWindow`` +- ``ORKStepContainerLeftRightPaddingForWindow`` +- ``ORKStepContainerTitleToBodyTopPaddingForWindow`` +- ``ORKStepContainerTitleToBulletTopPaddingForWindow`` +- ``ORKStepContainerTopContentHeightForWindow`` +- ``ORKStepContainerTopPaddingForWindow`` +- ``ORKTaskProgressMake`` +- ``ORKTaskTotalProgressMake`` +- ``ORKTitleLabelFontTextStyleForWindow`` +- ``ORKUpdateScrollViewBottomInset`` +- ``ORKWidthForSignatureView`` diff --git a/ResearchKit/ResearchKit.docc/API Collections/Results.md b/ResearchKit/ResearchKit.docc/API Collections/Results.md index 7124ef43f1..f525f63d70 100644 --- a/ResearchKit/ResearchKit.docc/API Collections/Results.md +++ b/ResearchKit/ResearchKit.docc/API Collections/Results.md @@ -15,6 +15,10 @@ Obtain information from a step. - ``ORKTaskResultSource`` +### Family History + +- ``ORKFamilyHistoryResult`` + ### File storage - ``ORKFileResult`` @@ -25,14 +29,6 @@ Obtain information from a step. - ``ORKSignatureResult`` - ``ORKConsentSignatureResult`` -### Hearing - -- ``ORKdBHLToneAudiometryResult`` -- ``ORKEnvironmentSPLMeterResult`` -- ``ORKToneAudiometryResult`` -- ``ORKSpeechInNoiseResult`` -- ``ORKSpeechRecognitionResult`` - ### Data collection - ``ORKCollector`` @@ -40,31 +36,8 @@ Obtain information from a step. - ``ORKHealthCorrelationCollector`` - ``ORKDataCollectionManager`` - ``ORKDataCollectionManagerDelegate`` -- ``ORKRangeOfMotionResult`` -- ``ORKReactionTimeResult`` - ``ORKBundleAsset`` -### Dexterity - -- ``ORKTimedWalkResult`` -- ``ORKTowerOfHanoiResult`` -- ``ORKTrailmakingResult`` -- ``ORKHolePegTestResult`` -- ``ORKTappingIntervalResult`` -- ``ORKHolePegTestResult`` -- ``ORKNormalizedReactionTimeResult`` -- ``ORKPSATResult`` - -### Memory - -- ``ORKSpatialSpanMemoryResult`` - -### Vision - -- ``ORKStroopResult`` -- ``ORKAccuracyStroopResult`` -- ``ORKAmslerGridResult`` - ### Surveys - ``ORKQuestionResult`` @@ -88,7 +61,3 @@ Obtain information from a step. ### Web - ``ORKWebViewStepResult`` - -### 3-D models - -- ``ORKUSDZModelManagerResult`` diff --git a/ResearchKit/ResearchKit.docc/API Collections/Steps.md b/ResearchKit/ResearchKit.docc/API Collections/Steps.md index 0162415cf0..19c0d99e50 100644 --- a/ResearchKit/ResearchKit.docc/API Collections/Steps.md +++ b/ResearchKit/ResearchKit.docc/API Collections/Steps.md @@ -11,26 +11,18 @@ Using ResearchKit, you can build apps that obtain consent, give instructions, pr ### Essentials - ``ORKStep`` -- ``ORKStepViewController`` -- ``ORKStepViewControllerDelegate`` ### Active steps - ``ORKActiveStep`` -- ``ORKActiveStepViewController`` - ``ORKMotionActivityCollector`` - ``ORKMotionActivityPermissionType`` -- ``ORKHolePegTestSample`` -- ``ORKEnvironmentSPLMeterStep`` -- ``ORKBodySagittal`` ### Authentication and consent steps - ``ORKLoginStep`` -- ``ORKLoginStepViewController`` - ``ORKSignatureStep`` - ``ORKVerificationStep`` -- ``ORKVerificationStepViewController`` - ``ORKLoginFormItemIdentifierEmail`` - ``ORKLoginFormItemIdentifierPassword`` - ``ORKKeychainWrapper`` @@ -52,18 +44,22 @@ Using ResearchKit, you can build apps that obtain consent, give instructions, pr - ``ORKConsentReviewStep`` - ``ORKConsentSharingStep`` - ``ORKCompletionStep`` -- ``ORKCompletionStepViewController`` -- ``ORKCompletionStepIdentifier`` ### Custom steps - ``ORKCustomStep`` -- ``ORKCustomStepViewController`` + +### Family History + +- ``ORKFamilyHistoryStep`` +- ``ORKConditionStepConfiguration`` +- ``ORKHealthCondition`` +- ``ORKRelatedPerson`` +- ``ORKRelativeGroup`` ### Form steps - ``ORKFormStep`` -- ``ORKFormStepViewController`` - ``ORKBodyItem`` - ``ORKBulletType`` - ``ORKBodyItemStyle`` @@ -76,43 +72,31 @@ Using ResearchKit, you can build apps that obtain consent, give instructions, pr ### Image and 3D model steps -- ``ORK3DModelStep`` -- ``ORK3DModelManager`` -- ``ORKUSDZModelManager`` -- ``ORK3DModelManagerProtocol`` - ``ORKFrontFacingCameraStep`` - ``ORKImageCaptureStep`` -- ``ORKPlaybackButton`` ### Location steps - ``ORKLocation`` - ``ORKLocationPermissionType`` -- ``ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION`` ### Passcode steps - ``ORKPasscodeStep`` -- ``ORKPasscodeViewController`` -- ``ORKPasscodeDelegate`` - ``ORKPasscodeFlow`` - ``ORKPasscodeType`` ### PDF and page steps - ``ORKPDFViewerStep`` -- ``ORKPDFViewerStepViewController`` - ``ORKPageStep`` -- ``ORKPageStepViewController`` - ``ORKPDFViewerActionBarOption`` ### Question and instruction steps - ``ORKQuestionStep`` - ``ORKInstructionStep`` -- ``ORKInstructionStepViewController`` - ``ORKLearnMoreInstructionStep`` -- ``ORKLearnMoreStepViewController`` - ``ORKNavigablePageStep`` ### Registration steps @@ -130,8 +114,6 @@ Using ResearchKit, you can build apps that obtain consent, give instructions, pr ### Review steps - ``ORKReviewStep`` -- ``ORKReviewViewController`` -- ``ORKReviewViewControllerDelegate`` ### Step recording @@ -142,7 +124,6 @@ Using ResearchKit, you can build apps that obtain consent, give instructions, pr ### Table steps - ``ORKTableStep`` -- ``ORKTableStepViewController`` - ``ORKTableStepSource`` ### Video steps @@ -157,19 +138,12 @@ Using ResearchKit, you can build apps that obtain consent, give instructions, pr ### Wait steps - ``ORKWaitStep`` -- ``ORKWaitStepViewController`` ### Web steps - ``ORKWebViewStep`` -- ``ORKWebViewStepDelegate`` -- ``ORKWebViewStepViewController`` ### Other steps - ``ORKSecondaryTaskStep`` - ``ORKSkipStepNavigationRule`` -- ``ORKTouchAnywhereStep`` -- ``ORKTouchAnywhereStepViewController`` -- ``ORKAccuracyStroopStep`` - diff --git a/ResearchKit/ResearchKit.docc/API Collections/Tasks.md b/ResearchKit/ResearchKit.docc/API Collections/Tasks.md index f3867c9866..de05362f06 100644 --- a/ResearchKit/ResearchKit.docc/API Collections/Tasks.md +++ b/ResearchKit/ResearchKit.docc/API Collections/Tasks.md @@ -13,44 +13,20 @@ A task can be a simple ordered sequence of steps, or be dynamic with previous re - ``ORKTask`` - ``ORKOrderedTask`` - ``ORKNavigableOrderedTask`` -- ``SwiftUIViewFactory`` - -### View controllers - -- ``ORKTaskViewController`` -- ``ORKTaskViewControllerDelegate`` -- ``ORKTaskViewControllerFinishReason`` -- ``ORKTaskViewControllerReviewMode`` ### Audio -- ``ORKAudiometryStimulus`` - ``ORKAudioRecorderConfiguration`` -- ``ORKToneAudiometrySample`` -- ``ORKdBHLToneAudiometryFrequencySample`` -- ``ORKdBHLTaskContext`` -- ``ORKdBHLToneAudiometryUnit`` ### Dexterity - ``ORKPredefinedTaskHandOption`` - ``ORKPredefinedTaskOption`` - ``ORKPredefinedTaskLimbOption`` -- ``ORKPSATSample`` - ``ORKPSATPresentationMode`` -- ``ORKTappingSample`` -- ``ORKTappingButtonIdentifier`` -- ``ORKTowerOfHanoiMove`` -- ``ORKTrailmakingTap`` - ``ORKTrailMakingTypeIdentifier`` - ``ORKTremorActiveTaskOption`` -### Memory - -- ``ORKSpatialSpanMemoryGameRecord`` -- ``ORKSpatialSpanMemoryGameStatus`` -- ``ORKSpatialSpanMemoryGameTouchSample`` - ### Navigation - ``ORKStepNavigationRule`` @@ -63,7 +39,6 @@ A task can be a simple ordered sequence of steps, or be dynamic with previous re - ``ORKStepModifier`` - ``ORKResultPredicateTaskIdentifierVariableName`` - ``ORKNullStepIdentifier`` -- ``ORKStepViewControllerNavigationDirection`` - ``ORKKeyValueStepModifier`` - ``ORKResultPredicate`` - ``ORKResultSelector`` @@ -73,4 +48,3 @@ A task can be a simple ordered sequence of steps, or be dynamic with previous re - ``ORKTaskProgress`` - ``ORKTaskTotalProgress`` - ``ORKProgressIndicatorType`` -- ``ORKTaskViewControllerProgressMode`` diff --git a/ResearchKit/ResearchKit.docc/ResearchKit.md b/ResearchKit/ResearchKit.docc/ResearchKit.md index be535bbc87..af95ed594f 100644 --- a/ResearchKit/ResearchKit.docc/ResearchKit.md +++ b/ResearchKit/ResearchKit.docc/ResearchKit.md @@ -38,4 +38,19 @@ capabilities of frameworks such as HealthKit and CoreMotion. - - -- +- + +### Articles + +- +- +- +- +- +- + +### Other + +- +- +- diff --git a/ResearchKit/ResearchKit.docc/Resources/Conditional-Logic-in-ResearchKit/Conditional-Logic-in-ResearchKit.md b/ResearchKit/ResearchKit.docc/Resources/Conditional-Logic-in-ResearchKit/Conditional-Logic-in-ResearchKit.md new file mode 100644 index 0000000000..74e701c217 --- /dev/null +++ b/ResearchKit/ResearchKit.docc/Resources/Conditional-Logic-in-ResearchKit/Conditional-Logic-in-ResearchKit.md @@ -0,0 +1,115 @@ +# Conditional Logic in ResearchKit + +How to use conditional logic for ResearchKit steps and forms. + +## Understanding Conditional Logic + +When presenting a survey or task, conditionally show specific content based on the participant's responses. ResearchKit provides two solutions for conditional logic. + +- **Step Navigation rules**: Conditionally navigate to a specific step based on the participant's response. +- **Form Item Visibility rules** - Conditionally hide or show specific form questions based on results from the same form or a form within another step. + + + +### Navigating Steps Conditionally +Review the following classes to conditionally navigate to or skip specific steps during an `ORKTask`. + +- `ORKResultSelector` - A class that identifies a result within a set of task results. +- `ORKResultPredicate` - Creates a predicate by accepting an `ORKResultSelector` and the expected result. +- `ORKPredicateStepNavigationRule` - A class that determines what step to navigate to if a given `ORKResultPredicate` is true. +- `ORKNavigableOrderedTask` - A subclass of the `ORKOrderedTask` that can accept one or more `ORKPredicateStepNavigationRule` objects and apply the expected conditional navigation. + + +The task for this example includes the steps seen below. + +![Step 1](conditional-logic-in-researchKit-step1) +![Step 2](conditional-logic-in-researchKit-step2) +![Step 3](conditional-logic-in-researchKit-step3) + +The conditional logic is based on answering `Yes` or `No` for the first question ("Do you like Apples?"): + +- **Answering yes**: navigates to the second screen to select your favorite apple. +- **Answering no**: skips the second screen and navigates directly to the completion step. + +```swift + +//Construct Steps +let boolFormStep = ORKFormStep(identifier: "FormStep1") +boolFormStep.title = "Apple Task" +boolFormStep.text = "Please answer the following question." + +let boolAnswerFormat = ORKAnswerFormat.booleanAnswerFormat() +let boolFormItem = ORKFormItem(identifier: "BooleanFormItemIdentifier", + text: "Do you like Apples?", + answerFormat: boolAnswerFormat) + +boolFormStep.formItems = [boolFormItem] + +let appleTextChoiceFormStep = appleTextChoiceFormStepExample() +let completionStep = completionStepExample() + +//Conditional Logic +let boolResultSelector = ORKResultSelector(stepIdentifier: boolFormStep.identifier, resultIdentifier: boolFormItem.identifier) +let boolResultPredicate = ORKResultPredicate.predicateForBooleanQuestionResult(with: boolResultSelector, expectedAnswer: false) +let navigationRule = ORKPredicateStepNavigationRule(resultPredicatesAndDestinationStepIdentifiers: [ (boolResultPredicate, completionStep.identifier) ]) + +//Construct Navigable Task + Set Navigation Rule +let navigableTask = ORKNavigableOrderedTask(identifier: "NavigableTaskIdentifier", steps: [formStep1, appleTextChoiceFormStep, completionStep]) +navigableTask.setNavigationRule(navigationRule, forTriggerStepIdentifier: formStep1.identifier) +``` + +Selecting yes: + +@Video(source: "conditional-logic-in-researchKit-step-yes") + +Selecting no: + +@Video(source: "conditional-logic-in-researchKit-step-no") + +### Managing Form Item Visibility + +Familiarize yourself with the following classes to conditionally hide or show a question based on results from questions within the same form. + +- `ORKResultSelector` - A class that identifies a result within a set of task results. +- `ORKResultPredicate` - Creates a predicate by accepting an `ORKResultSelector` and the expected result. +- `ORKPredicateFormItemVisibilityRule` - A class that determines if the form item it's attached to is hidden or visible if a given `ORKResultPredicate` is true. + +Following the previous example, use the same questions but now with both on the same page. + + +- **Answering yes**: makes the apple choice question visible. +- **Answering no**: hides the apple choice question if visible. + + +```swift +//Construct FormStep +let formStep = ORKFormStep(identifier: "FormStep1") +formStep.title = "Apple Task" +formStep.text = "Please answer the following question." + +let boolAnswerFormat = ORKAnswerFormat.booleanAnswerFormat() +let boolFormItem = ORKFormItem(identifier: "BooleanFormItemIdentifier", + text: "Do you like Apples?", + answerFormat: boolAnswerFormat) + + +let appleChoiceFormItem = appleChoiceFormItem() + +formStep.formItems = [boolFormItem, appleChoiceFormItem] + +let completionStep = completionStepExample() + +//Conditional Logic +let resultSelector: ORKResultSelector = .init(stepIdentifier: formStep.identifier, resultIdentifier: boolFormItem.identifier) +let predicate = ORKResultPredicate.predicateForBooleanQuestionResult(with: resultSelector, expectedAnswer: true) +let visibilityRule = ORKPredicateFormItemVisibilityRule(predicate: predicate) + +appleChoiceFormItem.visibilityRule = visibilityRule + +//Construct Navigable Task + let navigableTask = ORKNavigableOrderedTask(identifier: "NavigableTaskIdentifier", steps: [formStep, completionStep]) +``` + +Selecting yes & no: + +@Video(source: "conditional-logic-in-researchKit-formItem-yes-no") diff --git a/ResearchKit/ResearchKit.docc/Resources/Conditional-Logic-in-ResearchKit/conditional-logic-in-researchKit-formItem-yes-no.mov b/ResearchKit/ResearchKit.docc/Resources/Conditional-Logic-in-ResearchKit/conditional-logic-in-researchKit-formItem-yes-no.mov new file mode 100644 index 0000000000..2476c33b8d Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Conditional-Logic-in-ResearchKit/conditional-logic-in-researchKit-formItem-yes-no.mov differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Conditional-Logic-in-ResearchKit/conditional-logic-in-researchKit-step-no.mov b/ResearchKit/ResearchKit.docc/Resources/Conditional-Logic-in-ResearchKit/conditional-logic-in-researchKit-step-no.mov new file mode 100644 index 0000000000..a156b213c7 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Conditional-Logic-in-ResearchKit/conditional-logic-in-researchKit-step-no.mov differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Conditional-Logic-in-ResearchKit/conditional-logic-in-researchKit-step-yes.mov b/ResearchKit/ResearchKit.docc/Resources/Conditional-Logic-in-ResearchKit/conditional-logic-in-researchKit-step-yes.mov new file mode 100644 index 0000000000..71fe719d3c Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Conditional-Logic-in-ResearchKit/conditional-logic-in-researchKit-step-yes.mov differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Conditional-Logic-in-ResearchKit/conditional-logic-in-researchKit-step1@3x.png b/ResearchKit/ResearchKit.docc/Resources/Conditional-Logic-in-ResearchKit/conditional-logic-in-researchKit-step1@3x.png new file mode 100644 index 0000000000..0ba840de97 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Conditional-Logic-in-ResearchKit/conditional-logic-in-researchKit-step1@3x.png differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Conditional-Logic-in-ResearchKit/conditional-logic-in-researchKit-step2@3x.png b/ResearchKit/ResearchKit.docc/Resources/Conditional-Logic-in-ResearchKit/conditional-logic-in-researchKit-step2@3x.png new file mode 100644 index 0000000000..ee81a02352 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Conditional-Logic-in-ResearchKit/conditional-logic-in-researchKit-step2@3x.png differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Conditional-Logic-in-ResearchKit/conditional-logic-in-researchKit-step3@3x.png b/ResearchKit/ResearchKit.docc/Resources/Conditional-Logic-in-ResearchKit/conditional-logic-in-researchKit-step3@3x.png new file mode 100644 index 0000000000..4fc4889eb3 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Conditional-Logic-in-ResearchKit/conditional-logic-in-researchKit-step3@3x.png differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Configurations/Configurations.md b/ResearchKit/ResearchKit.docc/Resources/Configurations/Configurations.md index 90ab410655..93c1af768f 100644 --- a/ResearchKit/ResearchKit.docc/Resources/Configurations/Configurations.md +++ b/ResearchKit/ResearchKit.docc/Resources/Configurations/Configurations.md @@ -8,8 +8,6 @@ Provide collection and presentation information to a step. - ``ORKAccelerometerRecorderConfiguration`` - ``ORKDeviceMotionRecorderConfiguration`` -- ``ORKHealthClinicalTypeRecorderConfiguration`` -- ``ORKHealthQuantityTypeRecorderConfiguration`` - ``ORKLocationRecorderConfiguration`` - ``ORKPedometerRecorderConfiguration`` - ``ORKRecorderConfiguration`` @@ -26,8 +24,4 @@ Provide collection and presentation information to a step. ### Style selection -- ``ORKTextButton`` -- ``ORKContinueButton`` -- ``ORKBorderedButton`` -- ``ORKBorderedButtonDisabledStyle`` - ``ORKNavigationContainerButtonStyle`` diff --git a/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/Creating-Surveys.md b/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/Creating-Surveys.md new file mode 100644 index 0000000000..86f932ff63 --- /dev/null +++ b/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/Creating-Surveys.md @@ -0,0 +1,198 @@ +# Creating surveys + +Configure and present surveys with ResearchKit. + +## Overview + +A survey is a sequence of questions that you use to collect data from your users. In a ResearchKit app, a survey is composed of a survey task that has a collection of step objects (``ORKStep``). ``ORKFormStep`` instances can be added to the collection of steps to handle one or more questions each such as "What medications are you taking?" or "How many hours did you sleep last night?". + +You can collect results for the individual steps or for the task as a whole. There are two types of survey tasks: an ordered task (``ORKOrderedTask``) and a navigable ordered task (``ORKNavigableOrderedTask``) which can be used to apply conditional logic. + +In an ordered task, the order that the steps appear are always the same. + + +![Ordered Tasks](creating-surveys-ordered-tasks) + +In a navigable ordered task, the order of the tasks can change, or branch out, depending on how the user answered a question in a previous task. + +![Navigable Ordered Tasks](creating-surveys-navigable-ordered-tasks) + +The steps for creating a task to present a survey are: + +1. Create one or more steps +2. Create a task +3. Collect results + +## 1. Create steps + +The survey module provides a form step that can contain one or more questions +(``ORKFormStep``). You can also use an instruction step +(``ORKInstructionStep``) or a video instruction step (``ORKVideoInstructionStep``) to introduce the survey or provide instructions. + +### Instruction step + +An instruction step explains the purpose of a task and provides +instructions for the user. An ``ORKInstructionStep`` object includes an +identifier, title, text, detail text, and an image. An +instruction step doesn't collect any data and yields an empty +``ORKStepResult`` that records how long the instruction was +on screen. + +```swift +let instructionStep = ORKInstructionStep(identifier: "identifier") +instructionStep.title = "Selection Survey" +instructionStep.text = "This survey helps us understand your eligibility for the fitness study." +``` + +Creating a step as shown in the code above, including it in a task, and presenting with a task view controller, yields something like this: + +![Instruction Step](creating-surveys-instruction-step) + +### Form Step + +Whether your survey has one question or several related questions, you can use a form step (``ORKFormStep``) to present them on one page. Each question in a form step is represented as a form item (``ORKFormItem``), each with its +own answer format. + +The result of a form step contains one question result for each form +item. The results are matched to their corresponding form items using +their identifier property. + +The following code shows how to create a form that requests some basic details: + + +```swift +let sectionHeaderFormItem = ORKFormItem(sectionTitle: "Basic Information") +let nameFormItem = ORKFormItem(identifier: "NameIdentifier", text: "What is your name?", answerFormat: ORKTextAnswerFormat()) +let emailFormItem = ORKFormItem(identifier: "EmailIdentifier", text: "What is your email?", answerFormat: ORKEmailAnswerFormat()) +let headacheFormItem = ORKFormItem(identifier: "HeadacheIdentifier", text: "Do you have a headache?", answerFormat: ORKBooleanAnswerFormat()) + +let formStep = ORKFormStep(identifier: "FormStepIdentifier") +formStep.title = "Basic Information" +formStep.detailText = "please answer the questions below" +formStep.formItems = [sectionHeaderFormItem, nameFormItem, emailFormItem, headacheFormItem] +``` + +The code above creates this form step: + +![Form Step](creating-surveys-form-step) + +### Answer Formats + +In the ResearchKit™ framework, an answer format defines how the user should be asked to +answer a question or an item in a form. For example, consider a +survey question such as "On a scale of 1 to 10, how much pain do you +feel?" The answer format for this question would naturally be a +discrete scale on that range, so you can use scale answer format (``ORKScaleAnswerFormat``), +and set its minimum and maximum +properties to reflect the desired range. + +The screenshots below show the standard answer formats that the ResearchKit framework provides. + +| | | +|---|---| +| ![Background Check Question](creating-surveys-background-check) | ![Blood Type Question](creating-surveys-blood-type) | +| ![Symptoms Question](creating-surveys-symptom-choice) | ![Age Question](creating-surveys-age) | +| ![Time of Day Question](creating-surveys-time-of-day) | ![Next Meeting Question](creating-surveys-next-meeting) | +| ![Text Entry Question](creating-surveys-feeling-text-entry) | ![Mood Question](creating-surveys-slider) | +| ![Email Question](creating-surveys-email) | ![Location Question](creating-surveys-location) | + +## 2. Create a survey task + +Once you create one or more steps, create an ``ORKOrderedTask`` object to +contain the steps. The code below shows the steps created above being added to a task. + +```swift +// Create a task wrapping the instruction and form steps created earlier. +let orderedTask = ORKOrderedTask(identifier: "OrderedTaskIdentifier", steps: [instructionStep, formStep]) +``` + + +You must assign a string identifier to each step. The step identifier must be unique within the task, because it's the key that connects a step in the task hierarchy with the step result in the result hierarchy. + +To present the task, attach it to a task view controller and present +it. The code below shows how to create a task view controller and present it modally. + +```swift +let taskViewController = ORKTaskViewController(task: task, taskRun: nil) +taskViewController.delegate = self + +present(taskViewController, animated: true) +``` + +## 3. Collect Results +The result property of the task view controller gives you the results of the task. +Each step view controller that the user views produces a step result +(``ORKStepResult``). The task view controller collates these results as +the user navigates through the task, in order to produce an +``ORKTaskResult``. + +Both the task result and step result are collection results, in that +they can contain other result objects. For example, a task result contains an array of step results. + +The results contained in a step result vary depending on the type of +step. For example, a form step produces one question result for +every form item; and an active task with recorders generally produces +one result for each recorder. + +The hierarchy of results corresponds closely to the input +model hierarchy of task and steps as you can see here: + +![Results Hierarchy](creating-surveys-results-hierarchy) + +Among other properties, every result has an identifier. This +identifier is what connects the result to the model object (task, +step, form item, or recorder) that produced it. Every result also +includes start and end times, using the startDate and endDate +properties respectively. Use these properties to infer how long the user +spent on the step. + + +#### Saving Results on Task Completion + +After a task is completed, you can save or upload the results. This approach +will likely include serializing the result hierarchy in some form, +either using the built-in `NSSecureCoding` support, or another +format appropriate for your application. + +If your task can produce file output, the files are generally referenced by an ``ORKFileResult`` object and they are placed in the output directory that you set on the task view controller. After you complete a task, one implementation might be to serialize the result hierarchy into the output directory, compress the entire output +directory, and share it. + +In the following example, the result is archived with +`NSKeyedArchiver` on successful completion. If you choose to support +saving and restoring tasks, the user may save the task. This +example also demonstrates how to obtain the restoration data that +would later be needed to restore the task. + +```swift + func taskViewController(_ taskViewController: ORKTaskViewController, + didFinishWith reason: ORKTaskFinishReason, + error: Error?) { + switch reason { + case .completed: + // Archive the result object first + do { + let data = try NSKeyedArchiver.archivedData(withRootObject: taskViewController.result, + requiringSecureCoding: true) + // Save the data to disk with file protection + // or upload to a remote server securely. + + // If any file results are expected, also zip up the outputDirectory. + } catch { + print("error archiving result data: \(error.localizedDescription)") + } + + break; + case .failed, .discarded, .earlyTermination: + // Generally, discard the result. + // Consider clearing the contents of the output directory. + break; + case .saved: + let data = taskViewController.restorationData + // Store the restoration data persistently for later use. + // Normally, keep the output directory for when you will restore. + break; + } + + taskViewController.dismiss(animated: true, completion: nil) +} +``` diff --git a/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-age@3x.png b/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-age@3x.png new file mode 100644 index 0000000000..75ce34ad47 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-age@3x.png differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-background-check@3x.png b/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-background-check@3x.png new file mode 100644 index 0000000000..afd5d0a55b Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-background-check@3x.png differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-blood-type@3x.png b/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-blood-type@3x.png new file mode 100644 index 0000000000..fe4565f5a0 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-blood-type@3x.png differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-email@3x.png b/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-email@3x.png new file mode 100644 index 0000000000..97d5c04dbc Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-email@3x.png differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-feeling-text-entry@3x.png b/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-feeling-text-entry@3x.png new file mode 100644 index 0000000000..f638224e30 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-feeling-text-entry@3x.png differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-form-step@3x.png b/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-form-step@3x.png new file mode 100644 index 0000000000..daf99a6d46 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-form-step@3x.png differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-instruction-step@3x.png b/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-instruction-step@3x.png new file mode 100644 index 0000000000..6b48a9f690 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-instruction-step@3x.png differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-location@3x.png b/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-location@3x.png new file mode 100644 index 0000000000..55b0b1c649 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-location@3x.png differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-navigable-ordered-tasks@2x.png b/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-navigable-ordered-tasks@2x.png new file mode 100644 index 0000000000..2d334fee09 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-navigable-ordered-tasks@2x.png differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-next-meeting@3x.png b/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-next-meeting@3x.png new file mode 100644 index 0000000000..ec1f155d67 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-next-meeting@3x.png differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-ordered-tasks@1x.png b/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-ordered-tasks@1x.png new file mode 100644 index 0000000000..846d7f305c Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-ordered-tasks@1x.png differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-results-hierarchy@1x.png b/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-results-hierarchy@1x.png new file mode 100755 index 0000000000..7c0a5820d7 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-results-hierarchy@1x.png differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-slider@3x.png b/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-slider@3x.png new file mode 100644 index 0000000000..907cdaf94e Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-slider@3x.png differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-symptom-choice@3x.png b/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-symptom-choice@3x.png new file mode 100644 index 0000000000..64bc43b346 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-symptom-choice@3x.png differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-time-of-day@3x.png b/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-time-of-day@3x.png new file mode 100644 index 0000000000..13ff2e6fb5 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Creating-Surveys/creating-surveys-time-of-day@3x.png differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Family-History-Step/Family-History-Step.md b/ResearchKit/ResearchKit.docc/Resources/Family-History-Step/Family-History-Step.md new file mode 100644 index 0000000000..b5a7c83ce5 --- /dev/null +++ b/ResearchKit/ResearchKit.docc/Resources/Family-History-Step/Family-History-Step.md @@ -0,0 +1,107 @@ +# Family History Step + +Use the Family History Step to collect insightful health trends. + +## Overview + +Accurate family health history is a highly valuable data set that can help patients prepare for or avoid common health conditions they might face in the future. However, obtaining this information has always been a struggle to collect manually and even digitally. Now, with ResearchKit, developers and researchers can quickly construct an ORKFamilyHistoryStep and present a survey to collect data for family health history that is tailored to their specific needs. + +With the ``ORKFamilyHistoryStep`` you can specify: + +- **Relative Types** - Determine the exact type of family members the survey asks about. +- **Survey Questions** - Use the same questions for each relative group or create a different survey for each. +- **Health Conditions** - Include a list of health conditions that can be the same or different for each relative group. +- **Displayed Results** - Determine which results are displayed back to the user after completing each relative's survey. + +## Understanding the Family History Step classes + +Before initializing an ``ORKFamilyHistoryStep`` you should familiarize yourself with the classes required. + +- **ORKHealthCondition** - This represents a single health condition presented in your survey. +- **ORKConditionStepConfiguration** - This object provides the information needed for the health conditions list presented to the user. +- **ORKRelativeGroup** - This represents a specific relative group such as Grandparents, Children, or Siblings. +- **ORKRelatedPerson** -This represents a family member added during the survey. Retrieve this object from the result of the ``ORKFamilyHistoryStep``. + +In the next section, we will construct an ``ORKFamilyHistoryStep`` using the classes above. + +## Constructing a Family History Step + +Following the example below, we will recreate the same Family History Step only for the parent group. + +![Instruction Step](family-history-step-instruction-step) +![Family History Step With No Relatives](family-history-step-no-relatives) +![Family History Step With Parent Added](family-history-step-parent-added) + +### Creating Health Condition Objects + +First, create the ``ORKHealthCondition`` objects necessary to display the health conditions specific to your survey. + +```swift + let healthConditions = [ + ORKHealthCondition(identifier: "healthConditionIdentifier1", displayName: "Diabetes", value: "Diabetes" as NSString), + ORKHealthCondition(identifier: "healthConditionIdentifier2", displayName: "Heart Attack", value: "Heart Attack" as NSString), + ORKHealthCondition(identifier: "healthConditionIdentifier3", displayName: "Stroke", value: "Stroke" as NSString) + ] +``` + +### Create Condition Step Configuration + +Next, initialize an ``ORKConditionStepConfiguration`` and add the necessary information, which includes the health conditions array created before this. + +```swift +let conditionStepConfiguration = ORKConditionStepConfiguration(stepIdentifier: "FamilyHistoryConditionStepIdentifier", + conditionsFormItemIdentifier: "HealthConditionsFormItemIdentifier", + conditions: healthConditions, + formItems: []) +``` + +- **stepIdentifier** - When the user is presented with the health conditions to select, they are technically looking at an ``ORKFormStep`` that was initialized by the ``ORKFamilyHistoryStep`` itself. The value you set for this property is the step identifier for the health conditions form step. +- **conditionsFormItemIdentifier** - The string used as the identifier for the health conditions text choice question. Use this identifier to locate each family member's health condition selected in the ORKResult. +- **conditions** - The user will be presented with an individual text choice for each "ORKHealthCondition" in this list. +- **formItems** - Optionally, provide more form items to present additional questions under the health conditions text choices. + +### Create Relative Group + +The last object needed for the family history step is the ``ORKRelativeGroup``. + +```swift +let parentFormStep = ORKFormStep(identifier: "ParentSurveyIdentifier") +parentFormStep.isOptional = false +parentFormStep.title = "Parent" +parentFormStep.detailText = "Answer these questions to the best of your ability." +parentFormStep.formItems = parentFormStepFormItems() + +let parentRelativeGroup = ORKRelativeGroup(identifier: "ParentGroupIdentifier", + name: "Biological Parent", + sectionTitle: "Biological Parents", + sectionDetailText: "Include your blood-related parents.", + identifierForCellTitle: "ParentNameIdentifier", + maxAllowed: 2, + formSteps: [parentFormStep], + detailTextIdentifiers: ["ParentSexAtBirthIdentifier", "ParentVitalStatusIdentifier", "ParentAgeFormItemIdentifier"]) +``` + +### Create Family History Step + +For the last step, we will construct an ``ORKFamilyHistoryStep`` and pass in the initialized objects from above. + +```swift +let familyHistoryStep = ORKFamilyHistoryStep(identifier: "FamilyHistoryStepIdentifier) +familyHistoryStep.title = "Family Health History" +familyHistoryStep.detailText = "The overview of your biological family members can inform health risks and lifestyle." +familyHistoryStep.conditionStepConfiguration = conditionStepConfiguration +familyHistoryStep.relativeGroups = relativeGroups +``` + +### Parsing Family History Step Result + +After presenting the task, parse the ``ORKTaskResult`` to access the ``ORKFamilyHistoryResult``. + + +```swift +guard let stepResult = (taskViewController.result.results?[1] as? ORKStepResult) else { return } + +if let familyHistoryResult = stepResult.results?.first as? ORKFamilyHistoryResult { + let relatedPersons = familyHistoryResult.relatedPersons +} +``` diff --git a/ResearchKit/ResearchKit.docc/Resources/Family-History-Step/family-history-step-instruction-step@3x.png b/ResearchKit/ResearchKit.docc/Resources/Family-History-Step/family-history-step-instruction-step@3x.png new file mode 100644 index 0000000000..b323accd7c Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Family-History-Step/family-history-step-instruction-step@3x.png differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Family-History-Step/family-history-step-no-relatives@3x.png b/ResearchKit/ResearchKit.docc/Resources/Family-History-Step/family-history-step-no-relatives@3x.png new file mode 100644 index 0000000000..77c3a9782c Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Family-History-Step/family-history-step-no-relatives@3x.png differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Family-History-Step/family-history-step-parent-added@3x.png b/ResearchKit/ResearchKit.docc/Resources/Family-History-Step/family-history-step-parent-added@3x.png new file mode 100644 index 0000000000..b0047aac6f Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Family-History-Step/family-history-step-parent-added@3x.png differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/AmslerGridImages/amsler-grid-1@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/AmslerGridImages/amsler-grid-1@3x.PNG new file mode 100644 index 0000000000..d478cd60c8 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/AmslerGridImages/amsler-grid-1@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/AmslerGridImages/amsler-grid-2@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/AmslerGridImages/amsler-grid-2@3x.PNG new file mode 100644 index 0000000000..31aa8abbb9 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/AmslerGridImages/amsler-grid-2@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/AmslerGridImages/amsler-grid-3@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/AmslerGridImages/amsler-grid-3@3x.PNG new file mode 100644 index 0000000000..14686ba7b2 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/AmslerGridImages/amsler-grid-3@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/AmslerGridImages/amsler-grid-4@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/AmslerGridImages/amsler-grid-4@3x.PNG new file mode 100644 index 0000000000..369ebde587 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/AmslerGridImages/amsler-grid-4@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/AmslerGridImages/amsler-grid-5@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/AmslerGridImages/amsler-grid-5@3x.PNG new file mode 100644 index 0000000000..4a96decceb Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/AmslerGridImages/amsler-grid-5@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/AmslerGridImages/amsler-grid-6@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/AmslerGridImages/amsler-grid-6@3x.PNG new file mode 100644 index 0000000000..d6e9e58ad8 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/AmslerGridImages/amsler-grid-6@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/AudioTaskImages/audio-task-1@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/AudioTaskImages/audio-task-1@3x.PNG new file mode 100644 index 0000000000..20a9453ad3 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/AudioTaskImages/audio-task-1@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/AudioTaskImages/audio-task-2@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/AudioTaskImages/audio-task-2@3x.PNG new file mode 100644 index 0000000000..82f1fa1249 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/AudioTaskImages/audio-task-2@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/AudioTaskImages/audio-task-3@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/AudioTaskImages/audio-task-3@3x.PNG new file mode 100644 index 0000000000..7d02c9e116 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/AudioTaskImages/audio-task-3@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/AudioTaskImages/audio-task-4@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/AudioTaskImages/audio-task-4@3x.PNG new file mode 100644 index 0000000000..da1a78e934 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/AudioTaskImages/audio-task-4@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/AudioTaskImages/audio-task-5@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/AudioTaskImages/audio-task-5@3x.PNG new file mode 100644 index 0000000000..115071b8f5 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/AudioTaskImages/audio-task-5@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/EnvironmentSPLImages/spl-meter-1@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/EnvironmentSPLImages/spl-meter-1@3x.PNG new file mode 100644 index 0000000000..950ef47318 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/EnvironmentSPLImages/spl-meter-1@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/EnvironmentSPLImages/spl-meter-2@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/EnvironmentSPLImages/spl-meter-2@3x.PNG new file mode 100644 index 0000000000..607fc6e841 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/EnvironmentSPLImages/spl-meter-2@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/EnvironmentSPLImages/spl-meter-3@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/EnvironmentSPLImages/spl-meter-3@3x.PNG new file mode 100644 index 0000000000..8b093e7b9a Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/EnvironmentSPLImages/spl-meter-3@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/FitnessTaskImages/fitness-task-1@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/FitnessTaskImages/fitness-task-1@3x.PNG new file mode 100644 index 0000000000..12d7ed17c5 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/FitnessTaskImages/fitness-task-1@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/FitnessTaskImages/fitness-task-2@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/FitnessTaskImages/fitness-task-2@3x.PNG new file mode 100644 index 0000000000..5b7358a4bf Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/FitnessTaskImages/fitness-task-2@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/FitnessTaskImages/fitness-task-3@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/FitnessTaskImages/fitness-task-3@3x.PNG new file mode 100644 index 0000000000..7d32a34929 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/FitnessTaskImages/fitness-task-3@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/FitnessTaskImages/fitness-task-4@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/FitnessTaskImages/fitness-task-4@3x.PNG new file mode 100644 index 0000000000..6e9a4954e9 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/FitnessTaskImages/fitness-task-4@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/FitnessTaskImages/fitness-task-5@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/FitnessTaskImages/fitness-task-5@3x.PNG new file mode 100644 index 0000000000..048f1b0a21 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/FitnessTaskImages/fitness-task-5@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/FitnessTaskImages/fitness-task-6@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/FitnessTaskImages/fitness-task-6@3x.PNG new file mode 100644 index 0000000000..8d9c7e3a99 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/FitnessTaskImages/fitness-task-6@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/FitnessTaskImages/fitness-task-7@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/FitnessTaskImages/fitness-task-7@3x.PNG new file mode 100644 index 0000000000..5a8cad90b3 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/FitnessTaskImages/fitness-task-7@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/HolePegTaskImages/hole-peg-task-1@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/HolePegTaskImages/hole-peg-task-1@3x.PNG new file mode 100644 index 0000000000..a3a6b21a26 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/HolePegTaskImages/hole-peg-task-1@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/HolePegTaskImages/hole-peg-task-2@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/HolePegTaskImages/hole-peg-task-2@3x.PNG new file mode 100644 index 0000000000..c6c303753d Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/HolePegTaskImages/hole-peg-task-2@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/HolePegTaskImages/hole-peg-task-3@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/HolePegTaskImages/hole-peg-task-3@3x.PNG new file mode 100644 index 0000000000..8216325c97 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/HolePegTaskImages/hole-peg-task-3@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/HolePegTaskImages/hole-peg-task-4@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/HolePegTaskImages/hole-peg-task-4@3x.PNG new file mode 100644 index 0000000000..508e440aad Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/HolePegTaskImages/hole-peg-task-4@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/HolePegTaskImages/hole-peg-task-5@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/HolePegTaskImages/hole-peg-task-5@3x.PNG new file mode 100644 index 0000000000..3d758add42 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/HolePegTaskImages/hole-peg-task-5@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/HolePegTaskImages/hole-peg-task-6@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/HolePegTaskImages/hole-peg-task-6@3x.PNG new file mode 100644 index 0000000000..700d667a21 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/HolePegTaskImages/hole-peg-task-6@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/HolePegTaskImages/hole-peg-task-7@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/HolePegTaskImages/hole-peg-task-7@3x.PNG new file mode 100644 index 0000000000..72e405123b Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/HolePegTaskImages/hole-peg-task-7@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/KneeRangeOfMotionTaskImages/knee-range-of-motion-1@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/KneeRangeOfMotionTaskImages/knee-range-of-motion-1@3x.PNG new file mode 100644 index 0000000000..4949dde5d7 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/KneeRangeOfMotionTaskImages/knee-range-of-motion-1@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/KneeRangeOfMotionTaskImages/knee-range-of-motion-2@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/KneeRangeOfMotionTaskImages/knee-range-of-motion-2@3x.PNG new file mode 100644 index 0000000000..1aa098620e Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/KneeRangeOfMotionTaskImages/knee-range-of-motion-2@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/KneeRangeOfMotionTaskImages/knee-range-of-motion-3@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/KneeRangeOfMotionTaskImages/knee-range-of-motion-3@3x.PNG new file mode 100644 index 0000000000..257de98840 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/KneeRangeOfMotionTaskImages/knee-range-of-motion-3@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/KneeRangeOfMotionTaskImages/knee-range-of-motion-4@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/KneeRangeOfMotionTaskImages/knee-range-of-motion-4@3x.PNG new file mode 100644 index 0000000000..f796074040 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/KneeRangeOfMotionTaskImages/knee-range-of-motion-4@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/PsatTaskImages/psat-task-1@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/PsatTaskImages/psat-task-1@3x.PNG new file mode 100644 index 0000000000..a80bf3300f Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/PsatTaskImages/psat-task-1@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/PsatTaskImages/psat-task-2@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/PsatTaskImages/psat-task-2@3x.PNG new file mode 100644 index 0000000000..719a805a02 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/PsatTaskImages/psat-task-2@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/PsatTaskImages/psat-task-3@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/PsatTaskImages/psat-task-3@3x.PNG new file mode 100644 index 0000000000..bb086fc920 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/PsatTaskImages/psat-task-3@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/PsatTaskImages/psat-task-4@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/PsatTaskImages/psat-task-4@3x.PNG new file mode 100644 index 0000000000..b477a10920 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/PsatTaskImages/psat-task-4@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/PsatTaskImages/psat-task-5@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/PsatTaskImages/psat-task-5@3x.PNG new file mode 100644 index 0000000000..2fb9e13b30 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/PsatTaskImages/psat-task-5@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ReactionTimeTaskImages/reaction-time-task-1@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ReactionTimeTaskImages/reaction-time-task-1@3x.PNG new file mode 100644 index 0000000000..6bebce3aee Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ReactionTimeTaskImages/reaction-time-task-1@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ReactionTimeTaskImages/reaction-time-task-2@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ReactionTimeTaskImages/reaction-time-task-2@3x.PNG new file mode 100644 index 0000000000..a6740091c2 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ReactionTimeTaskImages/reaction-time-task-2@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ReactionTimeTaskImages/reaction-time-task-3@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ReactionTimeTaskImages/reaction-time-task-3@3x.PNG new file mode 100644 index 0000000000..4a6218b0b6 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ReactionTimeTaskImages/reaction-time-task-3@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ReactionTimeTaskImages/reaction-time-task-4@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ReactionTimeTaskImages/reaction-time-task-4@3x.PNG new file mode 100644 index 0000000000..c4fec2550a Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ReactionTimeTaskImages/reaction-time-task-4@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ShortWalkTaskImages/short-walk-task-1@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ShortWalkTaskImages/short-walk-task-1@3x.PNG new file mode 100644 index 0000000000..17a3a6459a Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ShortWalkTaskImages/short-walk-task-1@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ShortWalkTaskImages/short-walk-task-2@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ShortWalkTaskImages/short-walk-task-2@3x.PNG new file mode 100644 index 0000000000..46e882be0e Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ShortWalkTaskImages/short-walk-task-2@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ShortWalkTaskImages/short-walk-task-3@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ShortWalkTaskImages/short-walk-task-3@3x.PNG new file mode 100644 index 0000000000..63d76a6ca0 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ShortWalkTaskImages/short-walk-task-3@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ShortWalkTaskImages/short-walk-task-4@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ShortWalkTaskImages/short-walk-task-4@3x.PNG new file mode 100644 index 0000000000..1b29cc07ce Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ShortWalkTaskImages/short-walk-task-4@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ShortWalkTaskImages/short-walk-task-5@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ShortWalkTaskImages/short-walk-task-5@3x.PNG new file mode 100644 index 0000000000..81c66214e9 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ShortWalkTaskImages/short-walk-task-5@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ShortWalkTaskImages/short-walk-task-6@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ShortWalkTaskImages/short-walk-task-6@3x.PNG new file mode 100644 index 0000000000..529af2305d Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ShortWalkTaskImages/short-walk-task-6@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ShortWalkTaskImages/short-walk-task-7@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ShortWalkTaskImages/short-walk-task-7@3x.PNG new file mode 100644 index 0000000000..26808cb7a2 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ShortWalkTaskImages/short-walk-task-7@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpatialSpanMemoryTaskImages/spatial-span-memory-task-1@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpatialSpanMemoryTaskImages/spatial-span-memory-task-1@3x.PNG new file mode 100644 index 0000000000..5676932ef7 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpatialSpanMemoryTaskImages/spatial-span-memory-task-1@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpatialSpanMemoryTaskImages/spatial-span-memory-task-2@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpatialSpanMemoryTaskImages/spatial-span-memory-task-2@3x.PNG new file mode 100644 index 0000000000..d08fa5766b Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpatialSpanMemoryTaskImages/spatial-span-memory-task-2@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpatialSpanMemoryTaskImages/spatial-span-memory-task-3@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpatialSpanMemoryTaskImages/spatial-span-memory-task-3@3x.PNG new file mode 100644 index 0000000000..4ae9b6960e Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpatialSpanMemoryTaskImages/spatial-span-memory-task-3@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpatialSpanMemoryTaskImages/spatial-span-memory-task-4@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpatialSpanMemoryTaskImages/spatial-span-memory-task-4@3x.PNG new file mode 100644 index 0000000000..c2100e2627 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpatialSpanMemoryTaskImages/spatial-span-memory-task-4@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpeechInNoiseImages/speech-in-noise-1@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpeechInNoiseImages/speech-in-noise-1@3x.PNG new file mode 100644 index 0000000000..6e1103f1b0 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpeechInNoiseImages/speech-in-noise-1@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpeechInNoiseImages/speech-in-noise-2@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpeechInNoiseImages/speech-in-noise-2@3x.PNG new file mode 100644 index 0000000000..88788aaca8 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpeechInNoiseImages/speech-in-noise-2@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpeechInNoiseImages/speech-in-noise-3@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpeechInNoiseImages/speech-in-noise-3@3x.PNG new file mode 100644 index 0000000000..ba26700956 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpeechInNoiseImages/speech-in-noise-3@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpeechInNoiseImages/speech-in-noise-4@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpeechInNoiseImages/speech-in-noise-4@3x.PNG new file mode 100644 index 0000000000..f40929481c Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpeechInNoiseImages/speech-in-noise-4@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpeechInNoiseImages/speech-in-noise-5@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpeechInNoiseImages/speech-in-noise-5@3x.PNG new file mode 100644 index 0000000000..a0fca14708 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpeechInNoiseImages/speech-in-noise-5@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpeechInNoiseImages/speech-in-noise-6@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpeechInNoiseImages/speech-in-noise-6@3x.PNG new file mode 100644 index 0000000000..82e33bef77 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpeechInNoiseImages/speech-in-noise-6@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpeechRecognitionImages/speech-recognition-task-1@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpeechRecognitionImages/speech-recognition-task-1@3x.PNG new file mode 100644 index 0000000000..1f09515325 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpeechRecognitionImages/speech-recognition-task-1@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpeechRecognitionImages/speech-recognition-task-2@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpeechRecognitionImages/speech-recognition-task-2@3x.PNG new file mode 100644 index 0000000000..496b51a3f8 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpeechRecognitionImages/speech-recognition-task-2@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpeechRecognitionImages/speech-recognition-task-3@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpeechRecognitionImages/speech-recognition-task-3@3x.PNG new file mode 100644 index 0000000000..39f1b55a3e Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpeechRecognitionImages/speech-recognition-task-3@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpeechRecognitionImages/speech-recognition-task-4@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpeechRecognitionImages/speech-recognition-task-4@3x.PNG new file mode 100644 index 0000000000..2658055b3e Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpeechRecognitionImages/speech-recognition-task-4@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpeechRecognitionImages/speech-recognition-task-5@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpeechRecognitionImages/speech-recognition-task-5@3x.PNG new file mode 100644 index 0000000000..20c98a472a Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/SpeechRecognitionImages/speech-recognition-task-5@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/StroopTaskImages/stroop-task-1@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/StroopTaskImages/stroop-task-1@3x.PNG new file mode 100644 index 0000000000..4b0783e9c7 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/StroopTaskImages/stroop-task-1@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/StroopTaskImages/stroop-task-2@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/StroopTaskImages/stroop-task-2@3x.PNG new file mode 100644 index 0000000000..921c58d1ea Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/StroopTaskImages/stroop-task-2@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/StroopTaskImages/stroop-task-3@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/StroopTaskImages/stroop-task-3@3x.PNG new file mode 100644 index 0000000000..fef3ac183f Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/StroopTaskImages/stroop-task-3@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/StroopTaskImages/stroop-task-4@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/StroopTaskImages/stroop-task-4@3x.PNG new file mode 100644 index 0000000000..1079fb10b2 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/StroopTaskImages/stroop-task-4@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/StroopTaskImages/stroop-task-5@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/StroopTaskImages/stroop-task-5@3x.PNG new file mode 100644 index 0000000000..ddefa3ea21 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/StroopTaskImages/stroop-task-5@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TOHTaskImages/tower-of-hanoi-1@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TOHTaskImages/tower-of-hanoi-1@3x.PNG new file mode 100644 index 0000000000..1d6d1fbf1e Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TOHTaskImages/tower-of-hanoi-1@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TOHTaskImages/tower-of-hanoi-2@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TOHTaskImages/tower-of-hanoi-2@3x.PNG new file mode 100644 index 0000000000..6d6c777fce Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TOHTaskImages/tower-of-hanoi-2@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TOHTaskImages/tower-of-hanoi-3@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TOHTaskImages/tower-of-hanoi-3@3x.PNG new file mode 100644 index 0000000000..fb557569d8 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TOHTaskImages/tower-of-hanoi-3@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TOHTaskImages/tower-of-hanoi-4@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TOHTaskImages/tower-of-hanoi-4@3x.PNG new file mode 100644 index 0000000000..40aecc1799 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TOHTaskImages/tower-of-hanoi-4@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TimedWalkTaskImages/timed-walk-task-1@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TimedWalkTaskImages/timed-walk-task-1@3x.PNG new file mode 100644 index 0000000000..16e20b0a8a Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TimedWalkTaskImages/timed-walk-task-1@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TimedWalkTaskImages/timed-walk-task-2@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TimedWalkTaskImages/timed-walk-task-2@3x.PNG new file mode 100644 index 0000000000..58e283fd21 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TimedWalkTaskImages/timed-walk-task-2@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TimedWalkTaskImages/timed-walk-task-3@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TimedWalkTaskImages/timed-walk-task-3@3x.PNG new file mode 100644 index 0000000000..ff16714cfb Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TimedWalkTaskImages/timed-walk-task-3@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TimedWalkTaskImages/timed-walk-task-4@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TimedWalkTaskImages/timed-walk-task-4@3x.PNG new file mode 100644 index 0000000000..7361d9ea1d Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TimedWalkTaskImages/timed-walk-task-4@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TimedWalkTaskImages/timed-walk-task-5@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TimedWalkTaskImages/timed-walk-task-5@3x.PNG new file mode 100644 index 0000000000..8b299ed914 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TimedWalkTaskImages/timed-walk-task-5@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TimedWalkTaskImages/timed-walk-task-6@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TimedWalkTaskImages/timed-walk-task-6@3x.PNG new file mode 100644 index 0000000000..a2e7bde453 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TimedWalkTaskImages/timed-walk-task-6@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TimedWalkTaskImages/timed-walk-task-7@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TimedWalkTaskImages/timed-walk-task-7@3x.PNG new file mode 100644 index 0000000000..d97d358fa3 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TimedWalkTaskImages/timed-walk-task-7@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TimedWalkTaskImages/timed-walk-task-8@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TimedWalkTaskImages/timed-walk-task-8@3x.PNG new file mode 100644 index 0000000000..150490c5cf Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TimedWalkTaskImages/timed-walk-task-8@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ToneAudiometryTaskImages/tone-audiometry-task-1@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ToneAudiometryTaskImages/tone-audiometry-task-1@3x.PNG new file mode 100644 index 0000000000..02b8938943 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ToneAudiometryTaskImages/tone-audiometry-task-1@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ToneAudiometryTaskImages/tone-audiometry-task-2@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ToneAudiometryTaskImages/tone-audiometry-task-2@3x.PNG new file mode 100644 index 0000000000..bfa73946fd Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ToneAudiometryTaskImages/tone-audiometry-task-2@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ToneAudiometryTaskImages/tone-audiometry-task-3@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ToneAudiometryTaskImages/tone-audiometry-task-3@3x.PNG new file mode 100644 index 0000000000..ee25843f03 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ToneAudiometryTaskImages/tone-audiometry-task-3@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ToneAudiometryTaskImages/tone-audiometry-task-4@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ToneAudiometryTaskImages/tone-audiometry-task-4@3x.PNG new file mode 100644 index 0000000000..213a572021 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ToneAudiometryTaskImages/tone-audiometry-task-4@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ToneAudiometryTaskImages/tone-audiometry-task-5@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ToneAudiometryTaskImages/tone-audiometry-task-5@3x.PNG new file mode 100644 index 0000000000..96393d1f06 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ToneAudiometryTaskImages/tone-audiometry-task-5@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ToneAudiometryTaskImages/tone-audiometry-task-6@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ToneAudiometryTaskImages/tone-audiometry-task-6@3x.PNG new file mode 100644 index 0000000000..e9efb5bbde Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/ToneAudiometryTaskImages/tone-audiometry-task-6@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TrailMakingTaskImages/trail-making-task-1@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TrailMakingTaskImages/trail-making-task-1@3x.PNG new file mode 100644 index 0000000000..485347ecf7 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TrailMakingTaskImages/trail-making-task-1@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TrailMakingTaskImages/trail-making-task-2@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TrailMakingTaskImages/trail-making-task-2@3x.PNG new file mode 100644 index 0000000000..5b9a323af2 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TrailMakingTaskImages/trail-making-task-2@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TrailMakingTaskImages/trail-making-task-3@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TrailMakingTaskImages/trail-making-task-3@3x.PNG new file mode 100644 index 0000000000..d7f689960a Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TrailMakingTaskImages/trail-making-task-3@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TrailMakingTaskImages/trail-making-task-4@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TrailMakingTaskImages/trail-making-task-4@3x.PNG new file mode 100644 index 0000000000..c9ee83ba9f Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TrailMakingTaskImages/trail-making-task-4@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TrailMakingTaskImages/trail-making-task-5@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TrailMakingTaskImages/trail-making-task-5@3x.PNG new file mode 100644 index 0000000000..09dcb51696 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TrailMakingTaskImages/trail-making-task-5@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TrailMakingTaskImages/trail-making-task-6@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TrailMakingTaskImages/trail-making-task-6@3x.PNG new file mode 100644 index 0000000000..aa7b7618df Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TrailMakingTaskImages/trail-making-task-6@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TwoFingerTappingTaskImages/two-finger-tapping-task-1@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TwoFingerTappingTaskImages/two-finger-tapping-task-1@3x.PNG new file mode 100644 index 0000000000..edce99c290 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TwoFingerTappingTaskImages/two-finger-tapping-task-1@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TwoFingerTappingTaskImages/two-finger-tapping-task-2@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TwoFingerTappingTaskImages/two-finger-tapping-task-2@3x.PNG new file mode 100644 index 0000000000..9fe886268a Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TwoFingerTappingTaskImages/two-finger-tapping-task-2@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TwoFingerTappingTaskImages/two-finger-tapping-task-3@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TwoFingerTappingTaskImages/two-finger-tapping-task-3@3x.PNG new file mode 100644 index 0000000000..ecf7223d53 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TwoFingerTappingTaskImages/two-finger-tapping-task-3@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TwoFingerTappingTaskImages/two-finger-tapping-task-4@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TwoFingerTappingTaskImages/two-finger-tapping-task-4@3x.PNG new file mode 100644 index 0000000000..c7d28c9c31 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TwoFingerTappingTaskImages/two-finger-tapping-task-4@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TwoFingerTappingTaskImages/two-finger-tapping-task-5@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TwoFingerTappingTaskImages/two-finger-tapping-task-5@3x.PNG new file mode 100644 index 0000000000..b8acfbf215 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TwoFingerTappingTaskImages/two-finger-tapping-task-5@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TwoFingerTappingTaskImages/two-finger-tapping-task-6@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TwoFingerTappingTaskImages/two-finger-tapping-task-6@3x.PNG new file mode 100644 index 0000000000..85d5b4456e Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/TwoFingerTappingTaskImages/two-finger-tapping-task-6@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/Understanding-Active-Tasks.md b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/Understanding-Active-Tasks.md new file mode 100644 index 0000000000..25f59d92f6 --- /dev/null +++ b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/Understanding-Active-Tasks.md @@ -0,0 +1,435 @@ +# Understanding Active Tasks + +Learn more about the Active Tasks provided by ResearchKit™. + +## Overview + +Active tasks invite users to perform activities under partially +controlled conditions while iPhone sensors are used to collect data. +For example, an active task for analyzing gait and balance might ask +the user to walk a short distance, while collecting accelerometer data on the device. + +## Predefined Active Tasks + +ResearchKit includes a number of predefined tasks, which fall into seven categories: motor activities, fitness, cognition, speech, hearing, hand dexterity, and vision. The table below summarizes each task and describes the data it generates. + +Category | Task | Sensor | Data Collected +-------------|------------------------------|-----------------------------------------------|---------------- +Motor Skills | Range of Motion | Accelerometer, Gyroscope | Device Motion +^ | Gait and Balance | Accelerometer, Gyroscope | Device Motion, Pedometer +^ | Tapping Speed | Multi-Touch display, Accelerometer (optional) | Touch Activity +Fitness | Fitness | GPS, Gyroscope | Device motion, Pedometer, Location, Heart rate +^ | Timed Walk | GPS, Gyroscope | Device motion, Pedometer, Location, Heart rate +Cognition | Spatial Memory | Multi-Touch display, Accelerometer (optional) | Touch Activity, Actual sequences +^ | Stroop Test | Multi-Touch display | User selection, Completion time +^ | Trail Making Test | Multi-Touch display | Completion time, Touch activity +^ | Paced Serial Addition (PSAT) | Multi-Touch display | Addition results from user +^ | Tower of Hanoi | Multi-Touch display | Every move taken by user +^ | Reaction Time | Accelerometer, Gyroscope | Device motion +Speech | Sustained Phonation | Microphone | Uncompressed audio +^ | Speech Recognition | Microphone | Raw audio, Transcription, Edited transcript +^ | Speech-in-Noise | Microphone | Raw audio, Transcription, Edited transcript, Reception Threshold (SRT) +Hearing | Environment SPL | Microphone | Environment sound pressure level in dBA +^ | Tone Audiometry | AirPods Headphones | Minimum amplitude recognized by the user +^ | DBHL Tone Audiometry | AirPods Headphones | Hearing threshold in dB HL scale +Hand Dexterity | 9-Hole Peg | Multi-Touch display | Completion time, Move distance +Vision | Amsler Grid | Multi-Touch display | Touch activity, Eye side, Areas of distortion annotated by the user + + +You can disable the instruction or completion steps that are automatically +included in the framework by passing appropriate options when you create an active task. See the +`ORKPredefinedTaskOption` constants for the available options. + +You can use options flags to exclude data collection for data types that are not needed for your study. For example, to perform the fitness task without recording heart rate data, use the `ORKPredefinedTaskOptionExcludeHeartRate` option. + +## Range of Motion +In the range of motion task, participants follow movement instructions while accelerometer and gyroscope data is captured to measure flexed and extended positions for the knee or shoulder. Range of motion steps for the knee are shown in Figure 1. + + +| | | +|------------------------------------------------------------------|-----------------------------------------------------| +| ![Instruction step introducing the task](knee-range-of-motion-1) | ![A touch anywhere step](knee-range-of-motion-2) | +| ![A further touch anywhere step](knee-range-of-motion-3) | ![Confirms task completion](knee-range-of-motion-4) | + +Figure 1. Range of motion steps for the right knee + +## Gait and Balance + +In the gait and balance task, +the user walks for a short distance, which may be indoors. You might +use this semi-controlled task to collect objective measurements that +can be used to estimate stride length, smoothness, sway, and other +aspects of the participant's walking. + +Gait and balance steps are shown in Figure 2. + +| | | +|---------------------------------------------------------------------|-------------------------------------------------------------| +| ![Instruction step introducing the task](short-walk-task-1) | ![Instruction step introducing the task](short-walk-task-2) | +| ![Count down a specified duration into the task](short-walk-task-3) | ![Asking user to walk](short-walk-task-4) | +| ![Asking user to walk](short-walk-task-5) | ![Asking user to rest](short-walk-task-6) | +| ![Confirms task completion](short-walk-task-7) + + +Figure 2. Gait and balance steps + +## Tapping Speed + +In the tapping task, the user rapidly alternates between tapping two +targets on the touch screen. The resulting touch data can be used to +assess basic motor capabilities such as speed, accuracy, and rhythm. + +Touch data, and optionally accelerometer data from CoreMotion in iOS, are +collected using public APIs. No analysis is performed by the ResearchKit framework on the data. + +Tapping speed steps are shown in Figure 3. + +| | | +|-----------------------------------------------------------------------------------------|-----------------------------------------------------------------------------| +| ![Instruction step introducing the task](two-finger-tapping-task-1) | ![Providing instruction for the right hand task](two-finger-tapping-task-2) | +| ![The user rapidly taps on the targets using the right hand](two-finger-tapping-task-3) | ![Providing instruction for the left hand task](two-finger-tapping-task-4) | +| ![The user rapidly taps on the targets using the left hand](two-finger-tapping-task-5) | ![Confirms task completion](two-finger-tapping-task-6) | + + +Figure 3. Tapping speed steps + +## Fitness + +In the fitness task, the user walks for a specified duration (usually +several minutes). Sensor data is collected and returned through the +task view controller's delegate. Sensor data can include +accelerometer, device motion, pedometer, location, and heart rate data +where available. + +Toward the end of the walk, if heart rate data is available, the user +is asked to sit down and rest for a period. Data collection continues +during the rest period. + +Fitness steps are shown in Figure 4. + +| | | +|----------------------------------------------------------|-----------------------------------------------------------------------------------------| +| ![Instruction step introducing the task](fitness-task-1) | ![Instruction step introducing the task](fitness-task-2) | +| ![Health access alert](fitness-task-3) | ![Count down a specified duration to begin the task](fitness-task-4) | +| ![Displays distance and heart rate](fitness-task-5) | ![The rest step, which can be skipped if heart rate data is unavailable](fitness-task-6)| +| ![Confirms task completion](fitness-task-7) + +Figure 4. Fitness task + +All of the data is collected from public [CoreMotion](https://developer.apple.com/library/ios/documentation/CoreMotion/Reference/CoreMotion_Reference/index.html) and [HealthKit](https://developer.apple.com/library/ios/documentation/HealthKit/Reference/HealthKit_Framework/) APIs on iOS, and serialized to JSON. No analysis is applied to the data by the ResearchKit framework. + +## Timed Walk +In the timed walk task, the user is asked to walk quickly and safely for a specific distance. The task is immediately administered again by having the user walk the same distance in the opposite direction. The timed walk task differs from both the fitness and the short walk tasks in that the distance walked by the user is fixed. A timed walk task measures the user's lower-extremity function. + +The data collected by this task includes accelerometer, device motion, pedometer data, and location of the user. Note that the location is available only if the user agrees to share their location. +Data collected by the task is in the form of an `ORKTimedWalkResult` object. + +Timed walk steps are shown in Figure 5. + +| | | +|---------------------------------------------------------------|-----------------------------------------------------------------------------| +| ![Instruction step introducing the task](timed-walk-task-1) | ![Gathers information about the user’s assistive device](timed-walk-task-2) | +| ![Instructions on how to perform the task](timed-walk-task-3) | ![Count down a specified duration to begin the task](timed-walk-task-4) | +| ![Actual task screen](timed-walk-task-5) | ![Instruct the user to turn around](timed-walk-task-6) | +| ![Actual task screen](timed-walk-task-7) | ![Task completion](timed-walk-task-8) | + +Figure 5. Timed walk steps + +## Spatial Memory + +In the spatial memory task, +the user is asked to observe and then recall pattern sequences of +increasing length in a game-like environment. The task collects data that +can be used to assess visuospatial memory and executive function. + +The span (that is, the length of the pattern sequence) is automatically varied +during the task, increasing after successful completion of a sequence, +and decreasing after failures, in the range from `minimumSpan` to +`maximumSpan`. The `playSpeed` property lets you control the speed of sequence +playback, and the `customTargetImage` property lets you customize the shape of the tap target. The game finishes when either `maxTests` tests have been +completed, or the user has made `maxConsecutiveFailures` errors in a +row. + +The results collected are scores derived from the game, the details of +the game, and the touch inputs made by the user. + +Spatial memory test steps are shown in Figure 6. + +| | | +|------------------------------------------------------------------------|----------------------------------------------------------------| +| ![Instruction step introducing the task](spatial-span-memory-task-1) | ![Describes what the user must do](spatial-span-memory-task-2) | +| ![The user must recall the sequence](spatial-span-memory-task-3) | ![Confirms task completion](spatial-span-memory-task-4) | + +Figure 6. Spatial memory steps + +## Stroop Test +In the Stroop test, the participant is shown a series of words that are displayed in color, and must select the first letter of the color's name. Stroop test steps are shown in Figure 7. + +| | | +|-------------------------------------------------------------------------|------------------------------------------------------------------------------| +| ![Instruction step introducing the task](stroop-task-1) | ![Further instructions](stroop-task-2) | +| ![Count down a specified duration to begin the activity](stroop-task-3) | ![A typical Stroop test; the correct answer is “Y” for yellow](stroop-task-4)| +| ![Confirms task completion](stroop-task-5) + +Figure 7. Stroop test steps + +## Trail Making Test +In the trail making test, the participant connects a series of labelled circles, in order. The time to complete the test is recorded. The circles can be labelled with sequential numbers (1, 2, 3, ...) or with alternating numbers and letters (1, a, 2, b, 3, c, ...). + +Trail making test steps are shown in Figure 8. + +| | | +|---------------------------------------------------------------|-------------------------------------------------------------------------------| +| ![Instruction step introducing the task](trail-making-task-1) | ![Instruction step introducing the task](trail-making-task-2) | +| ![Further instructions](trail-making-task-3) | ![Count down a specified duration to begin the activity](trail-making-task-4) | +| ![The activity screen, shown mid-task](trail-making-task-5) | ![Confirms task completion](trail-making-task-6) | + +Figure 8. Trail making test steps + +## Paced Serial Addition Test (PSAT) + +The Paced Serial Addition Test (PSAT) task measures the cognitive function that assesses auditory and/or visual information processing speed, flexibility, and the calculation ability of the user. + +Single digits are presented every two or three seconds and the user must add each new digit to the one immediately before. + +There are three variations of this test: + +1. PASAT: Paced Auditory Serial Addition Test - the device speaks the digit every two or three seconds. +2. PVSAT: Paced Visual Serial Addition Test - the device shows the digit on screen. +3. PAVSAT: Paced Auditory and Visual Serial Addition Test - the device speaks the digit and shows it onscreen every two to three seconds. + +The score for the PSAT task is the total number of correct answers out of the number of possible correct answers. Data collected by the task is in the form of an `ORKPSATResult` object. + +PVSAT steps are shown in Figure 9. + +Note that the visual and auditory components of the task are optional. You can choose to include either of them or both. + +| | | +|---------------------------------------------------------------|---------------------------------------------------------------------------------------------------| +| ![Instruction step introducing the task](psat-task-1) | ![Describes what the user must do](psat-task-2) | +| ![Count down a specified duration into the task](psat-task-3) | ![The user must add each new digit on the screen to the one immediately prior to it](psat-task-4) | +| ![Confirms task completion](psat-task-5) + +Figure 9. PVSAT memory steps + +## Tower of Hanoi + +In the Tower of Hanoi task, the user is asked to solve the classic Tower of Hanoi puzzle in a minimum number of moves. To solve the puzzle, the user must move the entire stack to the highlighted platform in as few moves as possible. This task measures the user's problem solving skills. A Tower of Hanoi task finishes when the user completes the puzzle correctly or concedes that they cannot solve the puzzle. + +Data collected by this task is in the form of an `ORKTowerOfHanoiResult` object. It contains every move taken by the user and indicates whether the puzzle was successfully completed or not. + +Tower of Hanoi steps are shown in Figure 10. + +| | | +|------------------------------------------------------------|------------------------------------------------------| +| ![Instruction step introducing the task](tower-of-hanoi-1) | ![Describes what the user must do](tower-of-hanoi-2) | +| ![Actual task](tower-of-hanoi-3) | ![Confirms task completion](tower-of-hanoi-4) | + +Figure 10. Tower of Hanoi steps + +## Reaction Time + +In the reaction time task, the user shakes the device in response to a visual cue on the device's screen. The task is divided into a number of attempts, which you determine. To complete an attempt in a task, the user must shake or move the device with an acceleration that exceeds a threshold value ( `thresholdAcceleration` property) within the given time. The task finishes when the user successfully completes all the attempts as instructed in the task. Use this task to evaluate a user's response to the stimulus and calculate their reaction time. + +Data collected by this task is in the form of `ORKReactionTimeResult` objects. Each of these objects contain a timestamp representing the delivery of the stimulus and an `ORKFileResult` object that references the motion data collected during an attempt. To present this task, use an `ORKTaskViewController` object. + +Reaction time steps are shown in Figure 11. + +| | | +|----------------------------------------------------------------|----------------------------------------------------------| +| ![Instruction step introducing the task](reaction-time-task-1) | ![Describes what the user must do](reaction-time-task-2) | +| ![Actual task](reaction-time-task-3) | ![Confirms task completion](reaction-time-task-4) | + +Figure 11. Reaction time steps + +## Sustained Phonation + +In the sustained phonation task, the user makes a sustained sound, and an audio +recording is made. The ResearchKit framework uses the [AVFoundation](https://developer.apple.com/library/ios/documentation/AVFoundation/Reference/AVFoundationFramework/) framework to collect this +data and to present volume indication during recording. Analysis of the audio data is not included in the +ResearchKit framework, but might naturally involve looking at the power spectrum +and how it relates to the ability to produce certain +sounds. You can define your analysis on this +task according to your own requirements. +Audio steps are shown in Figure 12. + +| | | +|--------------------------------------------------------------------|---------------------------------------------------------------------------------| +| ![Instruction step introducing the task](audio-task-1) | ![Instruction step describes user action during the task](audio-task-2) | +| ![Count down a specified duration to begin the task](audio-task-3) | ![Displays a graph during audio playback (audio collection step)](audio-task-4) | +| ![Confirms task completion](audio-task-5) + +Figure 12. Audio steps + +## Speech Recognition + +Researchers and developers can use ResearchKit to record audio data and produce transcriptions generated by Apple’s speech recognition system. ResearchKit also provides word alignments, confidence scores, and alternative speech recognition hypotheses in the form of an n-best list. Medical researchers and developers can leverage this information to analyze speech and language features like speaking rate, word usage, and pause durations. + +The n-best list and the confidence measure detect uncertainty in the speech recognition system's hypothesis in certain cases of unintelligible speech or speech containing word fragments or meaningless words. These conditions are found to be a useful indicator of cognitive decline associated with Alzheimer's disease and related dementias (1, 2), as well as other mental health issues (3). Additionally, researchers and developers can use the raw audio data captured through ResearchKit to investigate and deploy speech indicators for research and system design. + +The `ORKSpeechRecognitionStep` class represents a single recording step. In this step, the user's speech is recorded from the microphone. + +Speech recognition steps showing capturing and displaying recorded text are in Figure 13. + +| | | +|---------------------------------------------------------------------|-----------------------------------------------------------------------------| +| ![Instruction step introducing the task](speech-recognition-task-1) | ![Instruct the user to prepare for recording](speech-recognition-task-2) | +| ![Records the user’s speech](speech-recognition-task-3) | ![Provides the transcription and allows editing](speech-recognition-task-4) | +| ![Task completion](speech-recognition-task-5) + +Figure 13. Speech recognition steps + +Once a user completes the recording, they are given the option to edit the transcript generated by the speech recognition engine. The data collected by this task consists of three main components: + +1. The raw audio recording of what the user said. +2. The transcription generated by the speech recognition engine returned as an object of type `SFTranscript`. +3. The edited transcript, if any, by the user. + +## Speech-in-Noise + +Understanding speech in noisy environments depends on both the level of the background noise and the hearing health of the listener. A speech-in-noise test quantifies the difficulty of understanding speech in noisy environments. + +A speech-in-noise test consists of presenting spoken sentences at different noise levels and asking listeners to repeat what they heard. Based on the sentence and word recognition rate, a metric is calculated. The speech intelligibility metric used in this test is the Speech Reception Threshold (SRT). It represents the SNR at which 50% of the words are correctly repeated by the user. The SRT is calculated using the Tillman-Olsen formula (4). + +The `ORKSpeechInNoiseStep` class plays the speech from a file set by the `speechFileNameWithExtension` property mixed with noise from a file set by the `noiseFileNameWithExtension` property. The noise gain is set through the `gainAppliedToNoise` property. Use the `filterFileNameWithExtension` property to specify a ramp-up/ramp-down filter. + +Speech-in-noise steps are shown in Figure 14. + +| | | +|----------------------------------------------------------------------------|-----------------------------------------------------------------------| +| ![Instruction step introducing the task](speech-in-noise-1) | ![Instructs the user how to proceed with the task](speech-in-noise-2) | +| ![Plays the spoken sentence with background noise](speech-in-noise-3) | ![Records the user’s voice](speech-in-noise-4) | +| ![Displays spoken text and provides transcript editing](speech-in-noise-5) | ![Task completion](speech-in-noise-6) | + +Figure 14. Speech-in-noise steps + +## Environment SPL Meter + +The Environment SPL Meter is not a task, but a single step that detects the sound pressure level in the user's environment. Configure this step with the following properties: + +- `thresholdValue` is the maximum permissible value for the environment sound pressure level in dBA. +- `samplingInterval` is the rate at which the `AVAudioPCMBuffer` is queried and A-weighted filter is applied. +- `requiredContiguousSamples` is the number of consecutive samples less than threshold value required for the step to proceed. + +| | | +|--------------------------------------------------------|--------------------------------------------------------------------| +| ![Prompt the user if environment is too loud](spl-meter-1) | ![Task collects required samples under the threshold](spl-meter-2) | +| ![Step is complete](spl-meter-3) + +The environment SPL meter step is shown in Figure 15. + +## Tone Audiometry + +In the tone audiometry task, users listen through headphones to a series of tones, and tap left or right buttons on the screen when they hear each tone. These tones are of different audio frequencies, playing on different channels (left and right), with the volume being progressively increased until the user taps one of the buttons. A tone audiometry task measures different properties of a user's hearing ability, based on their reaction to a wide range of frequencies. + +Data collected in this task consists of audio signal amplitude for specific frequencies and channels for each ear. + +Tone audiometry steps are shown in Figure 16. + +| | | +|--------------------------------------------------------------------------------------|----------------------------------------------------------------------------------| +| ![Instruction step introducing the task](tone-audiometry-task-1) | ![Instruction step introducing the task](tone-audiometry-task-2) | +| ![Further instructions](tone-audiometry-task-3) | ![Count down a specified duration to begin the activity](tone-audiometry-task-4) | +| ![The tone test screen with buttons for left and right ears](tone-audiometry-task-5) | ![Confirms task completion](speech-in-noise-6) | + +Figure 16. Tone audiometry steps + +## dBHL Tone Audiometry + +The dBHL tone audiometry task implements the Hughson-Westlake method of determining hearing threshold. It is similar to the tone audiometry task, except that it utilizes a dB HL scale. + +Data collected in this task consists of audio signal amplitude for specific frequencies and channels for each ear. + +dBHL tone audiometry steps are shown in Figure 17. + +| | | +|------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------| +| ![Instruction step introducing the task](dbhl-tone-audiometry-task-1) | ![Instruction step introducing the task](dbhl-tone-audiometry-task-2) | +| ![Further instructions](dbhl-tone-audiometry-task-3) | ![Check noise levels](dbhl-tone-audiometry-task-4) | +| ![Instruction step displaying which ear the user will hear sounds from](dbhl-tone-audiometry-task-5) | ![The tone test screen with buttons for the right ear](dbhl-tone-audiometry-task-6) | +| ![Instruction step displaying which ear the user will hear sounds from](dbhl-tone-audiometry-task-7) | ![The tone test screen with buttons for the left ear](dbhl-tone-audiometry-task-8) | +| ![Confirms task completion](dbhl-tone-audiometry-task-9) + +Figure 17. dBHL tone audiometry steps + +## 9-Hole Peg Test + +The 9-hole peg test is a two-step test of hand dexterity to measure the [MSFC score in Multiple Sclerosis](http://www.nationalmssociety.org/For-Professionals/Researchers/Resources-for-Researchers/Clinical-Study-Measures/9-Hole-Peg-Test-(9-HPT)), or signs of [Parkinson's disease or stroke](http://www.rehabmeasures.org/Lists/RehabMeasures/DispForm.aspx?ID=925). This task is well documented in the scientific literature (see [Earhart et al., 2011](http://www.ncbi.nlm.nih.gov/pubmed/22020457)). + +The data collected by this task includes the number of pegs, an array of move samples, and the total duration that the user spent taking the test. Practically speaking, this task generates a two-step test in which the participant must put a variable number of pegs in a hole (the place step), and then remove them (the remove step). This task tests both hands. + +The `ORKHolePegTestPlaceStep` class represents the place step. In this step, the user uses two fingers to touch the peg and drag it into the hole using their left hand. The `ORKHolePegTestRemoveStep` class represents the remove step. Here, the user moves the peg over a line using two fingers on their right hand. + +9-Hole peg test steps are shown in Figure 18. + +| | | +|--------------------------------------------------------------------------------|--------------------------------------------------------------------------------| +| ![Instruction step introducing the task](hole-peg-task-1) | ![Describes what the user must do](hole-peg-task-2) | +| ![Instructs the user to perform the step with the right hand](hole-peg-task-3) | ![Instructs the user to perform the step with the right hand](hole-peg-task-4) | +| ![Instructs the user to perform the step with the left hand](hole-peg-task-5) | ![Instructs the user to perform the step with the left hand](hole-peg-task-6) | +| ![Task completion](hole-peg-task-7) | + +Figure 18. 9-hole peg test steps + + +## Amsler Grid + +The Amsler Grid task is a tool used to detect the onset of vision problems such as macular degeneration. + +The `ORKAmslerGridStep` class represents a single measurement step. In this step, the user observes the grid while closing one eye for any anomalies and marks the areas that appear distorted, using their finger or a stylus. + +Data collected by this task is in the form of an `ORKAmslerGridResult` object for the eye. It contains the eye side (specified by `ORKAmslerGridEyeSide`) and the image of the grid, along with the user's annotations for the corresponding eye. + +Amsler grid steps for the left and right eyes are shown in Figure 19. + +| | | +|---------------------------------------------------------|------------------------------------------------------------------| +| ![Instruction step introducing the task](amsler-grid-1) | ![Instruct the user how to measure the left eye](amsler-grid-2) | +| ![Perform the left eye test](amsler-grid-3) | ![Instruct the user how to measure the right eye](amsler-grid-4) | +| ![Perform the right eye test](amsler-grid-5) | ![Task completion](amsler-grid-6) | + +Figure 19. Amsler grid steps + +## Collect the Data + +The data collected in active tasks is recorded in a hierarchy of `ORKResult` objects in memory. It is up to you to serialize this hierarchy for storage or transmission in a way that’s appropriate for your application. + +For high sample rate data, such as from the accelerometer, use the `ORKFileResult` in the hierarchy. This object references a file in the output directory (specified by the `outputDirectory` property of `ORKTaskViewController`) where the data is logged. + +The recommended approach for handling file-based output is to create a new directory per task and to remove it after you have processed the results of the task. + +Active steps support attaching recorder configurations +(`ORKRecorderConfiguration`). A recorder configuration defines a type of +data that should be collected for the duration of the step from a sensor or +a database on the device. For example: + +- The pedometer sensor returns a `CMPedometerData` object that provides step counts computed by the motion coprocessor on supported devices. +- The accelerometer sensor returns a `CMAccelerometerData` object that provides raw accelerometer samples indicating the forces on the device. +- A `CMDeviceMotion` object provides information about the orientation and movement of the device by combining data collected from the accelerometer, gyroscope, and magnetometer. +- HealthKit returns sample types, such as heart rate. +- CoreLocation returns location data (combined from GPS, Wi-Fi and cell tower information). + +The recorders used by ResearchKit's predefined active tasks always use +`NSFileProtectionCompleteUnlessOpen` while writing data to disk, and +then change the file protection level on any files generated to +`NSFileProtectionComplete` when recording is finished. + +### Access Health Data + +For HealthKit related data, there are two recorder configurations: + +- `ORKHealthQuantityTypeRecorderConfiguration` to access quantity data such as heart rate. +- `ORKHealthClinicalTypeRecorderConfiguration` to access health records data. + +Access to health quantity data and health records requires explicit permission that the user must grant explicitly. More information about accessing health records [can be found here](https://developer.apple.com/documentation/healthkit/health_and_fitness_samples/accessing_health_records?language=objc). + +-- +### References + +1. [Yancheva et. al., 2015] M. Yancheva, K. C. Fraser and F. Rudzicz, “Using linguistic features longitudinally to predict clinical scores for Alzheimer's disease and related dementias,” Proceedings of SLPAT 2015: 6th Workshop on Speech and Language Processing for Assistive Technologies, 2015. + +2. [Konig et al., 2015] A. König, A. Satt, A. Sorin, R. Hoory, O. Toledo-Ronen, A. Derreumaux, V. Manera, F. Verhey, P. Aalten, P. H. Robert, and R. David. “Automatic speech analysis for the assessment of patients with predementia and Alzheimer's disease,” Alzheimers Dement (Amst). 2015 Mar; 1(1): 112–124. + +3. [Gong and Poellabauer’ 17] Y. Gong and C. Poellabauer, “Topic Modeling Based Multi-modal Depression Detection,” AVEC@ACM Multimedia, 2017. + +4. [T.W. Tillman and W.O. Olsen] "Speech audiometry, Modern Development in Audiology (2nd Edition)," 1972. diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/dBHLToneAudiometryTaskImages/dbhl-tone-audiometry-task-1@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/dBHLToneAudiometryTaskImages/dbhl-tone-audiometry-task-1@3x.PNG new file mode 100644 index 0000000000..f81a8c0775 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/dBHLToneAudiometryTaskImages/dbhl-tone-audiometry-task-1@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/dBHLToneAudiometryTaskImages/dbhl-tone-audiometry-task-2@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/dBHLToneAudiometryTaskImages/dbhl-tone-audiometry-task-2@3x.PNG new file mode 100644 index 0000000000..f88d924379 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/dBHLToneAudiometryTaskImages/dbhl-tone-audiometry-task-2@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/dBHLToneAudiometryTaskImages/dbhl-tone-audiometry-task-3@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/dBHLToneAudiometryTaskImages/dbhl-tone-audiometry-task-3@3x.PNG new file mode 100644 index 0000000000..6271decd1c Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/dBHLToneAudiometryTaskImages/dbhl-tone-audiometry-task-3@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/dBHLToneAudiometryTaskImages/dbhl-tone-audiometry-task-4@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/dBHLToneAudiometryTaskImages/dbhl-tone-audiometry-task-4@3x.PNG new file mode 100644 index 0000000000..87375d0152 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/dBHLToneAudiometryTaskImages/dbhl-tone-audiometry-task-4@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/dBHLToneAudiometryTaskImages/dbhl-tone-audiometry-task-5@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/dBHLToneAudiometryTaskImages/dbhl-tone-audiometry-task-5@3x.PNG new file mode 100644 index 0000000000..8b816f628f Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/dBHLToneAudiometryTaskImages/dbhl-tone-audiometry-task-5@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/dBHLToneAudiometryTaskImages/dbhl-tone-audiometry-task-6@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/dBHLToneAudiometryTaskImages/dbhl-tone-audiometry-task-6@3x.PNG new file mode 100644 index 0000000000..4d22e27a83 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/dBHLToneAudiometryTaskImages/dbhl-tone-audiometry-task-6@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/dBHLToneAudiometryTaskImages/dbhl-tone-audiometry-task-7@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/dBHLToneAudiometryTaskImages/dbhl-tone-audiometry-task-7@3x.PNG new file mode 100644 index 0000000000..9054f54d9d Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/dBHLToneAudiometryTaskImages/dbhl-tone-audiometry-task-7@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/dBHLToneAudiometryTaskImages/dbhl-tone-audiometry-task-8@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/dBHLToneAudiometryTaskImages/dbhl-tone-audiometry-task-8@3x.PNG new file mode 100644 index 0000000000..111edb7e27 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/dBHLToneAudiometryTaskImages/dbhl-tone-audiometry-task-8@3x.PNG differ diff --git a/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/dBHLToneAudiometryTaskImages/dbhl-tone-audiometry-task-9@3x.PNG b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/dBHLToneAudiometryTaskImages/dbhl-tone-audiometry-task-9@3x.PNG new file mode 100644 index 0000000000..88a3843019 Binary files /dev/null and b/ResearchKit/ResearchKit.docc/Resources/Understanding-Active-Tasks/dBHLToneAudiometryTaskImages/dbhl-tone-audiometry-task-9@3x.PNG differ diff --git a/ResearchKit/ResearchKit.h b/ResearchKit/ResearchKit.h index 03fe91244b..3d921f55d9 100644 --- a/ResearchKit/ResearchKit.h +++ b/ResearchKit/ResearchKit.h @@ -29,26 +29,42 @@ */ -#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import #import +#import +#import +#import +#import +#import -#import +// Import these files for iOS only. +#if !TARGET_OS_WATCH && !TARGET_OS_VISION +#import #import -#import #import #import -#import -#import #import #import -#import #import #import #import #import #import #import -#import #import #import #import @@ -61,24 +77,15 @@ #import #import #import -#import #import #import -#import -#import -#import -#import -#import #import -#import -#import #import #import #import -#import #import #import #import @@ -92,10 +99,6 @@ #import #import - -#import -#import - #import #import @@ -107,10 +110,16 @@ #import #import - -#import #import -#import - #import #import + +// Family History +#import +#import +#import +#import +#import +#import + +#endif diff --git a/ResearchKit/ResearchKit_Private.h b/ResearchKit/ResearchKit_Private.h index b8a94726c4..b1b99dae6f 100644 --- a/ResearchKit/ResearchKit_Private.h +++ b/ResearchKit/ResearchKit_Private.h @@ -28,30 +28,35 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#import -#import #import #import #import #import #import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +#if !TARGET_OS_WATCH && !TARGET_OS_VISION +#import +#import #import #import #import #import -#import -#import -#import #import #import #import -#import -#import #import -#import #import #import -#import -#import #import #import + +#endif diff --git a/ResearchKit/Stale/ORKQuestionStep.h b/ResearchKit/Stale/ORKQuestionStep.h index 8ed9e3bef1..4d904d1d1a 100644 --- a/ResearchKit/Stale/ORKQuestionStep.h +++ b/ResearchKit/Stale/ORKQuestionStep.h @@ -34,7 +34,6 @@ #if TARGET_OS_IOS || TARGET_OS_VISION #import -#endif NS_ASSUME_NONNULL_BEGIN diff --git a/ResearchKit/Stale/ORKQuestionStep.m b/ResearchKit/Stale/ORKQuestionStep.m index 113e80ec30..90607410ad 100644 --- a/ResearchKit/Stale/ORKQuestionStep.m +++ b/ResearchKit/Stale/ORKQuestionStep.m @@ -28,15 +28,18 @@ 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 +#import +#import +#import +#import +#import #if TARGET_OS_IOS || TARGET_OS_VISION #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 || TARGET_OS_VISION @@ -131,11 +134,12 @@ - (BOOL)isFormatTextfield { return [impliedAnswerFormat isKindOfClass:[ORKTextAnswerFormat class]] && ![(ORKTextAnswerFormat *)impliedAnswerFormat multipleLines]; } +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION - (NSSet *)requestedHealthKitTypesForReading { HKObjectType *objType = [[self answerFormat] healthKitObjectTypeForAuthorization]; return (objType != nil) ? [NSSet setWithObject:objType] : nil; } - +#endif #endif - (instancetype)initWithIdentifier:(NSString *)identifier { diff --git a/ResearchKit/Stale/ORKQuestionStep_Private.h b/ResearchKit/Stale/ORKQuestionStep_Private.h index 85515fe1a5..34d7a02c1d 100644 --- a/ResearchKit/Stale/ORKQuestionStep_Private.h +++ b/ResearchKit/Stale/ORKQuestionStep_Private.h @@ -33,7 +33,7 @@ #if TARGET_OS_IOS || TARGET_OS_VISION #import #import -#endif +#import NS_ASSUME_NONNULL_BEGIN diff --git a/ResearchKitActiveTask/Common/ORKOrderedTask+ORKPredefinedActiveTask.m b/ResearchKitActiveTask/Common/ORKOrderedTask+ORKPredefinedActiveTask.m index efc65e67a4..42df4d34cf 100644 --- a/ResearchKitActiveTask/Common/ORKOrderedTask+ORKPredefinedActiveTask.m +++ b/ResearchKitActiveTask/Common/ORKOrderedTask+ORKPredefinedActiveTask.m @@ -127,9 +127,11 @@ @implementation ORKOrderedTask (ORKMakeTaskUtilities) + (NSArray*)makeRecorderConfigurationsWithOptions:(ORKPredefinedTaskOption)options { +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION HKUnit *bpmUnit = [[HKUnit countUnit] unitDividedByUnit:[HKUnit minuteUnit]]; HKQuantityType *heartRateType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeartRate]; - +#endif + NSMutableArray *recorderConfigurations = [NSMutableArray arrayWithCapacity:5]; if (!(ORKPredefinedTaskOptionExcludePedometer & options)) { @@ -143,13 +145,18 @@ @implementation ORKOrderedTask (ORKMakeTaskUtilities) [recorderConfigurations addObject:[[ORKDeviceMotionRecorderConfiguration alloc] initWithIdentifier:ORKDeviceMotionRecorderIdentifier frequency:100]]; } +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION if (!(ORKPredefinedTaskOptionExcludeLocation & options)) { [recorderConfigurations addObject:[[ORKLocationRecorderConfiguration alloc] initWithIdentifier:ORKLocationRecorderIdentifier]]; } +#endif +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION if (!(ORKPredefinedTaskOptionExcludeHeartRate & options)) { [recorderConfigurations addObject:[[ORKHealthQuantityTypeRecorderConfiguration alloc] initWithIdentifier:ORKHeartRateRecorderIdentifier healthQuantityType:heartRateType unit:bpmUnit]]; } +#endif + return [recorderConfigurations copy]; } @@ -1699,7 +1706,7 @@ + (ORKOrderedTask *)speechInNoiseTaskWithIdentifier:(NSString *)identifier ORKInstructionStep *step = [[ORKInstructionStep alloc] initWithIdentifier:ORKInstruction0StepIdentifier]; step.title = ORKLocalizedString(@"SPEECH_IN_NOISE_TITLE", nil); step.detailText = intendedUseDescription; - step.text = intendedUseDescription ? : ORKLocalizedString(@"SPEECH_IN_NOISE_INTRO_TEXT", nil); + step.text = ORKLocalizedString(@"SPEECH_IN_NOISE_INTRO_TEXT", nil); step.image = [UIImage imageNamed:@"speechInNoise" inBundle:[NSBundle bundleForClass:[self class]] compatibleWithTraitCollection:nil]; step.imageContentMode = UIViewContentModeCenter; step.shouldTintImages = YES; @@ -2342,10 +2349,12 @@ + (ORKOrderedTask *)timedWalkTaskWithIdentifier:(NSString *)identifier [recorderConfigurations addObject:[[ORKDeviceMotionRecorderConfiguration alloc] initWithIdentifier:ORKDeviceMotionRecorderIdentifier frequency:100]]; } + +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION if (! (options & ORKPredefinedTaskOptionExcludeLocation)) { [recorderConfigurations addObject:[[ORKLocationRecorderConfiguration alloc] initWithIdentifier:ORKLocationRecorderIdentifier]]; } - +#endif { ORKTimedWalkStep *step = [[ORKTimedWalkStep alloc] initWithIdentifier:ORKTimedWalkTrial1StepIdentifier]; step.title = ORKLocalizedString(@"TIMED_WALK_TITLE", nil); diff --git a/ResearchKitActiveTask/Common/Recorders/Health/ORKHealthClinicalTypeRecorder.h b/ResearchKitActiveTask/Common/Recorders/Health/ORKHealthClinicalTypeRecorder.h index 6d20ecdb54..c3e440ef8b 100644 --- a/ResearchKitActiveTask/Common/Recorders/Health/ORKHealthClinicalTypeRecorder.h +++ b/ResearchKitActiveTask/Common/Recorders/Health/ORKHealthClinicalTypeRecorder.h @@ -33,7 +33,7 @@ #import #import - +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION NS_ASSUME_NONNULL_BEGIN /** @@ -111,3 +111,4 @@ API_AVAILABLE(ios(12.0)) #endif NS_ASSUME_NONNULL_END +#endif diff --git a/ResearchKitActiveTask/Common/Recorders/Health/ORKHealthClinicalTypeRecorder.m b/ResearchKitActiveTask/Common/Recorders/Health/ORKHealthClinicalTypeRecorder.m index 1ccd171be9..6eb95e3a7d 100644 --- a/ResearchKitActiveTask/Common/Recorders/Health/ORKHealthClinicalTypeRecorder.m +++ b/ResearchKitActiveTask/Common/Recorders/Health/ORKHealthClinicalTypeRecorder.m @@ -32,14 +32,14 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE #if defined(__IPHONE_12_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_12_0 +#import #import "ORKHealthClinicalTypeRecorder.h" #import "ORKHelpers_Internal.h" #import "ORKDataLogger.h" #import "ORKRecorder_Private.h" #import "ORKRecorder_Internal.h" -#import - +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION @interface ORKHealthClinicalTypeRecorder () { ORKDataLogger *_logger; BOOL _isRecording; @@ -234,3 +234,4 @@ - (NSSet *)requestedHealthKitTypesForReading { @end #endif +#endif diff --git a/ResearchKitActiveTask/Common/Recorders/Health/ORKHealthQuantityTypeRecorder.h b/ResearchKitActiveTask/Common/Recorders/Health/ORKHealthQuantityTypeRecorder.h index d558d9a3e1..0218a3825d 100644 --- a/ResearchKitActiveTask/Common/Recorders/Health/ORKHealthQuantityTypeRecorder.h +++ b/ResearchKitActiveTask/Common/Recorders/Health/ORKHealthQuantityTypeRecorder.h @@ -35,8 +35,6 @@ NS_ASSUME_NONNULL_BEGIN -@class HKQuantityType; -@class HKUnit; @class ORKHealthQuantityTypeRecorder; @protocol ORKHealthQuantityTypeRecorderDelegate @@ -51,6 +49,10 @@ NS_ASSUME_NONNULL_BEGIN The `ORKHealthQuantityTypeRecorder` class represents a recorder for collecting real time sample data from HealthKit, such as heart rate, during an active task. */ +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION +@class HKQuantityType; +@class HKUnit; + ORK_CLASS_AVAILABLE @interface ORKHealthQuantityTypeRecorder : ORKRecorder @@ -128,5 +130,5 @@ ORK_CLASS_AVAILABLE @property (nonatomic, readonly, copy) HKUnit *unit; @end - +#endif NS_ASSUME_NONNULL_END diff --git a/ResearchKitActiveTask/Common/Recorders/Health/ORKHealthQuantityTypeRecorder.m b/ResearchKitActiveTask/Common/Recorders/Health/ORKHealthQuantityTypeRecorder.m index 10ee4e0f2a..1803a0e56f 100644 --- a/ResearchKitActiveTask/Common/Recorders/Health/ORKHealthQuantityTypeRecorder.m +++ b/ResearchKitActiveTask/Common/Recorders/Health/ORKHealthQuantityTypeRecorder.m @@ -29,14 +29,14 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE */ +#import #import "ORKHealthQuantityTypeRecorder.h" #import "ORKHelpers_Internal.h" #import "ORKDataLogger.h" #import "ORKRecorder_Private.h" #import "ORKRecorder_Internal.h" -#import - +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION @interface ORKHealthQuantityTypeRecorder () { ORKDataLogger *_logger; BOOL _isRecording; @@ -368,3 +368,4 @@ - (NSSet *)requestedHealthKitTypesForReading { } @end +#endif diff --git a/ResearchKitActiveTask/Common/Recorders/Location/CLLocation+ORKJSONDictionary.h b/ResearchKitActiveTask/Common/Recorders/Location/CLLocation+ORKJSONDictionary.h index 9dead51637..11ac4b2855 100644 --- a/ResearchKitActiveTask/Common/Recorders/Location/CLLocation+ORKJSONDictionary.h +++ b/ResearchKitActiveTask/Common/Recorders/Location/CLLocation+ORKJSONDictionary.h @@ -29,6 +29,7 @@ */ +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION @import CoreLocation; @@ -41,3 +42,4 @@ NS_ASSUME_NONNULL_BEGIN @end NS_ASSUME_NONNULL_END +#endif diff --git a/ResearchKitActiveTask/Common/Recorders/Location/CLLocation+ORKJSONDictionary.m b/ResearchKitActiveTask/Common/Recorders/Location/CLLocation+ORKJSONDictionary.m index d0a4af26da..c301bff3f8 100644 --- a/ResearchKitActiveTask/Common/Recorders/Location/CLLocation+ORKJSONDictionary.m +++ b/ResearchKitActiveTask/Common/Recorders/Location/CLLocation+ORKJSONDictionary.m @@ -33,7 +33,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE #import "ORKHelpers_Internal.h" - +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION @implementation CLLocation (ORKJSONDictionary) - (NSDictionary *)ork_JSONDictionary { @@ -71,3 +71,4 @@ - (NSDictionary *)ork_JSONDictionary { } @end +#endif diff --git a/ResearchKitActiveTask/Common/Recorders/Location/ORKLocationRecorder.h b/ResearchKitActiveTask/Common/Recorders/Location/ORKLocationRecorder.h index c1b8ea77f9..00da6a32b5 100644 --- a/ResearchKitActiveTask/Common/Recorders/Location/ORKLocationRecorder.h +++ b/ResearchKitActiveTask/Common/Recorders/Location/ORKLocationRecorder.h @@ -28,7 +28,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION #import #import #import @@ -69,3 +69,4 @@ ORK_CLASS_AVAILABLE @end NS_ASSUME_NONNULL_END +#endif diff --git a/ResearchKitActiveTask/Common/Recorders/Location/ORKLocationRecorder.m b/ResearchKitActiveTask/Common/Recorders/Location/ORKLocationRecorder.m index a3d7a328b1..6ba724a5ef 100644 --- a/ResearchKitActiveTask/Common/Recorders/Location/ORKLocationRecorder.m +++ b/ResearchKitActiveTask/Common/Recorders/Location/ORKLocationRecorder.m @@ -28,6 +28,7 @@ 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. */ +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION #import "ORKLocationRecorder.h" @@ -209,3 +210,4 @@ - (ORKPermissionMask)requestedPermissionMask { } @end +#endif diff --git a/ResearchKitActiveTask/ResearchKitActiveTask.docc/ResearchKitActiveTask.md b/ResearchKitActiveTask/ResearchKitActiveTask.docc/ResearchKitActiveTask.md new file mode 100644 index 0000000000..a8918ff35c --- /dev/null +++ b/ResearchKitActiveTask/ResearchKitActiveTask.docc/ResearchKitActiveTask.md @@ -0,0 +1,72 @@ +# ``ResearchKitActiveTask`` + +Use active tasks to capture sensor information. + +## Overview + +Active tasks invite users to perform activities under partially controlled conditions while iPhone sensors are used to collect data. + +## Topics + +### Steps + +- ``ORKAccuracyStroopStep`` +- ``ORKActiveStep`` +- ``ORKEnvironmentSPLMeterStep`` +- ``ORKTouchAnywhereStep`` +- ``ORK3DModelStep`` + +### Results + +- ``ORKAccuracyStroopResult`` +- ``ORKAmslerGridResult`` +- ``ORKdBHLToneAudiometryResult`` +- ``ORKEnvironmentSPLMeterResult`` +- ``ORKHolePegTestResult`` +- ``ORKNormalizedReactionTimeResult`` +- ``ORKPSATResult`` +- ``ORKRangeOfMotionResult`` +- ``ORKReactionTimeResult`` +- ``ORKSpatialSpanMemoryResult`` +- ``ORKSpeechInNoiseResult`` +- ``ORKSpeechRecognitionResult`` +- ``ORKStroopResult`` +- ``ORKTappingIntervalResult`` +- ``ORKTimedWalkResult`` +- ``ORKToneAudiometryResult`` +- ``ORKTowerOfHanoiResult`` +- ``ORKTrailmakingResult`` +- ``ORKUSDZModelManagerResult`` + +### View Controllers + +- ``ORKActiveStepViewController`` +- ``ORKTouchAnywhereStepViewController`` + +### Other + +- ``ORKAudioChannel`` +- ``ORKAudiometryProtocol`` +- ``ORKAudiometryStimulus`` +- ``ORKAudiometryTimestampProvider`` +- ``ORKBodySagittal`` +- ``ORKdBHLTaskContext`` +- ``ORKdBHLToneAudiometryFrequencySample`` +- ``ORKdBHLToneAudiometryUnit`` +- ``ORKHealthClinicalTypeRecorderConfiguration`` +- ``ORKHealthQuantityTypeRecorderConfiguration`` +- ``ORKHolePegTestSample`` +- ``ORKInvalidDBHLValue`` +- ``ORKPSATSample`` +- ``ORKSpatialSpanMemoryGameRecord`` +- ``ORKSpatialSpanMemoryGameStatus`` +- ``ORKSpatialSpanMemoryGameTouchSample`` +- ``ORKSpeechRecognizerLocale`` +- ``ORKTappingButtonIdentifier`` +- ``ORKTappingSample`` +- ``ORKToneAudiometrySample`` +- ``ORKTowerOfHanoiMove`` +- ``ORKTrailmakingTap`` +- ``ORKUSDZModelManager`` +- ``ORK3DModelManager`` +- ``ORK3DModelManagerProtocol`` diff --git a/ResearchKitActiveTask/ResearchKitActiveTask_Private.h b/ResearchKitActiveTask/ResearchKitActiveTask_Private.h index 3d61b65872..9aaf1feace 100644 --- a/ResearchKitActiveTask/ResearchKitActiveTask_Private.h +++ b/ResearchKitActiveTask/ResearchKitActiveTask_Private.h @@ -41,6 +41,7 @@ #import #import #import +#import #import #import #import diff --git a/ResearchKitActiveTask/SpeechRecognition/ORKSpeechRecognitionStepViewController.m b/ResearchKitActiveTask/SpeechRecognition/ORKSpeechRecognitionStepViewController.m index dce2749047..9b2dcd5d43 100644 --- a/ResearchKitActiveTask/SpeechRecognition/ORKSpeechRecognitionStepViewController.m +++ b/ResearchKitActiveTask/SpeechRecognition/ORKSpeechRecognitionStepViewController.m @@ -445,6 +445,11 @@ - (void)didFinishRecognition:(SFSpeechRecognitionResult *)recognitionResult { if (_errorState) { return; } + + if ([_localResult.transcription formattedString].length > 0 && ![recognitionResult.bestTranscription formattedString].length) { + return; + } + dispatch_sync(_speechRecognitionQueue, ^{ _localResult.transcription = recognitionResult.bestTranscription; #if defined(__IPHONE_14_5) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_14_5 diff --git a/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/ORKAudiometry.m b/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/ORKAudiometry.m index b7aacb85a9..1f8601a32c 100644 --- a/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/ORKAudiometry.m +++ b/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/ORKAudiometry.m @@ -32,6 +32,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE #import "ORKdBHLToneAudiometryStep.h" #import "ORKdBHLToneAudiometryResult.h" #import "ORKAudiometryStimulus.h" +#import "ORKHelpers_Internal.h" @interface ORKAudiometryTransition: NSObject @@ -136,7 +137,7 @@ - (void)registerStimulusPlayback { _preStimulusResponse = NO; } -- (void)registerResponse:(BOOL)response { +- (void)registerResponse:(BOOL)response forUnit:(ORKdBHLToneAudiometryUnit *_Nullable)unit { if (response) { [self stimulusAcknowledged]; } else { @@ -201,8 +202,8 @@ - (void)estimatedBHLForFrequency:(NSNumber *)freq { _prevFreq = [freq doubleValue]; _resultSample = [ORKdBHLToneAudiometryFrequencySample new]; _resultSample.channel = _audioChannel; - _resultSample.frequency = [freq doubleValue]; - _resultSample.calculatedThreshold = ORKInvalidDBHLValue; + _resultSample.frequency = ORKForceDoubleToLimits([freq doubleValue]); + _resultSample.calculatedThreshold = ORKForceDoubleToLimits(ORKInvalidDBHLValue); [_arrayOfResultSamples addObject:_resultSample]; } else { _numberOfTransitionsPerFreq += 1; @@ -220,7 +221,7 @@ - (void)estimatedBHLForFrequency:(NSNumber *)freq { } _resultUnit = [ORKdBHLToneAudiometryUnit new]; - _resultUnit.dBHLValue = _currentdBHL; + _resultUnit.dBHLValue = ORKForceDoubleToLimits(_currentdBHL); _resultUnit.startOfUnitTimeStamp = _getTimestamp(); [_arrayOfResultUnits addObject:_resultUnit]; @@ -278,7 +279,7 @@ - (void)stimulusAcknowledged { ORKAudiometryTransition *currentTransitionObject = [_transitionsDictionary objectForKey:currentKey]; currentTransitionObject.userInitiated -= 1; } else if ([self validateResultFordBHL:_currentdBHL]) { - _resultSample.calculatedThreshold = _currentdBHL; + _resultSample.calculatedThreshold = ORKForceDoubleToLimits(_currentdBHL); _indexOfFreqLoopList += 1; if (_indexOfFreqLoopList >= _freqLoopList.count) { _resultSample.units = [_arrayOfResultUnits copy]; @@ -316,18 +317,18 @@ - (BOOL)validateResultFordBHL:(float)dBHL { if (((previousTransitionObject.userInitiated/previousTransitionObject.totalTransitions <= 0.5) && (previousTransitionObject.totalTransitions >= 2)) || dBHL == _dBHLMinimumThreshold) { if (currentTransitionObject.totalTransitions == 2) { if (currentTransitionObject.userInitiated/currentTransitionObject.totalTransitions == 1.0) { - _resultSample.calculatedThreshold = dBHL; + _resultSample.calculatedThreshold = ORKForceDoubleToLimits(dBHL); return YES; } else { return NO; } } else { - _resultSample.calculatedThreshold = dBHL; + _resultSample.calculatedThreshold = ORKForceDoubleToLimits(dBHL); return YES; } } } else if (_minimumThresholdCounter > 2) { - _resultSample.calculatedThreshold = dBHL; + _resultSample.calculatedThreshold = ORKForceDoubleToLimits(dBHL); return YES; } return NO; diff --git a/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/ORKAudiometryProtocol.h b/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/ORKAudiometryProtocol.h index 18db21833e..c2e74ec0ab 100644 --- a/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/ORKAudiometryProtocol.h +++ b/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/ORKAudiometryProtocol.h @@ -29,11 +29,15 @@ */ #import +#import typedef NSTimeInterval(^ORKAudiometryTimestampProvider)(void); +typedef void(^ORKAudiometryStatusBlock)(BOOL, ORKAudiometryStimulus *_Nullable); @class ORKdBHLToneAudiometryFrequencySample; +NS_ASSUME_NONNULL_BEGIN + /** Defines the interface of an audiometry algorithm. */ @@ -55,21 +59,25 @@ typedef NSTimeInterval(^ORKAudiometryTimestampProvider)(void); @property (nonatomic, strong) ORKAudiometryTimestampProvider timestampProvider; /** - This method should return a `ORKAudiometryStimulus` providing the parameters of the tone that should presented next, if available. + Called just before presenting tone. */ -- (ORKAudiometryStimulus *)nextStimulus; +- (void)registerStimulusPlayback; /** - Called just before presenting tone. + Used by to create a new ORKdBHLToneAudiometryUnit that will register the timestamps of the tone being played + + @param double The value of the preStimulusDelay + @param NSTimeInterval The value of the startOfUnitTimeStamp */ -- (void)registerStimulusPlayback; +@optional +- (ORKdBHLToneAudiometryUnit *)createUnitWith:(double)preStimulusDelay startOfUnitTimeStamp:(NSTimeInterval)startOfUnitTimeStamp; /** Register the user response for the last presented tone. @param response A Boolean representing if the user acknowledged the last presented tone. */ -- (void)registerResponse:(BOOL)response; +- (void)registerResponse:(BOOL)response forUnit:(ORKdBHLToneAudiometryUnit *_Nullable)unit; /** Informs the audiometry algorithm that the last provided tone could not be reproduced due to signal clipping. Optional. @@ -85,6 +93,12 @@ typedef NSTimeInterval(^ORKAudiometryTimestampProvider)(void); @optional - (void)registerPreStimulusDelay:(double)preStimulusDelay; +/** + This method should return a `ORKAudiometryStimulus` providing the parameters of the tone that should presented next, if available. + */ +@optional +- (ORKAudiometryStimulus *)nextStimulus; + /** Returns an array of containing the results of the audiometry test. @@ -93,3 +107,5 @@ typedef NSTimeInterval(^ORKAudiometryTimestampProvider)(void); - (NSArray *)resultSamples; @end + +NS_ASSUME_NONNULL_END diff --git a/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/ORKdBHLToneAudiometryAudioGenerator.m b/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/ORKdBHLToneAudiometryAudioGenerator.m index 007a3b69e0..c7e335646f 100644 --- a/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/ORKdBHLToneAudiometryAudioGenerator.m +++ b/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/ORKdBHLToneAudiometryAudioGenerator.m @@ -93,6 +93,7 @@ - (NSNumber *)dbHLtoAmplitude: (double)dbHL atFrequency:(double)frequency; @end +const double DeviceVolumeMinimumValue = 0.0625; const double ORKdBHLSineWaveToneGeneratorSampleRateDefault = 44100.0f; static OSStatus ORKdBHLAudioGeneratorRenderTone(void *inRefCon, @@ -407,7 +408,7 @@ - (NSNumber *)dbHLtoAmplitude: (double)dbHL atFrequency:(double)frequency { // get current volume float currentVolume = [self getCurrentSystemVolume]; - currentVolume = (int)(currentVolume / 0.0625) * 0.0625; + currentVolume = ((int)(currentVolume / 0.0625) * 0.0625) >= DeviceVolumeMinimumValue ?: DeviceVolumeMinimumValue; // check in volume curve table for offset NSDecimalNumber *offsetDueToVolume = [NSDecimalNumber decimalNumberWithString:_volumeCurve[[NSString stringWithFormat:@"%.4f",currentVolume]]]; diff --git a/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/ORKdBHLToneAudiometryContentView.h b/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/ORKdBHLToneAudiometryContentView.h index a2ed8b794f..f80bd60cc5 100644 --- a/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/ORKdBHLToneAudiometryContentView.h +++ b/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/ORKdBHLToneAudiometryContentView.h @@ -30,10 +30,10 @@ */ -@import UIKit; -#import "ORKActiveStepCustomView.h" -#import "ORKUnitLabel.h" -#import "ORKRoundTappingButton.h" +#import +#import +#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/ORKdBHLToneAudiometryContentView.m b/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/ORKdBHLToneAudiometryContentView.m index 2fbcaa548f..14ef414a39 100644 --- a/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/ORKdBHLToneAudiometryContentView.m +++ b/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/ORKdBHLToneAudiometryContentView.m @@ -38,6 +38,8 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE static const CGFloat TopToProgressViewMinPadding = 10.0; +NSString * const ORKdBHLToneAudiometryTestInProgressLabelAccessibilityIdentifier = @"ORKdBHLToneAudiometryTestInProgressLabel"; + @interface TestingInProgressView : UIView @property (nonatomic, assign, getter=isActive) BOOL active; @@ -136,6 +138,7 @@ - (void)setupTextLabel compatibleWithTraitCollection:self.traitCollection]; _textLabel.font = [UIFont fontWithDescriptor:descriptor size:2 * TestingInProgressIndicatorRadius]; _textLabel.text = ORKLocalizedString(@"dBHL_TONE_AUDIOMETRY_TESTING_IN_PROGRESS", nil); + _textLabel.accessibilityIdentifier = ORKdBHLToneAudiometryTestInProgressLabelAccessibilityIdentifier; [self addSubview:_textLabel]; [NSLayoutConstraint activateConstraints:@[ diff --git a/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/ORKdBHLToneAudiometryStepViewController.m b/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/ORKdBHLToneAudiometryStepViewController.m index c6aab78aa9..f13e0fa56a 100644 --- a/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/ORKdBHLToneAudiometryStepViewController.m +++ b/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/ORKdBHLToneAudiometryStepViewController.m @@ -57,6 +57,9 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE #import "ORKNavigableOrderedTask.h" #import "ORKStepNavigationRule.h" +NSString * const ORKdBHLToneAudiometryStepViewAccessibilityIdentifier = @"ORKdBHLToneAudiometryStepView"; + + @interface ORKdBHLToneAudiometryStepViewController () { ORKdBHLToneAudiometryFrequencySample *_resultSample; ORKAudioChannel _audioChannel; @@ -108,6 +111,8 @@ - (ORKdBHLToneAudiometryStep *)dBHLToneAudiometryStep { - (void)viewDidLoad { [super viewDidLoad]; + + self.view.accessibilityIdentifier = ORKdBHLToneAudiometryStepViewAccessibilityIdentifier; } - (ORKdBHLToneAudiometryAudioGenerator *)createAudioGeneratorFromHeadphoneType:(ORKHeadphoneTypeIdentifier)type { @@ -205,7 +210,7 @@ - (ORKStepResult *)result { toneResult.startDate = sResult.startDate; toneResult.endDate = now; toneResult.samples = [self.audiometryEngine resultSamples]; - toneResult.outputVolume = [AVAudioSession sharedInstance].outputVolume; + toneResult.outputVolume = ORKForceDoubleToLimits([AVAudioSession sharedInstance].outputVolume); toneResult.headphoneType = self.dBHLToneAudiometryStep.headphoneType; toneResult.tonePlaybackDuration = [self dBHLToneAudiometryStep].toneDuration; toneResult.postStimulusDelay = [self dBHLToneAudiometryStep].postStimulusDelay; @@ -280,7 +285,7 @@ - (void)runTestTrial { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((preStimulusDelay + toneDuration + 0.2) * NSEC_PER_SEC)), dispatch_get_main_queue(), _pulseDurationWorkBlock); _postStimulusDelayWorkBlock = dispatch_block_create(DISPATCH_BLOCK_INHERIT_QOS_CLASS, ^{ - [self.audiometryEngine registerResponse:NO]; + [self.audiometryEngine registerResponse:NO forUnit:nil]; [self nextTrial]; }); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((preStimulusDelay + toneDuration + postStimulusDelay) * NSEC_PER_SEC)), dispatch_get_main_queue(), _postStimulusDelayWorkBlock); @@ -299,7 +304,7 @@ - (void)tapButtonPressed { [_hapticFeedback impactOccurred]; if (_preStimulusDelayWorkBlock && dispatch_block_testcancel(_preStimulusDelayWorkBlock) == 0) { - [self.audiometryEngine registerResponse:YES]; + [self.audiometryEngine registerResponse:YES forUnit:nil]; } [self nextTrial]; } diff --git a/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/retspl_dBFS_AIRPODSPROV2.plist b/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/retspl_dBFS_AIRPODSPROV2.plist new file mode 100644 index 0000000000..901604faa2 --- /dev/null +++ b/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/retspl_dBFS_AIRPODSPROV2.plist @@ -0,0 +1,86 @@ + + + + + 250.0000 + -85.0000 + 306.0092 + -87.4644 + 364.2145 + -90.0254 + 425.0334 + -92.7014 + 488.9024 + -95.5117 + 500.0000 + -96.0000 + 556.2799 + -96.1125 + 627.6493 + -96.2552 + 703.5228 + -96.4070 + 784.4448 + -96.5688 + 870.9962 + -96.7419 + 963.7980 + -96.9275 + 1000.0000 + -97.0000 + 1063.5161 + -96.8729 + 1170.8662 + -96.6582 + 1286.6186 + -96.4267 + 1411.6040 + -96.1767 + 1546.7192 + -95.9065 + 1692.9339 + -95.6141 + 1851.2974 + -95.2974 + 2000.0000 + -95.0000 + 2022.9461 + -95.0688 + 2209.1117 + -95.6273 + 2411.1302 + -96.2333 + 2630.4514 + -96.8913 + 2868.6490 + -97.6059 + 3127.4325 + -97.6177 + 3408.6590 + -96.7740 + 3714.3464 + -95.8569 + 4000.0000 + -95.0000 + 4046.6886 + -94.9066 + 4408.0704 + -94.1838 + 4801.0852 + -93.3978 + 5228.5533 + -92.5428 + 5693.5422 + -91.6129 + 6000.0000 + -91.0000 + 6199.3889 + -90.8006 + 6749.7233 + -90.2502 + 7348.4947 + -89.6515 + 8000.0000 + -89.0000 + + diff --git a/ResearchKitActiveTask/environmentSPLMeter/ORKEnvironmentSPLMeterContentView.m b/ResearchKitActiveTask/environmentSPLMeter/ORKEnvironmentSPLMeterContentView.m index 079d8573e5..af4cffa024 100644 --- a/ResearchKitActiveTask/environmentSPLMeter/ORKEnvironmentSPLMeterContentView.m +++ b/ResearchKitActiveTask/environmentSPLMeter/ORKEnvironmentSPLMeterContentView.m @@ -45,6 +45,8 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE static const CGFloat HalfCircleSize = 14.0; static const CGFloat BarViewHeight = 50.0; +NSString * const ORKEnvironmentSPLMeterOptimumNoiseLevelLabelAccessibilityIdentifier = @"ORKEnvironmentSPLMeterOptimumNoiseLevelLabel"; + @interface ORKEnvironmentSPLMeterContentView () @property(nonatomic, strong) ORKRingView *ringView; @property(nonatomic, strong) ORKEnvironmentSPLMeterBarView *barView; @@ -214,6 +216,8 @@ - (void)reachedOptimumNoiseLevel { _DBInstructionLabel.text = ORKLocalizedString(@"ENVIRONMENTSPL_OK", nil); + _DBInstructionLabel.accessibilityIdentifier = ORKEnvironmentSPLMeterOptimumNoiseLevelLabelAccessibilityIdentifier; + if (UIAccessibilityIsVoiceOverRunning() && [self.voiceOverDelegate respondsToSelector:@selector(contentView:shouldAnnounce:)]) { [self.voiceOverDelegate contentView:self shouldAnnounce:_DBInstructionLabel.text]; } diff --git a/ResearchKitActiveTask/environmentSPLMeter/ORKEnvironmentSPLMeterStep.m b/ResearchKitActiveTask/environmentSPLMeter/ORKEnvironmentSPLMeterStep.m index 8f6cd4240a..06b84d8d75 100644 --- a/ResearchKitActiveTask/environmentSPLMeter/ORKEnvironmentSPLMeterStep.m +++ b/ResearchKitActiveTask/environmentSPLMeter/ORKEnvironmentSPLMeterStep.m @@ -55,7 +55,7 @@ - (void)commonInit { self.shouldShowDefaultTimer = NO; // This is inserted here because it is required for any task that requires the SPL Meter step ORKAudioStreamerConfiguration *config = [[ORKAudioStreamerConfiguration alloc] initWithIdentifier:[NSString stringWithFormat:@"%@_streamerConfiguration",self.identifier]]; - + config.bypassAudioEngineStart = YES; self.recorderConfigurations = @[config]; } diff --git a/ResearchKitActiveTask/environmentSPLMeter/ORKEnvironmentSPLMeterStepViewController.m b/ResearchKitActiveTask/environmentSPLMeter/ORKEnvironmentSPLMeterStepViewController.m index ddd94d8777..9090fc55e4 100644 --- a/ResearchKitActiveTask/environmentSPLMeter/ORKEnvironmentSPLMeterStepViewController.m +++ b/ResearchKitActiveTask/environmentSPLMeter/ORKEnvironmentSPLMeterStepViewController.m @@ -55,6 +55,8 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE static const NSTimeInterval SPL_METER_PLAY_DELAY_VOICEOVER = 3.0; +NSString * const ORKEnvironmentSPLMeterStepViewAccessibilityIdentifier = @"ORKEnvironmentSPLMeterStepView"; + @interface ORKEnvironmentSPLMeterStepViewController () { AVAudioInputNode *_inputNode; AVAudioUnitEQ *_eqUnit; @@ -107,11 +109,12 @@ - (instancetype)initWithStep:(ORKStep *)step { - (void)viewDidLoad { [super viewDidLoad]; _environmentSPLMeterContentView = [ORKEnvironmentSPLMeterContentView new]; - [self setNavigationFooterView]; _environmentSPLMeterContentView.voiceOverDelegate = self; _environmentSPLMeterContentView.ringView.delegate = self; self.activeStepView.activeCustomView = _environmentSPLMeterContentView; + self.view.accessibilityIdentifier = ORKEnvironmentSPLMeterStepViewAccessibilityIdentifier; + [self.taskViewController setNavigationBarColor:[self.view backgroundColor]]; } @@ -137,6 +140,7 @@ - (void)setContinueButtonItem:(UIBarButtonItem *)continueButtonItem { - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; + [self setNavigationFooterView]; if (!_audioEngine.isRunning) { [self saveAudioSession]; _sensitivityOffset = [self sensitivityOffsetForDevice]; diff --git a/ResearchKitSwiftUI/ResearchKitSwiftUI.xcodeproj/project.pbxproj b/ResearchKitSwiftUI/ResearchKitSwiftUI.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..6b8a8482fb --- /dev/null +++ b/ResearchKitSwiftUI/ResearchKitSwiftUI.xcodeproj/project.pbxproj @@ -0,0 +1,783 @@ +// !$*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 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 389694172C6E7621007BA17D /* Utilities.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 */; }; + 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 = ""; }; + 380E14AF2C3350A900085D2B /* ResearchForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResearchForm.swift; sourceTree = ""; }; + 3815C0132C2081CE00A29CD3 /* DateTimeQuestion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateTimeQuestion.swift; sourceTree = ""; }; + 381CAC092C35E05A00FCE89A /* ResearchFormStepContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResearchFormStepContentView.swift; sourceTree = ""; }; + 381CAC0B2C35F4C300FCE89A /* ResearchFormCompletion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResearchFormCompletion.swift; sourceTree = ""; }; + 381CAC0F2C3C759200FCE89A /* HeightQuestion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeightQuestion.swift; sourceTree = ""; }; + 3820BE1F2C6574350012CB2C /* ResearchFormResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResearchFormResult.swift; sourceTree = ""; }; + 3820BE202C6574640012CB2C /* StepResultKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepResultKey.swift; sourceTree = ""; }; + 382CF7842C3F262F000F3433 /* WeightQuestion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeightQuestion.swift; sourceTree = ""; }; + 3858D2652C90EF1500F334EB /* ResultValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultValue.swift; sourceTree = ""; }; + 389694172C6E7621007BA17D /* Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = ""; }; + 38CD260F2C1909CC000FDBB0 /* Question.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Question.swift; sourceTree = ""; }; + 38CD263A2C1BA6EA000FDBB0 /* TextQuestion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextQuestion.swift; sourceTree = ""; }; + 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 = ""; }; + 5E05B53C2C2E135D00A0A83B /* DoneKeyboardToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoneKeyboardToolbar.swift; sourceTree = ""; }; + 5E44D4472C24A6FF0045007E /* NumericQuestion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NumericQuestion.swift; sourceTree = ""; }; + 5E507F802C1141F2002958E2 /* SliderValueForegroundStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SliderValueForegroundStyle.swift; sourceTree = ""; }; + 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; }; + 5EAFC71B2C7549B9008560D7 /* NavigationalLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationalLayout.swift; sourceTree = ""; }; + 5EBAA28C2C40634800F193C6 /* BodyItemIconForegroundStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BodyItemIconForegroundStyle.swift; sourceTree = ""; }; + 5EBAA28E2C4063C100F193C6 /* StepIconForegroundStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepIconForegroundStyle.swift; sourceTree = ""; }; + 5ED16E262C5AF7DF0072016D /* InstructionBodyItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstructionBodyItem.swift; sourceTree = ""; }; + 5ED33A362CAE1EB600926DA3 /* TextChoice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextChoice.swift; sourceTree = ""; }; + 5EDFA9D62BFFFA2B0044C156 /* StepHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepHeader.swift; sourceTree = ""; }; + 5EE5FD302C6A8482004BBABF /* StateManagementType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateManagementType.swift; sourceTree = ""; }; + 5EFC55342C9B9C4D002C9587 /* QuestionCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuestionCard.swift; sourceTree = ""; }; + 5EFC55732C9DEF9E002C9587 /* KeyboardUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardUtilities.swift; sourceTree = ""; }; + 69EE51522C77F1B8008B2AD6 /* QuestionRequired.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuestionRequired.swift; sourceTree = ""; }; + BA32DE9E2BCF34440041A937 /* SliderQuestion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SliderQuestion.swift; sourceTree = ""; }; + E473AC6E2CB0AFBE00530CB1 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = ResearchKitSwiftUI.strings; sourceTree = ""; }; + 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 = ""; }; + E4DBF1B82B97C3030085D6A1 /* TextChoiceOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextChoiceOption.swift; sourceTree = ""; }; + E4DBF1BE2BA24AA10085D6A1 /* StickyScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickyScrollView.swift; sourceTree = ""; }; + E4DBF1C02BA24B150085D6A1 /* StickyFooterLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickyFooterLayout.swift; sourceTree = ""; }; + E4DBF1C22BA24B860085D6A1 /* StickyScrollView+StickyLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StickyScrollView+StickyLayout.swift"; sourceTree = ""; }; + E4E0AFC92C5B12B40082ADF8 /* Color+ResearchFormColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+ResearchFormColors.swift"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 38DB82E02C753A8B0074B95D /* ResearchKitSwiftUITests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = ResearchKitSwiftUITests; sourceTree = ""; }; +/* 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 /* Localized */ = { + isa = PBXGroup; + children = ( + E473AC702CB0AFBE00530CB1 /* en.lproj */, + ); + path = Localized; + sourceTree = ""; + }; + 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 = ""; + }; + 0B7213472B4F39A500463E69 /* Step Views */ = { + isa = PBXGroup; + children = ( + 69EE514F2C77F17F008B2AD6 /* View Modifiers */, + E4DBF1B22B97C27E0085D6A1 /* Question Views */, + 5EDFA9D62BFFFA2B0044C156 /* StepHeader.swift */, + 5EBAA28C2C40634800F193C6 /* BodyItemIconForegroundStyle.swift */, + 5EBAA28E2C4063C100F193C6 /* StepIconForegroundStyle.swift */, + ); + path = "Step Views"; + sourceTree = ""; + }; + 0BF86B132B4F360000E3790E = { + isa = PBXGroup; + children = ( + 3820BE1E2C65742D0012CB2C /* Results */, + 5ED16E262C5AF7DF0072016D /* InstructionBodyItem.swift */, + 381CAC0B2C35F4C300FCE89A /* ResearchFormCompletion.swift */, + 380E14AF2C3350A900085D2B /* ResearchForm.swift */, + 5EAFC71B2C7549B9008560D7 /* NavigationalLayout.swift */, + 381CAC092C35E05A00FCE89A /* ResearchFormStepContentView.swift */, + 0B53DA5F2BD054EE00227126 /* Localized */, + 0BF86B1F2B4F360000E3790E /* ResearchKitSwiftUI */, + 38DB82E02C753A8B0074B95D /* ResearchKitSwiftUITests */, + 0BF86B1E2B4F360000E3790E /* Products */, + 0B72132E2B4F36B900463E69 /* Frameworks */, + ); + sourceTree = ""; + }; + 0BF86B1E2B4F360000E3790E /* Products */ = { + isa = PBXGroup; + children = ( + 0BF86B1D2B4F360000E3790E /* ResearchKitSwiftUI.framework */, + 38DB82DF2C753A8B0074B95D /* ResearchKitSwiftUITests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 0BF86B1F2B4F360000E3790E /* ResearchKitSwiftUI */ = { + isa = PBXGroup; + children = ( + 5EFC55722C9DEF8F002C9587 /* Editing Utilities */, + 0B7213472B4F39A500463E69 /* Step Views */, + 5E507F822C1144DE002958E2 /* Slider */, + 5E05B53B2C2E134900A0A83B /* SwiftUIKit */, + 0BF86B212B4F360000E3790E /* ResearchKitSwiftUI.docc */, + E4E0AFC92C5B12B40082ADF8 /* Color+ResearchFormColors.swift */, + ); + path = ResearchKitSwiftUI; + sourceTree = ""; + }; + 3820BE1E2C65742D0012CB2C /* Results */ = { + isa = PBXGroup; + children = ( + 3858D2652C90EF1500F334EB /* ResultValue.swift */, + 3820BE202C6574640012CB2C /* StepResultKey.swift */, + 3820BE1F2C6574350012CB2C /* ResearchFormResult.swift */, + ); + path = Results; + sourceTree = ""; + }; + 5E05B53B2C2E134900A0A83B /* SwiftUIKit */ = { + isa = PBXGroup; + children = ( + 5E05B53C2C2E135D00A0A83B /* DoneKeyboardToolbar.swift */, + ); + path = SwiftUIKit; + sourceTree = ""; + }; + 5E507F822C1144DE002958E2 /* Slider */ = { + isa = PBXGroup; + children = ( + 5E507F802C1141F2002958E2 /* SliderValueForegroundStyle.swift */, + ); + path = Slider; + sourceTree = ""; + }; + 5EFC55722C9DEF8F002C9587 /* Editing Utilities */ = { + isa = PBXGroup; + children = ( + 5EFC55732C9DEF9E002C9587 /* KeyboardUtilities.swift */, + ); + path = "Editing Utilities"; + sourceTree = ""; + }; + 69EE514F2C77F17F008B2AD6 /* View Modifiers */ = { + isa = PBXGroup; + children = ( + 69EE51522C77F1B8008B2AD6 /* QuestionRequired.swift */, + ); + path = "View Modifiers"; + sourceTree = ""; + }; + E473AC702CB0AFBE00530CB1 /* en.lproj */ = { + isa = PBXGroup; + children = ( + E473AC6F2CB0AFBE00530CB1 /* ResearchKitSwiftUI.strings */, + ); + path = en.lproj; + sourceTree = ""; + }; + E4DBF1B22B97C27E0085D6A1 /* Question Views */ = { + isa = PBXGroup; + children = ( + 389694172C6E7621007BA17D /* Utilities.swift */, + E4DBF1B72B97C2E80085D6A1 /* Supporting Views */, + 5EE5FD302C6A8482004BBABF /* StateManagementType.swift */, + E4DBF1B52B97C2AC0085D6A1 /* MultipleChoiceQuestion.swift */, + 5ED33A362CAE1EB600926DA3 /* TextChoice.swift */, + 381CAC0F2C3C759200FCE89A /* HeightQuestion.swift */, + 382CF7842C3F262F000F3433 /* WeightQuestion.swift */, + BA32DE9E2BCF34440041A937 /* SliderQuestion.swift */, + 38CD263A2C1BA6EA000FDBB0 /* TextQuestion.swift */, + 3815C0132C2081CE00A29CD3 /* DateTimeQuestion.swift */, + 5E44D4472C24A6FF0045007E /* NumericQuestion.swift */, + 38F4F6C52C4AD35C004E7A8D /* ImageChoiceQuestion.swift */, + ); + path = "Question Views"; + sourceTree = ""; + }; + E4DBF1B72B97C2E80085D6A1 /* Supporting Views */ = { + isa = PBXGroup; + children = ( + E4DBF1C22BA24B860085D6A1 /* StickyScrollView+StickyLayout.swift */, + E4DBF1C02BA24B150085D6A1 /* StickyFooterLayout.swift */, + E4DBF1BE2BA24AA10085D6A1 /* StickyScrollView.swift */, + E4DBF1B82B97C3030085D6A1 /* TextChoiceOption.swift */, + 38CD260F2C1909CC000FDBB0 /* Question.swift */, + 5EFC55342C9B9C4D002C9587 /* QuestionCard.swift */, + ); + path = "Supporting Views"; + sourceTree = ""; + }; +/* 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 */, + 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 /* Utilities.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 = ""; + }; +/* 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 */; +} diff --git a/ResearchKitSwiftUI/ResearchKitSwiftUI.xcodeproj/xcshareddata/xcschemes/ResearchKitSwiftUI.xcscheme b/ResearchKitSwiftUI/ResearchKitSwiftUI.xcodeproj/xcshareddata/xcschemes/ResearchKitSwiftUI.xcscheme new file mode 100644 index 0000000000..e921119b37 --- /dev/null +++ b/ResearchKitSwiftUI/ResearchKitSwiftUI.xcodeproj/xcshareddata/xcschemes/ResearchKitSwiftUI.xcscheme @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ResearchKitSwiftUI/ResearchKitSwiftUI.xcodeproj/xcshareddata/xcschemes/ResearchKitSwiftUITests.xcscheme b/ResearchKitSwiftUI/ResearchKitSwiftUI.xcodeproj/xcshareddata/xcschemes/ResearchKitSwiftUITests.xcscheme new file mode 100644 index 0000000000..9ded15c3fa --- /dev/null +++ b/ResearchKitSwiftUI/ResearchKitSwiftUI.xcodeproj/xcshareddata/xcschemes/ResearchKitSwiftUITests.xcscheme @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/ResearchKitTests/ORKAnswerFormatTests.m b/ResearchKitTests/ORKAnswerFormatTests.m index 284d408955..5f19fb3fc9 100644 --- a/ResearchKitTests/ORKAnswerFormatTests.m +++ b/ResearchKitTests/ORKAnswerFormatTests.m @@ -1448,7 +1448,7 @@ - (void)testTextAnswerFormat { XCTAssertEqual([regexAnswerFormat isAnswerValidWithString:incorrectPhoneNumber], NO, @"Should return NO since it is not in the correct format"); } -#if !TARGET_OS_VISION +#if !TARGET_OS_VISION && ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION - (void)testLocationAnswerFormat { ORKLocationAnswerFormat *answerFormat = [ORKAnswerFormat locationAnswerFormat]; [answerFormat setUseCurrentLocation:YES]; @@ -1513,6 +1513,7 @@ - (void)testValuePickerAnswerFormat { XCTAssertEqual([[[answerFormat textChoices] objectAtIndex:1] value], [NSNumber numberWithInteger:2]); } +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION - (void)testHealthKitCharacteristicTypeAnswerFormat { HKCharacteristicType *biologicalSex = [HKCharacteristicType characteristicTypeForIdentifier:HKCharacteristicTypeIdentifierBiologicalSex]; @@ -1600,6 +1601,7 @@ - (void)testHealthKitQuantityTypeAnswerFormat { XCTAssertEqual([answerFormat numericAnswerStyle], ORKNumericAnswerStyleInteger); XCTAssertEqual([answerFormat quantityType], calories); } +#endif - (void)testDateAnswerFormat { diff --git a/ResearchKitTests/ORKAudiometryTests.m b/ResearchKitTests/ORKAudiometryTests.m index 66a78afaee..f8b7c3fc14 100644 --- a/ResearchKitTests/ORKAudiometryTests.m +++ b/ResearchKitTests/ORKAudiometryTests.m @@ -90,7 +90,7 @@ - (void)runTestForAudiogram:(NSDictionary *)audiogramDict onAudiometryEngine:(id if ([audiometry respondsToSelector:@selector(registerStimulusPlayback)]) { [audiometry registerStimulusPlayback]; } - [audiometry registerResponse:stimulus.level >= referenceLevel.doubleValue]; + [audiometry registerResponse:stimulus.level >= referenceLevel.doubleValue forUnit:nil]; } NSArray *result = [audiometry resultSamples]; diff --git a/ResearchKitTests/ORKESerialization.m b/ResearchKitTests/ORKESerialization.m index e49aeb78bd..d2ae538ab3 100644 --- a/ResearchKitTests/ORKESerialization.m +++ b/ResearchKitTests/ORKESerialization.m @@ -230,14 +230,6 @@ static UIEdgeInsets edgeInsetsFromDictionary(NSDictionary *dict) { return (UIEdgeInsets){.top = ((NSNumber *)dict[@"top"]).doubleValue, .left = ((NSNumber *)dict[@"left"]).doubleValue, .bottom = ((NSNumber *)dict[@"bottom"]).doubleValue, .right = ((NSNumber *)dict[@"right"]).doubleValue}; } -static NSDictionary *dictionaryFromCoordinate (CLLocationCoordinate2D coordinate) { - return @{ @"latitude": @(coordinate.latitude), @"longitude": @(coordinate.longitude) }; -} - -static CLLocationCoordinate2D coordinateFromDictionary(NSDictionary *dict) { - return (CLLocationCoordinate2D){.latitude = ((NSNumber *)dict[@"latitude"]).doubleValue, .longitude = ((NSNumber *)dict[@"longitude"]).doubleValue }; -} - static ORKNumericAnswerStyle ORKNumericAnswerStyleFromString(NSString *s) { return tableMapReverse(s, ORKNumericAnswerStyleTable()); } @@ -262,6 +254,15 @@ static ORKMeasurementSystem ORKMeasurementSystemFromString(NSString *s) { return tableMapForward(measurementSystem, ORKMeasurementSystemTable()); } +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION +static NSDictionary *dictionaryFromCoordinate (CLLocationCoordinate2D coordinate) { + return @{ @"latitude": @(coordinate.latitude), @"longitude": @(coordinate.longitude) }; +} + +static CLLocationCoordinate2D coordinateFromDictionary(NSDictionary *dict) { + return (CLLocationCoordinate2D){.latitude = ((NSNumber *)dict[@"latitude"]).doubleValue, .longitude = ((NSNumber *)dict[@"longitude"]).doubleValue }; +} + static NSDictionary *dictionaryFromCircularRegion(CLCircularRegion *region) { NSDictionary *dictionary = region ? @{ @@ -276,11 +277,13 @@ static ORKMeasurementSystem ORKMeasurementSystemFromString(NSString *s) { static NSDictionary *dictionaryFromPostalAddress(CNPostalAddress *address) { return @{ @"city": address.city, @"street": address.street }; } +#endif static NSString *identifierFromClinicalType(HKClinicalType *type) { return type.identifier; } +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION static CLCircularRegion *circularRegionFromDictionary(NSDictionary *dict) { CLCircularRegion *circularRegion; if (dict.count == 3) { @@ -290,6 +293,7 @@ static ORKMeasurementSystem ORKMeasurementSystemFromString(NSString *s) { } return circularRegion; } +#endif static NSArray *arrayFromRegularExpressionOptions(NSRegularExpressionOptions regularExpressionOptions) { NSMutableArray *optionsArray = [NSMutableArray new]; @@ -382,12 +386,14 @@ static NSRegularExpressionOptions regularExpressionOptionsFromArray(NSArray *arr return passwordRules; } +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION static CNPostalAddress *postalAddressFromDictionary(NSDictionary *dict) { CNMutablePostalAddress *postalAddress = [[CNMutablePostalAddress alloc] init]; postalAddress.city = dict[@"city"]; postalAddress.street = dict[@"street"]; return [postalAddress copy]; } +#endif static HKClinicalType *typeFromIdentifier(NSString *identifier) { return [HKClinicalType clinicalTypeForIdentifier:identifier]; @@ -1242,6 +1248,7 @@ @implementation ORKESerializer PROPERTY(allowsSelection, NSNumber, NSObject, YES, nil, nil), PROPERTY(bulletType, NSNumber, NSObject, YES, nil, nil), PROPERTY(pinNavigationContainer, NSNumber, NSObject, YES, nil, nil), + PROPERTY(bottomPadding, NSNumber, NSObject, YES, nil, nil) }))), ENTRY(ORKTimedWalkStep, ^id(NSDictionary *dict, ORKESerializationPropertyGetter getter) { @@ -1951,14 +1958,6 @@ @implementation ORKESerializer PROPERTY(maximumValue, NSNumber, NSObject, NO, nil, nil), PROPERTY(defaultValue, NSNumber, NSObject, NO, nil, nil), })), - ENTRY(ORKLocationAnswerFormat, - ^id(__unused NSDictionary *dict, __unused ORKESerializationPropertyGetter getter) { - return [[ORKLocationAnswerFormat alloc] init]; - }, - (@{ - PROPERTY(useCurrentLocation, NSNumber, NSObject, YES, nil, nil), - PROPERTY(placeholder, NSString, NSObject, YES, nil, nil) - })), ENTRY(ORKSESAnswerFormat, ^id(__unused NSDictionary *dict, __unused ORKESerializationPropertyGetter getter) { return [[ORKSESAnswerFormat alloc] init]; @@ -1967,12 +1966,23 @@ @implementation ORKESerializer PROPERTY(topRungText, NSString, NSObject, YES, nil, nil), PROPERTY(bottomRungText, NSString, NSObject, YES, nil, nil) })), + +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION + ENTRY(ORKLocationAnswerFormat, + ^id(__unused NSDictionary *dict, __unused ORKESerializationPropertyGetter getter) { + return [[ORKLocationAnswerFormat alloc] init]; + }, + (@{ + PROPERTY(useCurrentLocation, NSNumber, NSObject, YES, nil, nil), + PROPERTY(placeholder, NSString, NSObject, YES, nil, nil) + })), ENTRY(ORKLocationRecorderConfiguration, ^id(NSDictionary *dict, ORKESerializationPropertyGetter getter) { return [[ORKLocationRecorderConfiguration alloc] initWithIdentifier:GETPROP(dict,identifier)]; }, (@{ })), +#endif ENTRY(ORKPedometerRecorderConfiguration, ^id(NSDictionary *dict, ORKESerializationPropertyGetter getter) { return [[ORKPedometerRecorderConfiguration alloc] initWithIdentifier:GETPROP(dict,identifier)]; @@ -2286,6 +2296,7 @@ @implementation ORKESerializer ^id(id timeZone, __unused ORKESerializationContext *context) { return @([timeZone secondsFromGMT]); }, ^id(id number, __unused ORKESerializationContext *context) { return [NSTimeZone timeZoneForSecondsFromGMT:(NSInteger)((NSNumber *)number).doubleValue]; }) })), +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION ENTRY(ORKLocation, ^id(NSDictionary *dict, ORKESerializationPropertyGetter getter) { CLLocationCoordinate2D coordinate = coordinateFromDictionary(dict[@ESTRINGIFY(coordinate)]); @@ -2311,6 +2322,7 @@ @implementation ORKESerializer (@{ PROPERTY(locationAnswer, ORKLocation, NSObject, NO, nil, nil) })), +#endif ENTRY(ORKSESQuestionResult, nil, (@{ @@ -2404,6 +2416,119 @@ @implementation ORKESerializer (@{ PROPERTY(retryCount, NSNumber, NSObject, NO, nil, nil) })), + ENTRY(ORKAgeAnswerFormat, + ^id(NSDictionary *dict, ORKESerializationPropertyGetter getter) { + return [[ORKAgeAnswerFormat alloc] initWithMinimumAge:((NSNumber *)GETPROP(dict, minimumAge)).integerValue + maximumAge:((NSNumber *)GETPROP(dict, maximumAge)).integerValue + minimumAgeCustomText:GETPROP(dict, minimumAgeCustomText) + maximumAgeCustomText:GETPROP(dict, maximumAgeCustomText) + showYear:((NSNumber *)GETPROP(dict, showYear)).boolValue + useYearForResult:((NSNumber *)GETPROP(dict, useYearForResult)).boolValue + treatMinAgeAsRange:((NSNumber *)GETPROP(dict, treatMinAgeAsRange)).boolValue + treatMaxAgeAsRange:((NSNumber *)GETPROP(dict, treatMaxAgeAsRange)).boolValue + defaultValue:((NSNumber *)GETPROP(dict, defaultValue)).integerValue]; + }, + (@{ + PROPERTY(minimumAge, NSNumber, NSObject, NO, nil, nil), + PROPERTY(maximumAge, NSNumber, NSObject, NO, nil, nil), + PROPERTY(minimumAgeCustomText, NSString, NSObject, YES, nil, nil), + PROPERTY(maximumAgeCustomText, NSString, NSObject, YES, nil, nil), + PROPERTY(showYear, NSNumber, NSObject, NO, nil, nil), + PROPERTY(useYearForResult, NSNumber, NSObject, NO, nil, nil), + PROPERTY(treatMinAgeAsRange, NSNumber, NSObject, NO, nil, nil), + PROPERTY(treatMaxAgeAsRange, NSNumber, NSObject, NO, nil, nil), + PROPERTY(relativeYear, NSNumber, NSObject, YES, nil, nil), + PROPERTY(defaultValue, NSNumber, NSObject, NO, nil, nil), + })), + ENTRY(ORKColorChoiceAnswerFormat, + ^id(NSDictionary *dict, ORKESerializationPropertyGetter getter) { + return [[ORKColorChoiceAnswerFormat alloc] initWithStyle:((NSNumber *)GETPROP(dict, style)).integerValue colorChoices:GETPROP(dict, colorChoices)]; + }, + (@{ + PROPERTY(style, NSNumber, NSObject, NO, NUMTOSTRINGBLOCK(ORKChoiceAnswerStyleTable()), STRINGTONUMBLOCK(ORKChoiceAnswerStyleTable())), + PROPERTY(colorChoices, ORKColorChoice, NSArray, NO, nil, nil), + })), + ENTRY(ORKColorChoice, + ^id(NSDictionary *dict, ORKESerializationPropertyGetter getter) { + return [[ORKColorChoice alloc] initWithColor:GETPROP(dict, color) + text:GETPROP(dict, text) + detailText:GETPROP(dict, detailText) + value:GETPROP(dict, value) + exclusive:((NSNumber *)GETPROP(dict, exclusive)).boolValue]; + }, + (@{ + PROPERTY(text, NSString, NSObject, NO, nil, nil), + PROPERTY(detailText, NSString, NSObject, NO, nil, nil), + PROPERTY(value, NSObject, NSObject, NO, nil, nil), + PROPERTY(exclusive, NSNumber, NSObject, NO, nil, nil), + PROPERTY(color, UIColor, NSObject, YES, + ^id(id color, __unused ORKESerializationContext *context) { return dictionaryFromColor(color); }, + ^id(id dict, __unused ORKESerializationContext *context) { return colorFromDictionary(dict); }) + })), + ENTRY(ORKRelativeGroup, + ^id(NSDictionary *dict, ORKESerializationPropertyGetter getter) { + return [[ORKRelativeGroup alloc] initWithIdentifier:GETPROP(dict, identifier) + name:GETPROP(dict, name) + sectionTitle:GETPROP(dict, sectionTitle) + sectionDetailText:GETPROP(dict, sectionDetailText) + identifierForCellTitle:GETPROP(dict, identifierForCellTitle) + maxAllowed:[GETPROP(dict, maxAllowed) unsignedIntegerValue] + formSteps:GETPROP(dict, formSteps) + detailTextIdentifiers:GETPROP(dict, detailTextIdentifiers)]; + }, + (@{ + PROPERTY(identifier, NSString, NSObject, YES, nil, nil), + PROPERTY(name, NSString, NSObject, YES, nil, nil), + PROPERTY(sectionTitle, NSString, NSObject, YES, nil, nil), + PROPERTY(sectionDetailText, NSString, NSObject, YES, nil, nil), + PROPERTY(identifierForCellTitle, NSString, NSObject, YES, nil, nil), + PROPERTY(maxAllowed, NSNumber, NSObject, YES, nil, nil), + PROPERTY(formSteps, ORKFormStep, NSArray, YES, nil, nil), + PROPERTY(detailTextIdentifiers, NSString, NSArray, YES, nil, nil), + })), + ENTRY(ORKHealthCondition, + ^id(NSDictionary *dict, ORKESerializationPropertyGetter getter) { + return [[ORKHealthCondition alloc] initWithIdentifier:GETPROP(dict, identifier) displayName:GETPROP(dict, displayName) value:GETPROP(dict, value)]; + }, + (@{ + PROPERTY(identifier, NSString, NSObject, YES, nil, nil), + PROPERTY(displayName, NSString, NSObject, YES, nil, nil), + PROPERTY(value, NSObject, NSObject, YES, nil, nil) + })), + ENTRY(ORKRelatedPerson, + ^id(NSDictionary *dict, ORKESerializationPropertyGetter getter) { + return [[ORKRelatedPerson alloc] initWithIdentifier:GETPROP(dict, identifier) groupIdentifier:GETPROP(dict, groupIdentifier) identifierForCellTitle:GETPROP(dict, identifierForCellTitle) taskResult:GETPROP(dict, taskResult)]; + }, + (@{ + PROPERTY(identifier, NSString, NSObject, YES, nil, nil), + PROPERTY(groupIdentifier, NSString, NSObject, YES, nil, nil), + PROPERTY(identifierForCellTitle, NSString, NSObject, YES, nil, nil), + PROPERTY(taskResult, ORKTaskResult, NSObject, YES, nil, nil) + })), + ENTRY(ORKFamilyHistoryResult, + nil, + (@{ + PROPERTY(relatedPersons, ORKRelatedPerson, NSArray, YES, nil, nil), + PROPERTY(displayedConditions, NSString, NSArray, YES, nil, nil) + })), + ENTRY(ORKConditionStepConfiguration, + ^id(NSDictionary *dict, ORKESerializationPropertyGetter getter) { + return [[ORKConditionStepConfiguration alloc] initWithStepIdentifier:GETPROP(dict, stepIdentifier) conditionsFormItemIdentifier:GETPROP(dict, conditionsFormItemIdentifier) conditions:GETPROP(dict, conditions) formItems:GETPROP(dict, formItems)]; + }, + (@{ + PROPERTY(stepIdentifier, NSString, NSObject, YES, nil, nil), + PROPERTY(conditionsFormItemIdentifier, NSString, NSObject, YES, nil, nil), + PROPERTY(conditions, ORKHealthCondition, NSArray, YES, nil, nil), + PROPERTY(formItems, ORKFormItem, NSArray, YES, nil, nil) + })), + ENTRY(ORKFamilyHistoryStep, + ^id(NSDictionary *dict, ORKESerializationPropertyGetter getter) { + return [[ORKFamilyHistoryStep alloc] initWithIdentifier:GETPROP(dict, identifier)]; + }, + (@{ + PROPERTY(conditionStepConfiguration, ORKConditionStepConfiguration, NSObject, YES, nil, nil), + PROPERTY(relativeGroups, ORKRelativeGroup, NSArray, YES, nil, nil), + })), ENTRY(ORKDevice, ^id(NSDictionary *dict, ORKESerializationPropertyGetter getter) { return [[ORKDevice alloc] initWithProduct:GETPROP(dict, product) osVersion:GETPROP(dict, osVersion) @@ -2454,6 +2579,7 @@ static id objectForJsonObject(id input, ORKESerializationJSONToObjectBlock converterBlock, ORKESerializationContext *context) { id output = nil; + if (converterBlock != nil) { input = converterBlock(input, context); if (input == nil) { @@ -2465,6 +2591,7 @@ static id objectForJsonObject(id input, id localizer = context.localizer; id stringInterpolator = context.stringInterpolator; + if (expectedClass != nil && [input isKindOfClass:expectedClass]) { // Input is already of the expected class, do nothing output = input; diff --git a/ResearchKitTests/ORKFamilyHistoryResultTests.swift b/ResearchKitTests/ORKFamilyHistoryResultTests.swift new file mode 100644 index 0000000000..cecada1f62 --- /dev/null +++ b/ResearchKitTests/ORKFamilyHistoryResultTests.swift @@ -0,0 +1,104 @@ +/* + Copyright (c) 2023, Apple Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. No license is granted to the trademarks of + the copyright holders even if such marks are included in this software. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import XCTest +@testable import ResearchKit + +class ORKFamilyHistoryResultTests: XCTestCase { + + func testCopiesAreEqual() { + let result = simpleFamilyHistoryResultWithIdentifier() + let copy = result.copy() as! ORKFamilyHistoryResult + + XCTAssertEqual(copy, result) + + // relatedPerson references are deep copied when copying the family history result. + // The properties of each relatedPerson should be equal, but the relatedPerson objects should not be pointer equal + XCTAssertNotIdentical(copy.relatedPersons?.first, result.relatedPersons?.first) + XCTAssertEqual(copy.relatedPersons?.first, result.relatedPersons?.first) + XCTAssertNotIdentical(copy.relatedPersons?.last, result.relatedPersons?.last) + XCTAssertEqual(copy.relatedPersons?.last, result.relatedPersons?.last) + + XCTAssertEqual(copy.displayedConditions, result.displayedConditions) + } + + func testIsEqual() { + let result = simpleFamilyHistoryResultWithIdentifier() + + do { // confirm changing the conditions breaks equality + let copy = result.copy() as! ORKFamilyHistoryResult + XCTAssertEqual(copy, result) + + copy.displayedConditions = ["none"] + XCTAssertNotEqual(result, copy) + } + + do { // confirm changing the relatedPersons array breaks equality + let copy = result.copy() as! ORKFamilyHistoryResult + XCTAssertEqual(copy, result) + + copy.relatedPersons = [] + XCTAssertNotEqual(result, copy) + } + + do { // confirm changing the identifier breaks equality + let copy = simpleFamilyHistoryResultWithIdentifier("xyz") + copy.relatedPersons = result.relatedPersons + copy.displayedConditions = result.displayedConditions + + XCTAssertNotEqual(copy, result) + } + } + + func simpleFamilyHistoryResultWithIdentifier(_ identifier: String = "abc") -> ORKFamilyHistoryResult { + let result = ORKFamilyHistoryResult(identifier: identifier) + result.displayedConditions = [ + "gout", + "heart disease", + "hypertension" + ] + result.relatedPersons = [ + ORKRelatedPerson( + identifier: "aaa", + groupIdentifier: "parents", + identifierForCellTitle: "title", + taskResult: ORKTaskResult(taskIdentifier: "1", taskRun: UUID(), outputDirectory: nil) + ), + ORKRelatedPerson( + identifier: "lil", + groupIdentifier: "children", + identifierForCellTitle: "title", + taskResult: ORKTaskResult(taskIdentifier: "2", taskRun: UUID(), outputDirectory: nil) + ), + ] + return result + } +} + diff --git a/ResearchKitTests/ORKFormStepViewController+TestingSupport.h b/ResearchKitTests/ORKFormStepViewController+TestingSupport.h index 76cf530a7d..dcff75e384 100644 --- a/ResearchKitTests/ORKFormStepViewController+TestingSupport.h +++ b/ResearchKitTests/ORKFormStepViewController+TestingSupport.h @@ -38,6 +38,11 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, strong) UITableView *tableView; +@property (nonatomic, strong) NSMutableDictionary *savedAnswers; +- (void)decodeRestorableStateWithCoder:(NSCoder *)coder; +- (void)removeInvalidSavedAnswers; + + /** returns a list of all the formItems */ diff --git a/ResearchKitTests/ORKHKSampleTests.m b/ResearchKitTests/ORKHKSampleTests.m index 72c1c6a791..78386885f0 100644 --- a/ResearchKitTests/ORKHKSampleTests.m +++ b/ResearchKitTests/ORKHKSampleTests.m @@ -42,6 +42,7 @@ @interface ORKHKSampleTests : XCTestCase @implementation ORKHKSampleTests +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION - (void)testHKSampleSerialization { NSDate *d1 = [NSDate dateWithTimeIntervalSinceReferenceDate:0]; NSDate *d2 = [NSDate dateWithTimeInterval:10 sinceDate:d1]; @@ -107,5 +108,5 @@ - (void)testHKCorrelationSerialization { XCTAssertTrue([dict[@"objects"] containsObject:dd], @""); XCTAssertTrue([dict[@"objects"] containsObject:ds], @""); } - +#endif @end diff --git a/ResearchKitTests/ORKJSONSerializationTests.m b/ResearchKitTests/ORKJSONSerializationTests.m index 776a966314..a70fb02aad 100644 --- a/ResearchKitTests/ORKJSONSerializationTests.m +++ b/ResearchKitTests/ORKJSONSerializationTests.m @@ -56,6 +56,18 @@ @implementation TestCompilerFlagHelper + (NSArray *)_fetchExclusionList { NSArray *classesToExclude = @[]; +#if !ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION + NSArray *locationClasses = @[ + @"ORKLocation", + @"ORKLocationQuestionResult", + @"ORKLocationAnswerFormat", + @"ORKLocationRecorderConfiguration" + ]; + + classesToExclude = [classesToExclude arrayByAddingObjectsFromArray:locationClasses]; +#endif + + return classesToExclude; } @@ -275,11 +287,11 @@ - (instancetype)orktest_init_alt { \ return webViewStep; }); ORK_MAKE_TEST_INIT(ORK3DModelStep, ^{return [[self.class alloc] initWithIdentifier:NSUUID.UUID.UUIDString modelManager: [[ORK3DModelManager alloc] init]]; }); +ORK_MAKE_TEST_INIT(ORKAgeAnswerFormat, ^{return [self initWithMinimumAge:0 maximumAge:80 minimumAgeCustomText:nil maximumAgeCustomText:nil showYear:NO useYearForResult:NO treatMinAgeAsRange:false treatMaxAgeAsRange:false defaultValue:0];}); ORK_MAKE_TEST_INIT(ORKImageChoice, ^{return [super init];}); - - +ORK_MAKE_TEST_INIT(ORKColorChoice, ^{return [super init];}); ORK_MAKE_TEST_INIT(ORKTextChoice, ^{return [super init];}); ORK_MAKE_TEST_INIT(ORKTextChoiceOther, ^{return [self initWithText:@"test" primaryTextAttributedString:nil detailText:@"test1" detailTextAttributedString:nil value:@"value" exclusive:YES textViewPlaceholderText:@"test2" textViewInputOptional:NO textViewStartsHidden:YES];}); ORK_MAKE_TEST_INIT(ORKPredicateStepNavigationRule, ^{return [self initWithResultPredicates:@[[ORKResultPredicate predicateForBooleanQuestionResultWithResultSelector:[ORKResultSelector selectorWithResultIdentifier:@"test"] expectedAnswer:YES]] destinationStepIdentifiers:@[@"test2"]];}); @@ -300,6 +312,8 @@ - (instancetype)orktest_init_alt { \ ORK_MAKE_TEST_INIT_ALT(CLCircularRegion, (^{ return [self initWithCenter:CLLocationCoordinate2DMake(3.0, 4.0) radius:150.0 identifier:@"identifier"]; })); + +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION ORK_MAKE_TEST_INIT(ORKLocation, (^{ CNMutablePostalAddress *postalAddress = [[CNMutablePostalAddress alloc] init]; postalAddress.city = @"cityA"; @@ -314,6 +328,8 @@ - (instancetype)orktest_init_alt { \ ORKLocation *location = [self initWithCoordinate:CLLocationCoordinate2DMake(4.0, 5.0) region:[[CLCircularRegion alloc] orktest_init_alt] userInput:@"addressStringB" postalAddress:postalAddress]; return location; })); +#endif + ORK_MAKE_TEST_INIT(HKSampleType, (^{ return [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierBodyMass]; })); @@ -592,6 +608,9 @@ - (id)init { @"ORKTextAnswerFormat.passwordRules", @"ORKTextAnswerFormat.spellCheckingType", @"ORKTextAnswerFormat.textContentType", + @"ORKColorChoice.value", + @"ORKColorChoice.value", + @"ORKHealthCondition.value", @"ORKTextChoice.detailTextAttributedString", @"ORKTextChoice.primaryTextAttributedString", @"ORKTextChoice.value", @@ -1006,8 +1025,14 @@ - (void)testORKSerialization { [(ORKDateAnswerFormat *)instance _setCurrentDateOverride:dateFormatOverrideDate]; [(ORKDateAnswerFormat *)instance setDaysAfterCurrentDateToSetMinimumDate:1]; [(ORKDateAnswerFormat *)instance setDaysBeforeCurrentDateToSetMinimumDate:1]; + } else if ([aClass isSubclassOfClass:[ORKAgeAnswerFormat class]]) { + [instance setValue:@(0) forKey:@"minimumAge"]; + [instance setValue:@(80) forKey:@"maximumAge"]; + [instance setValue:@(0) forKey:@"defaultValue"]; + [instance setValue:@(2023) forKey:@"relativeYear"]; + } else if ([aClass isSubclassOfClass:[ORKColorChoice class]]) { + [instance setValue:@"blah" forKey:@"value"]; } - // Serialization NSDictionary *instanceDictionary = [ORKESerializer JSONObjectForObject:instance context:context error:NULL]; @@ -1127,9 +1152,13 @@ - (BOOL)applySomeValueToClassProperty:(ClassProperty *)p forObject:(id)instance [instance setValue:index?[NSCalendar calendarWithIdentifier:NSCalendarIdentifierChinese]:[NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian] forKey:p.propertyName]; } else if (p.propertyClass == [NSTimeZone class]) { [instance setValue:index?[NSTimeZone timeZoneWithName:[NSTimeZone knownTimeZoneNames][0]]:[NSTimeZone timeZoneForSecondsFromGMT:1000] forKey:p.propertyName]; - } else if (p.propertyClass == [ORKLocation class]) { + } +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION + else if (p.propertyClass == [ORKLocation class]) { [instance setValue:(index ? [[ORKLocation alloc] orktest_init] : [[ORKLocation alloc] orktest_init_alt]) forKey:p.propertyName]; - } else if (p.propertyClass == [CLCircularRegion class]) { + } +#endif + else if (p.propertyClass == [CLCircularRegion class]) { [instance setValue:index?[[CLCircularRegion alloc] orktest_init_alt]:[[CLCircularRegion alloc] orktest_init] forKey:p.propertyName]; } else if (p.propertyClass == [NSPredicate class]) { [instance setValue:[NSPredicate predicateWithFormat:index?@"1 == 1":@"1 == 2"] forKey:p.propertyName]; @@ -1382,6 +1411,10 @@ - (void)testEquality { @"ORKSpeechRecognitionStep.shouldHideTranscript", @"ORKWebViewStepResult.html", @"ORKWebViewStepResult.htmlWithSignature", + @"ORKAgeAnswerFormat.minimumAge", + @"ORKAgeAnswerFormat.maximumAge", + @"ORKAgeAnswerFormat.relativeYear", + @"ORKAgeAnswerFormat.defaultValue", @"ORKTableStep.isBulleted", @"ORKTableStep.allowsSelection", @"ORKPDFViewerStep.actionBarOption", diff --git a/ResearchKitTests/ORKRecorderTests.m b/ResearchKitTests/ORKRecorderTests.m index c722e2daac..7cc46a5abe 100644 --- a/ResearchKitTests/ORKRecorderTests.m +++ b/ResearchKitTests/ORKRecorderTests.m @@ -34,10 +34,12 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE @import ResearchKitActiveTask; @import ResearchKitActiveTask_Private; -@import CoreLocation; @import CoreMotion; +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION +@import CoreLocation; + @interface ORKMockLocationManager : CLLocationManager @end @@ -64,6 +66,7 @@ - (CLLocationManager *)createLocationManager { } @end +#endif @interface ORKMockTouch : UITouch @@ -396,6 +399,7 @@ - (void)checkResult { _items = items; } +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION - (void)testLocationRecorder { ORKLocationRecorder *recorder = (ORKLocationRecorder *)[self createRecorder:[[ORKLocationRecorderConfiguration alloc] initWithIdentifier:@"location"]]; @@ -465,6 +469,7 @@ - (void)testLocationRecorder { XCTAssertTrue(ork_doubleEqual(longitude, ((NSNumber *)sample[@"coordinate"][@"longitude"]).doubleValue), @""); } } +#endif - (void)testAccelerometerRecorder { @@ -641,7 +646,7 @@ - (void)testAudioRecorder { } - (void)testHealthQuantityTypeRecorder { - +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION HKUnit *bpmUnit = [[HKUnit countUnit] unitDividedByUnit:[HKUnit minuteUnit]]; HKQuantityType *hbQuantityType = [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeartRate]; ORKHealthQuantityTypeRecorderConfiguration *recorderConfiguration = [[ORKHealthQuantityTypeRecorderConfiguration alloc] initWithIdentifier:@"healtQuantityTypeRecorder" healthQuantityType:hbQuantityType unit:bpmUnit]; @@ -649,6 +654,7 @@ - (void)testHealthQuantityTypeRecorder { ORKHealthQuantityTypeRecorder *recorder = (ORKHealthQuantityTypeRecorder *)[self createRecorder:recorderConfiguration]; XCTAssertTrue([recorder isKindOfClass:recorderClass], @""); +#endif } @end diff --git a/ResearchKitTests/ORKResultTests.m b/ResearchKitTests/ORKResultTests.m index 4ca9b2eff3..a14a48f896 100644 --- a/ResearchKitTests/ORKResultTests.m +++ b/ResearchKitTests/ORKResultTests.m @@ -34,6 +34,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE @import ResearchKitUI; @import ResearchKitUI_Private; @import ResearchKit_Private; +#import "ORKFormStepViewController+TestingSupport.h" @interface ORKResultTestsHelper: NSObject @end @@ -142,7 +143,7 @@ - (void)testTaskViewControllerPrematureViewLoading { ]]; ORKTaskViewController *taskViewController = [[ORKTaskViewController alloc] initWithTask:task taskRunUUID:nil]; ORKStepViewController *viewController = [((ORKOrderedTask *)ORKDynamicCast(taskViewController.task, ORKOrderedTask)).steps.firstObject makeViewControllerWithResult:nil]; - + XCTAssertFalse(viewController.isViewLoaded, "TaskViewController's viewControllerForStep should return a viewController *without* its view loaded"); } @@ -376,6 +377,118 @@ - (void)testTaskViewControllerRestorationWorks { } +/* + We are testing if an ORKFormStepViewController that is saved with results + is restored from an ORKTaskViewController, with a new bundle, or form, + that has removed or modified one of the ORKAnswerFormats, then the + savedResults should remove the invalid answer. + */ +- (void)testTaskViewControllerRestorationWorksAfterTaskChanges { + ORKFormStep *formItemStep = [[ORKFormStep alloc] initWithIdentifier:@"step"]; + + formItemStep.formItems = @[ + [[ORKFormItem alloc] initWithIdentifier:@"item1" text:nil answerFormat:ORKAnswerFormat.booleanAnswerFormat], + [[ORKFormItem alloc] initWithIdentifier:@"item2" text:nil answerFormat:ORKAnswerFormat.textAnswerFormat], + [[ORKFormItem alloc] initWithIdentifier:@"item3" text:nil answerFormat: + [ORKAnswerFormat choiceAnswerFormatWithStyle:ORKChoiceAnswerStyleSingleChoice textChoices:@[ + [[ORKTextChoice alloc] initWithText:@"text1" detailText:@"text1" value:@"text1" exclusive:true], + [[ORKTextChoice alloc] initWithText:@"text2" detailText:@"text2" value:@"text2" exclusive:true], + [[ORKTextChoice alloc] initWithText:@"text3" detailText:@"text3" value:@"text3" exclusive:true], + ]]] + ]; + ORKOrderedTask *task = [[ORKOrderedTask alloc] initWithIdentifier:@"test" steps:@[formItemStep]]; + + NSData *encodedTaskViewControllerData; + { + // create the task as if we were to present it + ORKTaskViewController *taskViewController = [[ORKTaskViewController alloc] initWithTask:task taskRunUUID:nil]; + + // viewWillAppear fills in the _managedStepIdentifiers in the taskViewController + [taskViewController viewWillAppear:false]; + + // make a few answers to test with, simulating answers entered by a user + __auto_type booleanAnswer = [[ORKBooleanQuestionResult alloc] initWithIdentifier:@"item1"]; + booleanAnswer.booleanAnswer = @(YES); + + __auto_type textAnswer = [[ORKTextQuestionResult alloc] initWithIdentifier:@"item2"]; + textAnswer.textAnswer = @"there is no answer, only questions"; + + __auto_type choiceAnswerResult = [[ORKChoiceQuestionResult alloc] initWithIdentifier:@"item3"]; + choiceAnswerResult.choiceAnswers = @[@"text3"]; + + ORKStepResult *stepResult = [[ORKStepResult alloc] initWithStepIdentifier:@"step" results:@[ + booleanAnswer, textAnswer, choiceAnswerResult + ]]; + // set the answers using taskViewController-internal method, to simulate user data entry + [taskViewController setManagedResult:stepResult forKey:@"step"]; + + // archive it + __auto_type keyedArchiver = [[NSKeyedArchiver alloc] initRequiringSecureCoding:YES]; + [taskViewController encodeRestorableStateWithCoder:keyedArchiver]; + encodedTaskViewControllerData = [keyedArchiver encodedData]; + } + XCTAssertNotNil(encodedTaskViewControllerData); + + // init a new taskViewController with the restoration data + { + // important to start with the same task so the identifiers match + ORKResultTestsHelper *taskDelegate = [[ORKResultTestsHelper alloc] init]; + + ORKTaskViewController *taskViewController = [[ORKTaskViewController alloc] initWithTask:task restorationData:encodedTaskViewControllerData delegate:taskDelegate error:nil]; + + [taskViewController viewWillAppear:true]; + + ORKFormStepViewController *formVc = (ORKFormStepViewController *)taskViewController.currentStepViewController; + + // encode the VC with encodedData of a task that supports the [[ORKTextChoice alloc] initWithText:@"text3" detailText:@"text3" value:@"text3" exclusive:true], + __auto_type keyedArchiver = [[NSKeyedArchiver alloc] initRequiringSecureCoding:YES]; + [formVc encodeRestorableStateWithCoder:keyedArchiver]; + encodedTaskViewControllerData = [keyedArchiver encodedData]; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + __auto_type keyedUnarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:encodedTaskViewControllerData]; +#pragma clang diagnostic pop + + ORKFormStep *formItemStep = [[ORKFormStep alloc] initWithIdentifier:@"step"]; + + formItemStep.formItems = @[ + [[ORKFormItem alloc] initWithIdentifier:@"item1" text:nil answerFormat:ORKAnswerFormat.booleanAnswerFormat], + [[ORKFormItem alloc] initWithIdentifier:@"item2" text:nil answerFormat:ORKAnswerFormat.textAnswerFormat], + [[ORKFormItem alloc] initWithIdentifier:@"item3" text:nil answerFormat: + [ORKAnswerFormat choiceAnswerFormatWithStyle:ORKChoiceAnswerStyleSingleChoice textChoices:@[ + [[ORKTextChoice alloc] initWithText:@"text1" detailText:@"text1" value:@"text1" exclusive:true], + [[ORKTextChoice alloc] initWithText:@"text2" detailText:@"text2" value:@"text2" exclusive:true], + ]]] + ]; + ORKFormStepViewController *formVc2 = [[ORKFormStepViewController alloc] initWithStep:formItemStep]; + XCTAssertNotNil(formVc2); + + // expect savedAnswers to be empty, because nothing has been restored + XCTAssertEqual(formVc2.savedAnswers.count, 0); + // expect the unarchiver to decode 3 saved answers, but the invalid one will be removed + // so we should expect 2 saved anwers + [formVc2 decodeRestorableStateWithCoder:keyedUnarchiver]; + XCTAssertEqual(formVc2.savedAnswers.count, 2); + + // test restoration logic directly + NSSet *decodableAnswerTypes = [NSSet setWithObjects:NSMutableDictionary.self, NSString.self, NSNumber.self, NSDate.self, nil]; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + __auto_type keyedUnarchiver2 = [[NSKeyedUnarchiver alloc] initForReadingWithData:encodedTaskViewControllerData]; +#pragma clang diagnostic pop + + NSMutableDictionary *savedAnswers = [keyedUnarchiver2 decodeObjectOfClasses:decodableAnswerTypes forKey:@"savedAnswers"]; + // we expect 3 saved answers from the unarchiver + XCTAssertEqual(savedAnswers.count, 3); + formVc2.savedAnswers = savedAnswers; + [formVc2 removeInvalidSavedAnswers]; + // after removing 1 invalid answer, we expect 2 saved answers + XCTAssertEqual(savedAnswers.count, 2); + } + +} + - (void)testConsentDocumentDecoding { ORKConsentDocument *document = [[ORKConsentDocument alloc] init]; document.signatures = @[ diff --git a/ResearchKitTests/ORKTaskTests.m b/ResearchKitTests/ORKTaskTests.m index 8fee8d66e4..7d6b55ec4d 100644 --- a/ResearchKitTests/ORKTaskTests.m +++ b/ResearchKitTests/ORKTaskTests.m @@ -49,8 +49,17 @@ @interface TestTaskViewControllerDelegate : NSObject *methodCalled; +@property (nonatomic, nullable) NSNumber *overrideHasSaveableResults; @end +@interface ORKTaskViewController (Testing) +- (BOOL)hasSaveableResults; +- (BOOL)isSafeToSkipConfirmation; +@end + +@interface ORKReviewStep (Testing) +- (BOOL)isStandalone; +@end @implementation ORKTaskTests { NSArray *_orderedTaskStepIdentifiers; @@ -1432,7 +1441,81 @@ - (void)testStepViewControllerWillDisappear { XCTAssertEqual(delegate.methodCalled.count, 1); XCTAssertEqualObjects(delegate.methodCalled.firstObject.selectorName, @"taskViewController:stepViewControllerWillDisappear:navigationDirection:"); NSArray *expectedArgs = @[taskViewController, stepViewController, @(ORKStepViewControllerNavigationDirectionForward)]; - XCTAssertEqualObjects(delegate.methodCalled.firstObject.arguments, expectedArgs); + XCTAssertEqualObjects(delegate.methodCalled.firstObject.arguments, expectedArgs); +} + +- (void)testTaskViewControllerCanDiscardLogic { + TestTaskViewControllerDelegate *delegate = [[TestTaskViewControllerDelegate alloc] init]; + ORKOrderedTask *task = [[ORKOrderedTask alloc] initWithIdentifier:@"TestTask" steps:@[ + [[ORKInstructionStep alloc] initWithIdentifier:@"instuction-0"], + [ORKReviewStep standaloneReviewStepWithIdentifier:@"review" steps:nil resultSource:nil], + ]]; + + // test cases where hasSaveableResults == NO + { + MockTaskViewController *taskViewController = [[MockTaskViewController alloc] initWithTask:task taskRunUUID:nil]; + taskViewController.delegate = delegate; + taskViewController.overrideHasSaveableResults = @(NO); + + XCTAssertFalse(taskViewController.modalInPresentation, "modalInPresentation should default to NO"); + [taskViewController viewWillAppear:false]; // get the first step loaded + XCTAssertFalse(taskViewController.modalInPresentation, "modalInPresentation should be NO after viewWillAppear if hasSaveableResults = NO and the current stepViewController is an instructionStep"); + + // if currentStep == instructionStep and not saveable -> canDiscardResults + XCTAssertEqual(taskViewController.currentStepViewController.step.identifier , @"instuction-0"); + XCTAssertTrue([taskViewController isSafeToSkipConfirmation]); + } + + // test cases where hasSaveableResults == YES + { + MockTaskViewController *taskViewController = [[MockTaskViewController alloc] initWithTask:task taskRunUUID:nil]; + taskViewController.delegate = delegate; + taskViewController.overrideHasSaveableResults = @(YES); + + XCTAssertFalse(taskViewController.modalInPresentation, "modalInPresentation should default to NO"); + [taskViewController viewWillAppear:false]; // get the first step loaded + XCTAssertTrue(taskViewController.modalInPresentation, "modalInPresentation should be YES after viewWillAppear if hasSaveableResults = YES and the current stepViewController is an instructionStep"); + + // if currentStep == instructionStep and saveable -> CANNOT discardResults + XCTAssertEqual(taskViewController.currentStepViewController.step.identifier , @"instuction-0"); + XCTAssertFalse([taskViewController isSafeToSkipConfirmation]); + } + + { + // create a reviewStep with steps + ORKStep *reviewableStep = [ORKQuestionStep questionStepWithIdentifier:@"Who's there?" title:nil question:nil answer:nil]; + ORKReviewStep *standaloneReviewStep = [ORKReviewStep standaloneReviewStepWithIdentifier:@"standalone-review" steps:@[reviewableStep] resultSource:nil]; + [task addStep:standaloneReviewStep]; + XCTAssertTrue(ORKDynamicCast([[task steps] lastObject], ORKReviewStep).isStandalone, "review step either was in the wrong spot in the task.steps array, or computed an unexpected value for `isStandalone`. reviewStep with steps != nil should return YES for isStandalone"); + + MockTaskViewController *taskViewController = [[MockTaskViewController alloc] initWithTask:task taskRunUUID:nil]; + [taskViewController viewWillAppear:false]; // get the first step loaded + + // step forward to the standalone reviewStep + [taskViewController goForward]; + [taskViewController goForward]; + ORKReviewStep *testStep = ORKDynamicCast(taskViewController.currentStepViewController.step, ORKReviewStep); + XCTAssertEqual([testStep identifier], @"standalone-review"); + + ORKReviewStepViewController *reviewViewController = ORKDynamicCast(taskViewController.currentStepViewController, ORKReviewStepViewController); + XCTAssertNotNil(reviewViewController, "taskViewController.currentStepViewController should be of type ORKReviewStepViewController at this point"); + } + + { + ORKStep *questionStep = [ORKQuestionStep questionStepWithIdentifier:@"Who's there?" title:nil question:nil answer:nil]; + ORKOrderedTask *task = [[ORKOrderedTask alloc] initWithIdentifier:@"basic task" steps:@[ + questionStep + ]]; + MockTaskViewController *taskViewController = [[MockTaskViewController alloc] initWithTask:task taskRunUUID:nil]; + [taskViewController viewWillAppear:false]; // get the first step loaded + + // confirm we have the expected currentStepViewController + XCTAssertEqual(taskViewController.currentStepViewController.step.identifier, @"Who's there?"); + + // if current viewController.readOnly is FALSE -> CANNOT discardResults + XCTAssertFalse([taskViewController isSafeToSkipConfirmation]); + } + } #endif @@ -1813,4 +1896,16 @@ - (void)flipToPreviousPageFrom:(ORKStepViewController *)fromController { [self.methodCalled addObject:obj]; } +- (BOOL)hasSaveableResults { + BOOL result = NO; + + if (self.overrideHasSaveableResults == nil) { + result = [super hasSaveableResults]; + } else { + result = self.overrideHasSaveableResults.boolValue; + } + + return result; +} + @end diff --git a/ResearchKitTests/samples.bundle/ORKAgeAnswerFormat.json b/ResearchKitTests/samples.bundle/ORKAgeAnswerFormat.json new file mode 100644 index 0000000000..88d62819eb --- /dev/null +++ b/ResearchKitTests/samples.bundle/ORKAgeAnswerFormat.json @@ -0,0 +1,2 @@ +{"defaultValue":0,"relativeYear":2023,"_class":"ORKAgeAnswerFormat","minimumAge":0,"maximumAge":80,"minimumAgeCustomText":"","maximumAgeCustomText":"","showYear":false,"useYearForResult":false ,"treatMinAgeAsRange":false,"treatMaxAgeAsRange":false, "showDontKnowButton":false,"customDontKnowButtonText":"","dontKnowButtonStyle":0} + diff --git a/ResearchKitTests/samples.bundle/ORKColorChoice.json b/ResearchKitTests/samples.bundle/ORKColorChoice.json new file mode 100644 index 0000000000..89b3deb199 --- /dev/null +++ b/ResearchKitTests/samples.bundle/ORKColorChoice.json @@ -0,0 +1,3 @@ +{"_class":"ORKColorChoice","value":"foo","text":"","detailText":"","color": {"r":1, "g":1, "b":1, "a":1 }, "exclusive":false} + + diff --git a/ResearchKitTests/samples.bundle/ORKColorChoiceAnswerFormat.json b/ResearchKitTests/samples.bundle/ORKColorChoiceAnswerFormat.json new file mode 100644 index 0000000000..8e782be953 --- /dev/null +++ b/ResearchKitTests/samples.bundle/ORKColorChoiceAnswerFormat.json @@ -0,0 +1,2 @@ +{"style":"singleChoice","_class":"ORKColorChoiceAnswerFormat","colorChoices":[],"showDontKnowButton":false,"customDontKnowButtonText":"","dontKnowButtonStyle":0} + diff --git a/ResearchKitTests/samples.bundle/ORKConditionStepConfiguration.json b/ResearchKitTests/samples.bundle/ORKConditionStepConfiguration.json new file mode 100644 index 0000000000..a6692947e2 --- /dev/null +++ b/ResearchKitTests/samples.bundle/ORKConditionStepConfiguration.json @@ -0,0 +1,4 @@ +{"_class":"ORKConditionStepConfiguration","stepIdentifier":"","conditionsFormItemIdentifier":"", "formItems":[{"placeholder":"","answerFormat":{"_class":"ORKAnswerFormat"},"_class":"ORKFormItem","optional":false,"learnMoreItem":{"_class":"ORKLearnMoreItem"},"text":"","identifier":"","detailText":"","showsProgress":false, "tagText": ""} +], "conditions":[{"_class":"ORKHealthCondition","identifier":"","displayName":"","value":"lorem"}]} + + diff --git a/ResearchKitTests/samples.bundle/ORKFamilyHistoryResult.json b/ResearchKitTests/samples.bundle/ORKFamilyHistoryResult.json new file mode 100644 index 0000000000..2623c04501 --- /dev/null +++ b/ResearchKitTests/samples.bundle/ORKFamilyHistoryResult.json @@ -0,0 +1,3 @@ +{"_class":"ORKFamilyHistoryResult","relatedPersons":[], "displayedConditions":[],"endDate":"2019-05-27T00:35:06-0700","startDate":"2019-05-27T00:35:06-0700","identifier":"","userInfo":{}} + + diff --git a/ResearchKitTests/samples.bundle/ORKFamilyHistoryStep.json b/ResearchKitTests/samples.bundle/ORKFamilyHistoryStep.json new file mode 100644 index 0000000000..3fa542e03d --- /dev/null +++ b/ResearchKitTests/samples.bundle/ORKFamilyHistoryStep.json @@ -0,0 +1,2 @@ +{"_class":"ORKFamilyHistoryStep","relativeGroups":[], "conditionStepConfiguration":{"_class":"ORKConditionStepConfiguration","stepIdentifier":"","formItems":[{"placeholder":"","answerFormat":{"_class":"ORKAnswerFormat"},"_class":"ORKFormItem","optional":false,"learnMoreItem":{"_class":"ORKLearnMoreItem"},"text":"","identifier":"","detailText":"","showsProgress":false, "tagText": ""} +], "conditions":[{"_class":"ORKHealthCondition","identifier":"","displayName":"","value":"lorem"}]}, "bodyItems":[],"optional":false,"useSurveyMode":false,"shouldTintImages":true,"iconImage":{"imageName":"1425D9D4-DB67-4B5D-93B0-FE3125E67262"},"auxiliaryImage":{"imageName":"5DEF32D5-D23E-4E8F-8C05-4B1D24E6257D"},"title":"","identifier":"","footnote":"","shouldAutomaticallyAdjustImageTintColor":0,"image":{"imageName":"DB613E0C-4491-457B-B958-C7FBE6232101"},"text":"","detailText":"","headerTextAlignment": 0,"imageContentMode": 0, "headerTextAlignment": 0,"bodyItemTextAlignment":0,"buildInBodyItems": 0,"useExtendedPadding":0,"earlyTerminationConfiguration":{"_class":"ORKEarlyTerminationConfiguration"}} diff --git a/ResearchKitTests/samples.bundle/ORKHealthCondition.json b/ResearchKitTests/samples.bundle/ORKHealthCondition.json new file mode 100644 index 0000000000..d18a91ce5c --- /dev/null +++ b/ResearchKitTests/samples.bundle/ORKHealthCondition.json @@ -0,0 +1,2 @@ +{"_class":"ORKHealthCondition","identifier":"","displayName":"","value":"lorem"} + diff --git a/ResearchKitTests/samples.bundle/ORKRelatedPerson.json b/ResearchKitTests/samples.bundle/ORKRelatedPerson.json new file mode 100644 index 0000000000..75bfefefce --- /dev/null +++ b/ResearchKitTests/samples.bundle/ORKRelatedPerson.json @@ -0,0 +1,2 @@ +{"_class":"ORKRelatedPerson","identifier":"","groupIdentifier":"", "identifierForCellTitle":"","taskResult":{"_class":"ORKTaskResult","endDate":"2019-05-27T00:35:06-0700","startDate":"2019-05-27T00:35:06-0700","taskRunUUID":"FDFCC3EC-01B6-43E7-8200-9B93E2744BB8","identifier":"","results":[],"userInfo":{},"device":{ "_class":"ORKDevice","product":"","osVersion": "","osBuild": "","platform": ""}}} + diff --git a/ResearchKitTests/samples.bundle/ORKRelativeGroup.json b/ResearchKitTests/samples.bundle/ORKRelativeGroup.json new file mode 100644 index 0000000000..9182597b01 --- /dev/null +++ b/ResearchKitTests/samples.bundle/ORKRelativeGroup.json @@ -0,0 +1,3 @@ +{"_class":"ORKRelativeGroup","identifier":"","name":"","sectionTitle":"","sectionDetailText":"","maxAllowed":0, "identifierForCellTitle":"","formSteps":[], "detailTextIdentifiers":[]} + + diff --git a/ResearchKitTests/samples.bundle/ORKTableStep.json b/ResearchKitTests/samples.bundle/ORKTableStep.json index a62fb76cd0..28059e1def 100644 --- a/ResearchKitTests/samples.bundle/ORKTableStep.json +++ b/ResearchKitTests/samples.bundle/ORKTableStep.json @@ -1 +1 @@ -{"bodyItems":[],"allowsSelection":false,"detailText":"","headerTextAlignment": 0,"optional":false,"useSurveyMode":false,"shouldTintImages":true,"_class":"ORKTableStep","auxiliaryImage":{"imageName":"C594DE5C-AB0F-468C-956E-8347C5E14E08"},"iconImage":{"imageName":"A3C3C914-C4ED-4633-9852-67D8AB687094"},"isBulleted":false,"title":"","identifier":"","footnote":"","shouldAutomaticallyAdjustImageTintColor":0,"image":{"imageName":"FC4CCE13-5C8A-434F-979C-1E48ADDEBBE0"},"items":[],"text":"","bulletType":0,"bulletIconNames":[],"imageContentMode": 0,"bodyItemTextAlignment":0,"buildInBodyItems": 0,"pinNavigationContainer":false,"useExtendedPadding":0,"earlyTerminationConfiguration": {"_class": "ORKEarlyTerminationConfiguration"}} +{"bodyItems":[],"allowsSelection":false,"detailText":"","bottomPadding":35,"headerTextAlignment": 0,"optional":false,"useSurveyMode":false,"shouldTintImages":true,"_class":"ORKTableStep","auxiliaryImage":{"imageName":"C594DE5C-AB0F-468C-956E-8347C5E14E08"},"iconImage":{"imageName":"A3C3C914-C4ED-4633-9852-67D8AB687094"},"isBulleted":false,"title":"","identifier":"","footnote":"","shouldAutomaticallyAdjustImageTintColor":0,"image":{"imageName":"FC4CCE13-5C8A-434F-979C-1E48ADDEBBE0"},"items":[],"text":"","bulletType":0,"bulletIconNames":[],"imageContentMode": 0,"bodyItemTextAlignment":0,"buildInBodyItems": 0,"pinNavigationContainer":false,"useExtendedPadding":0,"earlyTerminationConfiguration": {"_class": "ORKEarlyTerminationConfiguration"}} diff --git a/ResearchKitUI/Common/Answer Format/Choice Format Helpers/ORKColorChoiceCellGroup.h b/ResearchKitUI/Common/Answer Format/Choice Format Helpers/ORKColorChoiceCellGroup.h new file mode 100644 index 0000000000..22893c8294 --- /dev/null +++ b/ResearchKitUI/Common/Answer Format/Choice Format Helpers/ORKColorChoiceCellGroup.h @@ -0,0 +1,76 @@ +/* + Copyright (c) 2023, 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 +#import + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class ORKColorChoiceAnswerFormat; +@class ORKChoiceViewCell; +@class ORKTextChoiceAnswerFormat; +@class ORKScaleAnswerFormat; +@class ORKContinuousScaleAnswerFormat; +@class ORKTextScaleAnswerFormat; + + +@interface ORKColorChoiceCellGroup : NSObject + +- (instancetype)initWithColorChoiceAnswerFormat:(ORKColorChoiceAnswerFormat *)answerFormat + answer:(nullable id)answer + beginningIndexPath:(NSIndexPath *)indexPath + immediateNavigation:(BOOL)immediateNavigation; + +@property (nonatomic, assign) ORKQuestionStepPresentationStyle presentationStyle; + +@property (nonatomic, strong, nullable) id answer; + +@property (nonatomic, weak) id delegate; + +@property (nonatomic, copy) ORKColorChoiceAnswerFormat *answerFormat; + + +- (nullable ORKChoiceViewCell *)cellAtIndexPath:(NSIndexPath *)indexPath withReuseIdentifier:(nullable NSString *)identifier; + +- (BOOL)containsIndexPath:(NSIndexPath *)indexPath; + +- (void)didSelectCellAtIndexPath:(NSIndexPath *)indexPath; + +- (nullable id)answerForBoolean; + +- (NSUInteger)size; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ResearchKitUI/Common/Answer Format/Choice Format Helpers/ORKColorChoiceCellGroup.m b/ResearchKitUI/Common/Answer Format/Choice Format Helpers/ORKColorChoiceCellGroup.m new file mode 100644 index 0000000000..ab5e2bb941 --- /dev/null +++ b/ResearchKitUI/Common/Answer Format/Choice Format Helpers/ORKColorChoiceCellGroup.m @@ -0,0 +1,206 @@ +/* + Copyright (c) 2023, 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 "ORKColorChoiceCellGroup.h" + +#import "ORKAnswerFormat_Internal.h" +#import "ORKAnswerTextView.h" +#import "ORKChoiceAnswerFormatHelper.h" +#import "ORKChoiceViewCell.h" +#import "ORKHelpers_Internal.h" +#import "ORKSelectionSubTitleLabel.h" +#import "ORKSelectionTitleLabel.h" +#import "ORKTextChoiceCellGroup.h" + + +@implementation ORKColorChoiceCellGroup { + ORKChoiceAnswerFormatHelper *_helper; + BOOL _singleChoice; + BOOL _immediateNavigation; + NSIndexPath *_beginningIndexPath; + + NSMutableDictionary *_cells; +} + +- (instancetype)initWithColorChoiceAnswerFormat:(ORKColorChoiceAnswerFormat *)answerFormat + answer:(id)answer + beginningIndexPath:(NSIndexPath *)indexPath + immediateNavigation:(BOOL)immediateNavigation { + self = [super init]; + if (self) { + _beginningIndexPath = indexPath; + _helper = [[ORKChoiceAnswerFormatHelper alloc] initWithAnswerFormat:answerFormat]; + _answerFormat = answerFormat; + _singleChoice = answerFormat.style == ORKChoiceAnswerStyleSingleChoice; + _immediateNavigation = immediateNavigation; + _cells = [NSMutableDictionary new]; + _presentationStyle = ORKQuestionStepPresentationStyleDefault; + [self setAnswer:answer]; + } + return self; +} + +- (NSUInteger)size { + return [_helper choiceCount]; +} + +- (void)setAnswer:(id)answer { + _answer = answer; + + [self safelySetSelectedIndexesForAnswer:answer]; +} + +- (ORKChoiceViewCell *)cellAtIndexPath:(NSIndexPath *)indexPath withReuseIdentifier:(NSString *)identifier { + if ([self containsIndexPath:indexPath] == NO) { + return nil; + } + + return [self cellAtIndex:indexPath.row-_beginningIndexPath.row withReuseIdentifier:identifier]; +} + +- (ORKChoiceViewCell *)cellAtIndex:(NSUInteger)index withReuseIdentifier:(NSString *)identifier { + ORKChoiceViewCell *cell = _cells[@(index)]; + + if (cell == nil) { + ORKColorChoice *colorChoice = [_helper colorChoiceAtIndex:index]; + + if ([self.presentationStyle isEqualToString:ORKQuestionStepPresentationStyleDefault]) { + cell = [[ORKChoiceViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier]; + } else if ([self.presentationStyle isEqualToString:ORKQuestionStepPresentationStylePlatter]) { + cell = [[ORKChoiceViewPlatterCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier]; + } + + cell.isExclusive = colorChoice.exclusive; + cell.immediateNavigation = _immediateNavigation; + cell.shouldIgnoreDarkMode = YES; + [cell setSwatchColor:colorChoice.color]; + [cell setPrimaryText:colorChoice.text]; + [cell setDetailText:colorChoice.detailText]; + + _cells[@(index)] = cell; + + [self safelySetSelectedIndexesForAnswer:_answer]; + } + + return cell; +} + +- (void)didSelectCellAtIndexPath:(NSIndexPath *)indexPath { + if ([self containsIndexPath:indexPath]== NO) { + return; + } + NSUInteger index = indexPath.row - _beginningIndexPath.row; + ORKChoiceViewCell *touchedCell = [self cellAtIndex:index withReuseIdentifier:nil]; + + if (_singleChoice) { + [touchedCell setCellSelected:YES highlight:YES]; + for (ORKChoiceViewCell *cell in _cells.allValues) { + if (cell != touchedCell) { + [cell setCellSelected:NO highlight:NO]; + } + } + } else { + [touchedCell setCellSelected:!touchedCell.isCellSelected highlight:YES]; + if (touchedCell.isCellSelected) { + ORKColorChoice *touchedChoice = [_helper colorChoiceAtIndex:index]; + for (NSNumber *num in _cells.allKeys) { + ORKChoiceViewCell *cell = _cells[num]; + ORKColorChoice *choice = [_helper colorChoiceAtIndex:num.unsignedIntegerValue]; + if (cell != touchedCell && (touchedChoice.exclusive || (cell.isCellSelected && choice.exclusive))) { + [cell setCellSelected:NO highlight:NO]; + } + } + } + } + + _answer = [_helper answerForSelectedIndexes:[self selectedIndexes]]; + [self.delegate answerChangedForIndexPath:indexPath]; +} + +- (BOOL)containsIndexPath:(NSIndexPath *)indexPath { + NSUInteger count = _helper.choiceCount; + + return (indexPath.section == _beginningIndexPath.section) && + (indexPath.row >= _beginningIndexPath.row) && + (indexPath.row < (_beginningIndexPath.row + count)); +} + +-(void)safelySetSelectedIndexesForAnswer:(nullable id)answer { + NSArray *selectedIndexes; + @try { + selectedIndexes = [_helper selectedIndexesForAnswer:answer]; + } @catch (NSException *exception) { + selectedIndexes = [[NSArray alloc] init]; + ORK_Log_Error("Error getting selected indexes: %@", exception.reason); + } @finally { + [self setSelectedIndexes: selectedIndexes]; + } +} + +- (void)setSelectedIndexes:(NSArray *)indexes { + for (NSUInteger index = 0; index < self.size; index++ ) { + BOOL selected = [indexes containsObject:@(index)]; + + if (selected) { + // In case the cell has not been created, need to create cell + ORKChoiceViewCell *cell = [self cellAtIndex:index withReuseIdentifier:nil]; + [cell setCellSelected:YES highlight:NO]; + } else { + // It is ok to not create the cell at here + ORKChoiceViewCell *cell = _cells[@(index)]; + [cell setCellSelected:NO highlight:NO]; + + } + } +} + +- (NSArray *)selectedIndexes { + NSMutableArray *indexes = [NSMutableArray new]; + + for (NSUInteger index = 0; index < self.size; index++ ) { + ORKChoiceViewCell *cell = _cells[@(index)]; + if (cell.isCellSelected) { + [indexes addObject:@(index)]; + } + } + + return [indexes copy]; +} + +- (id)answerForBoolean { + // Boolean type uses a different format + if ([_answer isKindOfClass:[NSArray class]] ) { + NSArray *answerArray = _answer; + return (answerArray.count > 0) ? answerArray.firstObject : nil; + } + return _answer; +} + +@end diff --git a/ResearchKitUI/Common/Answer Format/Control Views/ORKAgePicker.h b/ResearchKitUI/Common/Answer Format/Control Views/ORKAgePicker.h new file mode 100644 index 0000000000..439b23b3b6 --- /dev/null +++ b/ResearchKitUI/Common/Answer Format/Control Views/ORKAgePicker.h @@ -0,0 +1,41 @@ +/* + Copyright (c) 2023, Apple Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. No license is granted to the trademarks of + the copyright holders even if such marks are included in this software. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +@import UIKit; +#import + + +NS_ASSUME_NONNULL_BEGIN + +@interface ORKAgePicker : NSObject + +@end + +NS_ASSUME_NONNULL_END diff --git a/ResearchKitUI/Common/Answer Format/Control Views/ORKAgePicker.m b/ResearchKitUI/Common/Answer Format/Control Views/ORKAgePicker.m new file mode 100644 index 0000000000..d44d6fa0e5 --- /dev/null +++ b/ResearchKitUI/Common/Answer Format/Control Views/ORKAgePicker.m @@ -0,0 +1,260 @@ +/* + Copyright (c) 2023, 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 "ORKAgePicker.h" + +#import "ORKResult_Private.h" +#import "ORKAnswerFormat_Internal.h" +#import "ORKAccessibilityFunctions.h" + + +@interface ORKAgePicker () + +@end + +static const CGFloat PickerSpacerHeight = 15.0; +static const CGFloat PickerMinimumHeight = 34.0; + +@implementation ORKAgePicker { + UIPickerView *_pickerView; + ORKAgeAnswerFormat *_answerFormat; + NSArray *_ageOptions; + id _answer; + __weak id _pickerDelegate; +} + +@synthesize pickerDelegate = _pickerDelegate; + +- (instancetype)initWithAnswerFormat:(ORKAgeAnswerFormat *)answerFormat answer:(id)answer pickerDelegate:(id)delegate { + self = [super init]; + if (self) { + NSAssert([answerFormat isKindOfClass:[ORKAgeAnswerFormat class]], @"answerFormat should be ORKAgeAnswerFormat"); + + _answerFormat = answerFormat; + _pickerDelegate = delegate; + _answer = answer; + + NSMutableArray *tempArray = [NSMutableArray new]; + for (int i = _answerFormat.minimumAge; i <= _answerFormat.maximumAge; i++) { + [tempArray addObject:[[NSNumber alloc] initWithInt:i]]; + } + + _ageOptions = [tempArray copy]; + } + return self; +} + +- (UIView *)pickerView { + if (_pickerView == nil) { + _pickerView = [[UIPickerView alloc] init]; + _pickerView.dataSource = self; + _pickerView.delegate = self; + [self setAnswer:_answer]; + } + return _pickerView; +} + +- (void)setAnswer:(id)answer { + _answer = answer; + + if (!ORKIsAnswerEmpty(_answer)) { + NSNumber *value = (NSNumber *)_answer; + NSUInteger indexOfAgeOption = 0; + + if ([self isAnswerAYear:value]) { + // if year is passed in, its converted back to age so picker can be set correctly + NSNumber *ageValueForYear = [[NSNumber alloc] initWithInt:_answerFormat.relativeYear - value.intValue]; + indexOfAgeOption = [self indexForAgeValue:ageValueForYear]; + } else if (value < 0) { + // if a sentinel value is passed in, either the first or last index is passed back accordingly + indexOfAgeOption = [value intValue] == [ORKAgeAnswerFormat minimumAgeSentinelValue] ? 0 : _ageOptions.count - 1; + } else { + indexOfAgeOption = [self indexForAgeValue:value]; + } + + // offset the index by 1 to account for the empty value in first position of the picker + [_pickerView selectRow:indexOfAgeOption + 1 inComponent:0 animated:YES]; + + return; + } + + if (_answerFormat.defaultValue >= _answerFormat.minimumAge && _answerFormat.defaultValue <= _answerFormat.maximumAge) { + // use default to select a row + NSNumber *defaultValue = [self defaultAnswerValue]; + NSUInteger indexOfAgeOption = [_ageOptions indexOfObject: defaultValue]; + + [_pickerView selectRow:indexOfAgeOption + 1 inComponent:0 animated:YES]; + } +} + +- (id)answer { + return _answer; +} + +- (BOOL)isAnswerAYear:(NSNumber *)answer { + NSInteger relativeYear = _answerFormat.relativeYear; + + return answer.intValue <= relativeYear && answer.intValue >= (relativeYear - _answerFormat.maximumAge); +} + +- (NSUInteger)indexForAgeValue:(NSNumber *)ageValue { + if ([ageValue intValue] < _answerFormat.minimumAge) { + return 0; + } else if ([ageValue intValue] > _answerFormat.maximumAge) { + return _ageOptions.count - 1; + } + + return [_ageOptions indexOfObject:ageValue]; +} + +- (NSNumber *)ageToYear:(NSNumber *)age { + return [[NSNumber alloc] initWithInt: _answerFormat.relativeYear - age.intValue]; +} + +- (NSNumber *)defaultAnswerValue { + return [[NSNumber alloc] initWithInt:_answerFormat.defaultValue]; +} + +- (NSNumber *)selectedAnswerValue { + NSNumber *answer = nil; + NSInteger selectedRowIndex = [_pickerView selectedRowInComponent:0]; + + // The first option in the picker is an empty string. We return nil if this is selected + if (selectedRowIndex == 0) { + return nil; + } else if ((selectedRowIndex == 1 && _answerFormat.treatMinAgeAsRange) || (selectedRowIndex == _ageOptions.count && _answerFormat.treatMaxAgeAsRange)) { + // if the min or max has been selected a sentinel value is passed back if it should be treated as a range. + int sentinelValue = selectedRowIndex == 1 ? [ORKAgeAnswerFormat minimumAgeSentinelValue] : [ORKAgeAnswerFormat maximumAgeSentinelValue]; + answer = [NSNumber numberWithInt:sentinelValue]; + return answer; + } + + answer = [self ageForIndex:selectedRowIndex]; + + return _answerFormat.useYearForResult ? [self ageToYear:answer] : answer; +} + +- (NSString *)selectedLabelText { + return [_answerFormat stringForAnswer:_answer]; +} + +- (void)pickerWillAppear { + [self pickerView]; + [self valueDidChange:self]; + [self accessibilityFocusOnPickerElement]; +} + +- (void)valueDidChange:(id)sender { + _answer = [self selectedAnswerValue]; + if ([self.pickerDelegate respondsToSelector:@selector(picker:answerDidChangeTo:)]) { + [self.pickerDelegate picker:self answerDidChangeTo:_answer]; + } +} + +- (NSNumber *)ageForIndex:(NSInteger)index { + // index will need to be offset to account for empty choice added to picker + index = (index - 1 < 0) ? 0 : index - 1; + return [_ageOptions objectAtIndex:index]; +} + +#pragma mark - Accessibility + +- (void)accessibilityFocusOnPickerElement { + if (UIAccessibilityIsVoiceOverRunning()) { + ORKAccessibilityPerformBlockAfterDelay(0.75, ^{ + NSArray *axElements = [self.pickerView accessibilityElements]; + if ([axElements count] > 0) { + UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, [axElements objectAtIndex:0]); + } + }); + } +} + +#pragma mark - UIPickerViewDataSource + +- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView { + return 1; +} + +- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component { + return _ageOptions.count + 1; +} + +- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component { + + if (row == 0) { + return @""; + } + + NSString *title; + int ageValue = [self ageForIndex:row].intValue; + + if (row == 1 && _answerFormat.minimumAgeCustomText) { + title = _answerFormat.minimumAgeCustomText; + } else if (row == _ageOptions.count && _answerFormat.maximumAgeCustomText) { + title = _answerFormat.maximumAgeCustomText; + } else if (_answerFormat.showYear) { + title = [NSString stringWithFormat:@"%li (%i)", _answerFormat.relativeYear - ageValue, ageValue]; + } else { + title = [NSString stringWithFormat:@"%i", ageValue]; + } + + return title; +} + +- (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view +{ + UILabel* valueLabel = (UILabel*)view; + if (!valueLabel) + { + valueLabel = [[UILabel alloc] init]; + [valueLabel setFont:[self defaultFont]]; + [valueLabel setTextAlignment:NSTextAlignmentCenter]; + } + valueLabel.text = [self pickerView:pickerView titleForRow:row forComponent:component]; + return valueLabel; +} + +- (UIFont *)defaultFont { + UIFontDescriptor *descriptor = [UIFontDescriptor preferredFontDescriptorWithTextStyle:UIFontTextStyleBody]; + return [UIFont systemFontOfSize:((NSNumber *)[descriptor objectForKey:UIFontDescriptorSizeAttribute]).doubleValue + 2.0]; +} + +- (CGFloat)pickerView:(UIPickerView *)pickerView rowHeightForComponent:(NSInteger)component { + UIFont *font = [self defaultFont]; + CGFloat height = font.pointSize + PickerSpacerHeight; + return (height < PickerMinimumHeight ? PickerMinimumHeight : height); +} + +- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component { + [self valueDidChange:self]; +} + +@end diff --git a/ResearchKitUI/Common/Answer Format/Control Views/ORKAnswerTextView.m b/ResearchKitUI/Common/Answer Format/Control Views/ORKAnswerTextView.m index bc99393d03..3fef63153c 100644 --- a/ResearchKitUI/Common/Answer Format/Control Views/ORKAnswerTextView.m +++ b/ResearchKitUI/Common/Answer Format/Control Views/ORKAnswerTextView.m @@ -70,6 +70,7 @@ - (void)commonInit { _placeholderTextView = [[UITextView alloc] initWithFrame:CGRectZero]; _placeholderTextView.textColor = [UIColor placeholderTextColor]; _placeholderTextView.userInteractionEnabled = NO; + _placeholderTextView.isAccessibilityElement = NO; _placeholderTextView.translatesAutoresizingMaskIntoConstraints = NO; [self insertSubview:_placeholderTextView atIndex:0]; @@ -183,6 +184,19 @@ - (BOOL)accessibilityDismissKeyboardForAction:(UIAccessibilityCustomAction *)cus return YES; } +- (NSString *)accessibilityLabel { + if (!_placeholderTextView.hidden) { + return _placeholderTextView.text; + } else { + return [super accessibilityLabel]; + } +} + +- (NSArray *)automationElements { + // Placeholder TextView is excluded as an accessibility element but should still be available to automation. + return [[super automationElements] arrayByAddingObject:_placeholderTextView]; +} + - (NSArray *)accessibilityCustomActions { NSArray *actions = nil; diff --git a/ResearchKitUI/Common/Answer Format/Control Views/ORKLocationSelectionView.h b/ResearchKitUI/Common/Answer Format/Control Views/ORKLocationSelectionView.h index 9025682201..428dea8f91 100644 --- a/ResearchKitUI/Common/Answer Format/Control Views/ORKLocationSelectionView.h +++ b/ResearchKitUI/Common/Answer Format/Control Views/ORKLocationSelectionView.h @@ -37,6 +37,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION #if !TARGET_OS_VISION diff --git a/ResearchKitUI/Common/Answer Format/Control Views/ORKLocationSelectionView.m b/ResearchKitUI/Common/Answer Format/Control Views/ORKLocationSelectionView.m index 10a1573c42..70cc8edb3e 100644 --- a/ResearchKitUI/Common/Answer Format/Control Views/ORKLocationSelectionView.m +++ b/ResearchKitUI/Common/Answer Format/Control Views/ORKLocationSelectionView.m @@ -36,7 +36,7 @@ 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. */ -#if !TARGET_OS_VISION +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && TARGET_OS_IOS && !TARGET_OS_VISION #import "ORKLocationSelectionView.h" @@ -356,7 +356,7 @@ - (void)setAnswer:(ORKLocation *)answer updateMap:(BOOL)updateMap { ORKLocation *location = isAnswerClassORKLocation ? (ORKLocation *)_answer : nil; if (location) { - + if (!location.userInput || !location.region |!location.postalAddress) { // redo geo decoding if any of them is missing [self reverseGeocodeAndDisplay:location]; diff --git a/ResearchKitUI/Common/Answer Format/Control Views/ORKPicker.m b/ResearchKitUI/Common/Answer Format/Control Views/ORKPicker.m index 2d4628d921..4603644cdd 100644 --- a/ResearchKitUI/Common/Answer Format/Control Views/ORKPicker.m +++ b/ResearchKitUI/Common/Answer Format/Control Views/ORKPicker.m @@ -31,6 +31,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE #import "ORKPicker.h" +#import "ORKAgePicker.h" #import "ORKDateTimePicker.h" #import "ORKHeightPicker.h" #import "ORKWeightPicker.h" @@ -38,7 +39,6 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE #import "ORKValuePicker.h" #import "ORKMultipleValuePicker.h" - #import "ORKAnswerFormat.h" @implementation ORKPicker : NSObject @@ -71,9 +71,10 @@ @implementation ORKPicker : NSObject picker = [[ORKWeightPicker alloc] initWithAnswerFormat:answerFormat answer:answer pickerDelegate:delegate]; } else if ([answerFormat isKindOfClass:[ORKMultipleValuePickerAnswerFormat class]]) { picker = [[ORKMultipleValuePicker alloc] initWithAnswerFormat:answerFormat answer:answer pickerDelegate:delegate]; + } else if ([answerFormat isKindOfClass:[ORKAgeAnswerFormat class]]) { + picker = [[ORKAgePicker alloc] initWithAnswerFormat:answerFormat answer:answer pickerDelegate:delegate]; } - NSAssert(picker, @"Cannot create picker for answer format %@", answerFormat); return picker; diff --git a/ResearchKitUI/Common/Answer Format/Control Views/ORKSESSelectionView.m b/ResearchKitUI/Common/Answer Format/Control Views/ORKSESSelectionView.m index 90126d6e45..bd714693ed 100644 --- a/ResearchKitUI/Common/Answer Format/Control Views/ORKSESSelectionView.m +++ b/ResearchKitUI/Common/Answer Format/Control Views/ORKSESSelectionView.m @@ -143,6 +143,8 @@ - (void)setupLabels { for (UILabel *label in @[_frontLabel, _rearLabel]) { label.numberOfLines = 1; label.font = [self bodyTextFont]; + label.adjustsFontSizeToFitWidth = YES; + label.minimumScaleFactor = 0.2; label.textColor = [UIColor grayColor]; label.translatesAutoresizingMaskIntoConstraints = NO; [self addSubview:label]; @@ -185,6 +187,8 @@ - (void)setupDontKnowButtonWithText:(NSString *)text { _frontLabel = [UILabel new]; _frontLabel.numberOfLines = 1; _frontLabel.font = [self bodyTextFont]; + _frontLabel.adjustsFontSizeToFitWidth = YES; + _frontLabel.minimumScaleFactor = 0.9; _frontLabel.textColor = [UIColor grayColor]; _frontLabel.translatesAutoresizingMaskIntoConstraints = NO; _frontLabel.textAlignment = NSTextAlignmentLeft; diff --git a/ResearchKitUI/Common/Answer Format/Form Step Views/ORKFormItemCell.h b/ResearchKitUI/Common/Answer Format/Form Step Views/ORKFormItemCell.h index 413638a61f..1c7d643ba9 100644 --- a/ResearchKitUI/Common/Answer Format/Form Step Views/ORKFormItemCell.h +++ b/ResearchKitUI/Common/Answer Format/Form Step Views/ORKFormItemCell.h @@ -113,11 +113,11 @@ NS_ASSUME_NONNULL_BEGIN @end -#if !TARGET_OS_VISION +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && !TARGET_OS_VISION @interface ORKFormItemLocationCell : ORKFormItemCell @end -#endif +#endif @interface ORKFormItemSESCell : ORKFormItemCell diff --git a/ResearchKitUI/Common/Answer Format/Form Step Views/ORKFormItemCell.m b/ResearchKitUI/Common/Answer Format/Form Step Views/ORKFormItemCell.m index 48e96d705a..f27d300c57 100644 --- a/ResearchKitUI/Common/Answer Format/Form Step Views/ORKFormItemCell.m +++ b/ResearchKitUI/Common/Answer Format/Form Step Views/ORKFormItemCell.m @@ -1820,6 +1820,7 @@ - (void)setFormItem:(ORKFormItem *)formItem { [answerFormat isKindOfClass:[ORKTimeIntervalAnswerFormat class]] || [answerFormat isKindOfClass:[ORKValuePickerAnswerFormat class]] || [answerFormat isKindOfClass:[ORKMultipleValuePickerAnswerFormat class]] || + [answerFormat isKindOfClass:[ORKAgeAnswerFormat class]] || [answerFormat isKindOfClass:[ORKHeightAnswerFormat class]] || [answerFormat isKindOfClass:[ORKWeightAnswerFormat class]])) { [self throwPickerTypeException]; @@ -1929,7 +1930,7 @@ - (void)dontKnowButtonWasPressed #if !TARGET_OS_VISION #pragma mark - ORKFormItemLocationCell - +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION @interface ORKFormItemLocationCell () @property (nonatomic, assign) BOOL editingHighlight; diff --git a/ResearchKitUI/Common/Answer Format/Question Step Views/ORKSurveyAnswerCellForLocation.h b/ResearchKitUI/Common/Answer Format/Question Step Views/ORKSurveyAnswerCellForLocation.h index 903b26893a..0f20f150a1 100644 --- a/ResearchKitUI/Common/Answer Format/Question Step Views/ORKSurveyAnswerCellForLocation.h +++ b/ResearchKitUI/Common/Answer Format/Question Step Views/ORKSurveyAnswerCellForLocation.h @@ -39,9 +39,11 @@ NS_ASSUME_NONNULL_BEGIN +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION @interface ORKSurveyAnswerCellForLocation : ORKSurveyAnswerCell @end +#endif NS_ASSUME_NONNULL_END diff --git a/ResearchKitUI/Common/Answer Format/Question Step Views/ORKSurveyAnswerCellForLocation.m b/ResearchKitUI/Common/Answer Format/Question Step Views/ORKSurveyAnswerCellForLocation.m index c4e3adf5f3..1b85722d24 100644 --- a/ResearchKitUI/Common/Answer Format/Question Step Views/ORKSurveyAnswerCellForLocation.m +++ b/ResearchKitUI/Common/Answer Format/Question Step Views/ORKSurveyAnswerCellForLocation.m @@ -29,8 +29,7 @@ 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. */ -#if !TARGET_OS_VISION - +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && !TARGET_OS_VISION #import "ORKSurveyAnswerCellForLocation.h" diff --git a/ResearchKitUI/Common/Answer Format/SwiftUI Views/SwiftUIViewFactory.swift b/ResearchKitUI/Common/Answer Format/SwiftUI Views/SwiftUIViewFactory.swift index cfbeedc625..4b12392b00 100644 --- a/ResearchKitUI/Common/Answer Format/SwiftUI Views/SwiftUIViewFactory.swift +++ b/ResearchKitUI/Common/Answer Format/SwiftUI Views/SwiftUIViewFactory.swift @@ -29,8 +29,8 @@ */ import Foundation -import SwiftUI import ResearchKit +import SwiftUI @objc public class SwiftUIViewFactory: NSObject { diff --git a/ResearchKitUI/Common/Answer Format/SwiftUI Views/TextChoiceView.swift b/ResearchKitUI/Common/Answer Format/SwiftUI Views/TextChoiceView.swift index ba6bcbe663..35a4df3ece 100644 --- a/ResearchKitUI/Common/Answer Format/SwiftUI Views/TextChoiceView.swift +++ b/ResearchKitUI/Common/Answer Format/SwiftUI Views/TextChoiceView.swift @@ -28,8 +28,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import SwiftUI import ResearchKit +import SwiftUI struct TextChoiceView: View { @ObservedObject var textChoiceHelper: SwiftUITextChoiceHelper @@ -101,7 +101,7 @@ struct TextChoiceView: View { if let img = image { Image(uiImage: img) .resizable() - .aspectRatio(contentMode: .fill) + .aspectRatio(contentMode: .fit) .frame(width: 115) .overlay(RoundedRectangle(cornerRadius: 6).stroke(Color.clear, lineWidth: 1)) diff --git a/ResearchKitUI/Common/Container Views/ORKBodyContainerView.m b/ResearchKitUI/Common/Container Views/ORKBodyContainerView.m index 594b3dddcd..f5a65ad9cd 100644 --- a/ResearchKitUI/Common/Container Views/ORKBodyContainerView.m +++ b/ResearchKitUI/Common/Container Views/ORKBodyContainerView.m @@ -306,6 +306,7 @@ - (void)setupBodyStyleImage { [_cardView addSubview:imageView]; [imageView.leadingAnchor constraintEqualToAnchor:_cardView.leadingAnchor].active = YES; [imageView.centerYAnchor constraintEqualToAnchor:_cardView.centerYAnchor].active = YES; + [_cardView.heightAnchor constraintGreaterThanOrEqualToAnchor:imageView.heightAnchor].active = YES; _cardStyleAccessoryView = imageView; } else { [self addArrangedSubview:imageView]; diff --git a/ResearchKitUI/Common/Container Views/ORKNavigationContainerView.m b/ResearchKitUI/Common/Container Views/ORKNavigationContainerView.m index 3a55187da6..19c507ac36 100644 --- a/ResearchKitUI/Common/Container Views/ORKNavigationContainerView.m +++ b/ResearchKitUI/Common/Container Views/ORKNavigationContainerView.m @@ -137,8 +137,9 @@ - (void)setupSkipButton { - (void)setupFootnoteLabel { _footnoteLabel = [ORKFootnoteLabel new]; _footnoteLabel.numberOfLines = 0; - _footnoteLabel.textAlignment = NSTextAlignmentNatural; + _footnoteLabel.textAlignment = NSTextAlignmentCenter; _footnoteLabel.translatesAutoresizingMaskIntoConstraints = NO; + _footnoteLabel.textColor = UIColor.secondaryLabelColor; [self addSubview:_footnoteLabel]; } @@ -478,9 +479,36 @@ - (void)setUpConstraints { } UIView *lastView = _skipButton ? : _continueButton; + + if (![self neverHasFootnote]) { + UIView *lastButton = [self neverHasSkipButton] ? _continueButton : _skipButton; + [_regularConstraints addObjectsFromArray:@[ + [NSLayoutConstraint constraintWithItem:_footnoteLabel + attribute:NSLayoutAttributeTop + relatedBy:NSLayoutRelationEqual + toItem:lastButton ? : self.safeAreaLayoutGuide + attribute:lastButton ? NSLayoutAttributeBottom : NSLayoutAttributeTop + multiplier:1.0 + constant:lastButton ? standardSpacing : topSpacing], + [NSLayoutConstraint constraintWithItem:_footnoteLabel + attribute:NSLayoutAttributeLeft + relatedBy:NSLayoutRelationEqual + toItem:self.safeAreaLayoutGuide + attribute:NSLayoutAttributeLeft + multiplier:1.0 + constant:leftRightPadding], + [NSLayoutConstraint constraintWithItem:_footnoteLabel + attribute:NSLayoutAttributeRight + relatedBy:NSLayoutRelationEqual + toItem:self.safeAreaLayoutGuide + attribute:NSLayoutAttributeRight + multiplier:1.0 + constant:-leftRightPadding], + ]]; + lastView = _footnoteLabel; + } if (lastView) { - [_regularConstraints addObject:[NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual diff --git a/ResearchKitUI/Common/Container Views/ORKStepContentView.m b/ResearchKitUI/Common/Container Views/ORKStepContentView.m index 972f7bc92b..3475ee97c1 100644 --- a/ResearchKitUI/Common/Container Views/ORKStepContentView.m +++ b/ResearchKitUI/Common/Container Views/ORKStepContentView.m @@ -1130,6 +1130,10 @@ - (void)setStepContentViewBottomConstraint { constant:constant]; } +- (void)setStepContentViewBottomConstraint:(CGFloat)bottomConstraint { + _stepContentBottomConstraint.constant = bottomConstraint; +} + - (void)setupUpdatedConstraints { _updatedConstraints = [[NSMutableArray alloc] init]; } diff --git a/ResearchKitUI/Common/Container Views/ORKStepContentView_Private.h b/ResearchKitUI/Common/Container Views/ORKStepContentView_Private.h index 606272a263..1f6db9ac8e 100644 --- a/ResearchKitUI/Common/Container Views/ORKStepContentView_Private.h +++ b/ResearchKitUI/Common/Container Views/ORKStepContentView_Private.h @@ -54,6 +54,8 @@ NS_ASSUME_NONNULL_BEGIN - (nullable ORKCompletionCheckmarkView *)completionCheckmarkView; +- (void)setStepContentViewBottomConstraint:(CGFloat)bottomConstraint; + @end NS_ASSUME_NONNULL_END diff --git a/ResearchKitUI/Common/Skin/ORKChoiceViewCell+ORKColorChoice.h b/ResearchKitUI/Common/Skin/ORKChoiceViewCell+ORKColorChoice.h new file mode 100644 index 0000000000..1e8c7f8242 --- /dev/null +++ b/ResearchKitUI/Common/Skin/ORKChoiceViewCell+ORKColorChoice.h @@ -0,0 +1,43 @@ +/* + Copyright (c) 2023, 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 "ORKChoiceViewCell.h" +#import "ORKAnswerFormat.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface ORKChoiceViewCell (ORKColorChoice) + +- (void)configureWithColorChoice:(ORKColorChoice *)colorChoice + isLastItem:(BOOL)isLastItem; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ResearchKitUI/Common/Skin/ORKChoiceViewCell+ORKColorChoice.m b/ResearchKitUI/Common/Skin/ORKChoiceViewCell+ORKColorChoice.m new file mode 100644 index 0000000000..ad75704da3 --- /dev/null +++ b/ResearchKitUI/Common/Skin/ORKChoiceViewCell+ORKColorChoice.m @@ -0,0 +1,46 @@ +/* + Copyright (c) 2023, 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 "ORKChoiceViewCell+ORKColorChoice.h" + +@implementation ORKChoiceViewCell (ORKColorChoice) + +- (void)configureWithColorChoice:(nonnull ORKColorChoice *)colorChoice isLastItem:(BOOL)isLastItem { + [self setSwatchColor:colorChoice.color]; + self.shouldIgnoreDarkMode = YES; + + self.isExclusive = colorChoice.exclusive;; + self.isLastItem = isLastItem; + self.immediateNavigation = NO; + [self setPrimaryText:colorChoice.text]; + [self setDetailText:colorChoice.detailText]; +} + +@end diff --git a/ResearchKitUI/Common/Skin/ORKLabel.m b/ResearchKitUI/Common/Skin/ORKLabel.m index dd76a1579d..3a783df24f 100644 --- a/ResearchKitUI/Common/Skin/ORKLabel.m +++ b/ResearchKitUI/Common/Skin/ORKLabel.m @@ -67,6 +67,7 @@ - (void)willMoveToWindow:(UIWindow *)newWindow { } - (void)updateAppearance { + self.font = [[self class] defaultFont]; [self invalidateIntrinsicContentSize]; } diff --git a/ResearchKitUI/Common/Step/Custom Step/ORKCustomStepViewController.m b/ResearchKitUI/Common/Step/Custom Step/ORKCustomStepViewController.m index 27c726488b..9da9ab4dae 100644 --- a/ResearchKitUI/Common/Step/Custom Step/ORKCustomStepViewController.m +++ b/ResearchKitUI/Common/Step/Custom Step/ORKCustomStepViewController.m @@ -142,6 +142,10 @@ - (void)configureContainerView { [_containerView setStepDetailText:self.customStep.detailText]; } + if (self.customStep.bodyItems) { + [_containerView setBodyItems:self.customStep.bodyItems]; + } + if (self.customStep.headerTextAlignment) { [_containerView setStepHeaderTextAlignment:self.customStep.headerTextAlignment]; } diff --git a/ResearchKitUI/Common/Step/Family History/Family History TableView Views/ORKFamilyHistoryRelatedPersonCell.h b/ResearchKitUI/Common/Step/Family History/Family History TableView Views/ORKFamilyHistoryRelatedPersonCell.h new file mode 100644 index 0000000000..71c30ae9de --- /dev/null +++ b/ResearchKitUI/Common/Step/Family History/Family History TableView Views/ORKFamilyHistoryRelatedPersonCell.h @@ -0,0 +1,67 @@ +/* + Copyright (c) 2023, 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 + +NS_ASSUME_NONNULL_BEGIN + +@class ORKFamilyHistoryRelatedPersonCell; +@protocol ORKFamilyHistoryRelatedPersonCellDelegate; + +typedef NS_ENUM(NSInteger, ORKFamilyHistoryTooltipOption) { + ORKFamilyHistoryTooltipOptionEdit, + ORKFamilyHistoryTooltipOptionDelete +}; + + +@protocol ORKFamilyHistoryRelatedPersonCellDelegate + +- (void)familyHistoryRelatedPersonCell:(ORKFamilyHistoryRelatedPersonCell *)relatedPersonCell + tappedOption:(ORKFamilyHistoryTooltipOption)option; + +@end + + +@interface ORKFamilyHistoryRelatedPersonCell : UITableViewCell + +@property (nonatomic) NSString *title; +@property (nonatomic) NSString *relativeID; +@property (nonatomic) NSArray *detailValues; +@property (nonatomic) NSArray *conditionValues; +@property (nonatomic) BOOL isLastItemBeforeAddRelativeButton; +@property (nonatomic, weak, nullable) id delegate; + +- (void)configureWithDetailValues:(NSArray *)detailValues + conditionsValues:(NSArray *)conditionsValues +isLastItemBeforeAddRelativeButton:(BOOL)isLastItemBeforeAddRelativeButton; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ResearchKitUI/Common/Step/Family History/Family History TableView Views/ORKFamilyHistoryRelatedPersonCell.m b/ResearchKitUI/Common/Step/Family History/Family History TableView Views/ORKFamilyHistoryRelatedPersonCell.m new file mode 100644 index 0000000000..55d30b4ace --- /dev/null +++ b/ResearchKitUI/Common/Step/Family History/Family History TableView Views/ORKFamilyHistoryRelatedPersonCell.m @@ -0,0 +1,467 @@ +/* + Copyright (c) 2023, 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 "ORKFamilyHistoryRelatedPersonCell.h" + +#import "ORKAccessibilityFunctions.h" + +#import + +static const CGFloat BackgroundViewBottomPadding = 18.0; +static const CGFloat CellLeftRightPadding = 12.0; +static const CGFloat CellTopBottomPadding = 12.0; +static const CGFloat CellBottomPadding = 8.0; +static const CGFloat CellLabelTopPadding = 8.0; +static const CGFloat CellBottomPaddingBeforeAddRelativeButton = 20.0; +static const CGFloat ContentLeftRightPadding = 16.0; +static const CGFloat DividerViewTopBottomPadding = 10.0; +static const CGFloat OptionsButtonWidth = 20.0; + +typedef NS_ENUM(NSUInteger, ORKFamilyHistoryEditDeleteViewEvent) { + ORKFamilyHistoryEditDeleteViewEventEdit = 0, + ORKFamilyHistoryEditDeleteViewEventDelete, +}; + +typedef void (^ORKFamilyHistoryEditDeleteViewEventHandler)(ORKFamilyHistoryEditDeleteViewEvent); + +@implementation ORKFamilyHistoryRelatedPersonCell { + UIView *_backgroundView; + UIView *_dividerView; + UILabel *_titleLabel; + UILabel *_conditionsLabel; + UIButton *_optionsButton; + + NSArray *_detailListLabels; + NSArray *_conditionListLabels; + + NSMutableArray *_viewConstraints; +} + +- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { + self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; + if (self) { + [self setBackgroundColor:[UIColor clearColor]]; + [self.contentView setBackgroundColor:[UIColor clearColor]]; + } + + return self; +} + +- (UIMenu *)optionsMenu API_AVAILABLE(ios(13.0)) { + ORKWeakTypeOf(self) weakSelf = self; + // Edit Button + UIImage *editImage = [UIImage systemImageNamed:@"pencil"]; + UIAction *editMenuItem = [UIAction actionWithTitle:ORKLocalizedString(@"FAMILY_HISTORY_EDIT_ENTRY", @"") + image:editImage + identifier:nil + handler:^(__kindof UIAction * _Nonnull action) { + ORKStrongTypeOf(weakSelf) strongSelf = weakSelf; + if (strongSelf) { + [strongSelf handleContentViewEvent:ORKFamilyHistoryEditDeleteViewEventEdit]; + } + }]; + + // Delete Button + UIImage *deleteImage = [UIImage systemImageNamed:@"trash.fill"]; + UIAction *deleteMenuItem = [UIAction actionWithTitle:ORKLocalizedString(@"FAMILY_HISTORY_DELETE_ENTRY", @"") + image:deleteImage + identifier:nil + handler:^(__kindof UIAction * _Nonnull action) { + ORKStrongTypeOf(weakSelf) strongSelf = weakSelf; + if (strongSelf) { + [strongSelf handleContentViewEvent:ORKFamilyHistoryEditDeleteViewEventDelete]; + } + }]; + [deleteMenuItem setAttributes:UIMenuElementAttributesDestructive]; + + NSArray *menuChildren = @[ + editMenuItem, + deleteMenuItem + ]; + UIMenu *menu = [UIMenu menuWithTitle:@"" children:menuChildren]; + return menu; +} + +- (UIAlertController *)alertForOptionsMenu { + ORKWeakTypeOf(self) weakSelf = self; + UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil + message:nil + preferredStyle:UIAlertControllerStyleActionSheet]; + UIAlertAction *editAction = [UIAlertAction actionWithTitle:ORKLocalizedString(@"FAMILY_HISTORY_EDIT_ENTRY", @"") + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * _Nonnull action) { + ORKStrongTypeOf(weakSelf) strongSelf = weakSelf; + if (strongSelf) { + [strongSelf handleContentViewEvent:ORKFamilyHistoryEditDeleteViewEventEdit]; + } + }]; + + UIAlertAction *deleteAction = [UIAlertAction actionWithTitle:ORKLocalizedString(@"FAMILY_HISTORY_DELETE_ENTRY", @"") + style:UIAlertActionStyleDestructive + handler:^(UIAlertAction * _Nonnull action) { + ORKStrongTypeOf(weakSelf) strongSelf = weakSelf; + if (strongSelf) { + [strongSelf handleContentViewEvent:ORKFamilyHistoryEditDeleteViewEventDelete]; + } + }]; + + UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:ORKLocalizedString(@"BUTTON_CANCEL", @"") + style:UIAlertActionStyleCancel + handler:^(UIAlertAction * _Nonnull action) { + }]; + + [alert addAction:editAction]; + [alert addAction:deleteAction]; + [alert addAction:cancelAction]; + return alert; +} + +- (void)presentOptionsMenuAlert { + UIAlertController *alert = [self alertForOptionsMenu]; + NSArray *windows = [[UIApplication sharedApplication] windows]; + for (UIWindow *window in windows) { + if (window.isKeyWindow) { + [window.rootViewController presentViewController:alert animated:true completion:nil]; + } + } +} + +- (void)setupSubViews { + _backgroundView = [UIView new]; + _backgroundView.clipsToBounds = YES; + _backgroundView.layer.cornerRadius = 12.0; + _backgroundView.translatesAutoresizingMaskIntoConstraints = NO; + [_backgroundView setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; + [self.contentView addSubview:_backgroundView]; + + _titleLabel = [self _primaryLabel]; + [_titleLabel setText:_title]; + [_backgroundView addSubview:_titleLabel]; + + _optionsButton = [UIButton new]; + _optionsButton.translatesAutoresizingMaskIntoConstraints = NO; + _optionsButton.backgroundColor = [UIColor clearColor]; + _optionsButton.tintColor = [UIColor systemGrayColor]; + _optionsButton.accessibilityLabel = ORKLocalizedString(@"AX_FAMILY_HISTORY_EDIT_BUTTON", nil); + _optionsButton.accessibilityHint = ORKLocalizedString(@"AX_FAMILY_HISTORY_EDIT_BUTTON", nil); + _optionsButton.accessibilityTraits = UIAccessibilityTraitButton; + if (@available(iOS 14.0, *)) { + _optionsButton.menu = [self optionsMenu]; + _optionsButton.showsMenuAsPrimaryAction = YES; + } else { + [_optionsButton addTarget:self action:@selector(presentOptionsMenuAlert) forControlEvents:UIControlEventTouchUpInside]; + } + + UIImageConfiguration *configuration = [UIImageSymbolConfiguration configurationWithFont:[UIFont preferredFontForTextStyle:UIFontTextStyleBody] scale:ORKImageScaleToUse()]; + [_optionsButton setImage:[UIImage systemImageNamed:@"ellipsis.circle" withConfiguration:configuration] forState:UIControlStateNormal]; + [_optionsButton.imageView setContentMode:UIViewContentModeScaleAspectFit]; + + [_backgroundView addSubview:_optionsButton]; + + _dividerView = [UIView new]; + _dividerView.translatesAutoresizingMaskIntoConstraints = NO; + _dividerView.backgroundColor = [UIColor separatorColor]; + [_backgroundView addSubview:_dividerView]; + + _conditionsLabel = [self _primaryLabel]; + _conditionsLabel.text = ORKLocalizedString(@"FAMILY_HISTORY_CONDITIONS", @""); + [_backgroundView addSubview:_conditionsLabel]; + + [self updateViewColors]; +} + +- (void)updateViewColors { + _backgroundView.backgroundColor = [UIColor secondarySystemGroupedBackgroundColor]; + _dividerView.backgroundColor = [UIColor separatorColor]; + _titleLabel.textColor = [UIColor labelColor]; + _conditionsLabel.textColor = [UIColor labelColor]; + _optionsButton.tintColor = [UIColor secondaryLabelColor]; + + [self updateViewLabelsTextColor:[UIColor secondaryLabelColor]]; +} + +- (void)updateViewLabelsTextColor:(UIColor *)color { + for (UILabel* detailLabel in _detailListLabels) { + detailLabel.textColor = color; + } + for (UILabel* conditionLabel in _conditionListLabels) { + conditionLabel.textColor = color; + } +} + +- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection { + [super traitCollectionDidChange:previousTraitCollection]; + [self updateViewColors]; +} + +- (void)_clearActiveConstraints { + if (_viewConstraints.count > 0) { + [NSLayoutConstraint deactivateConstraints:_viewConstraints]; + } + + _viewConstraints = [NSMutableArray new]; + + for (UILabel *label in _conditionListLabels) { + [label removeFromSuperview]; + } + _conditionListLabels = @[]; + + for (UILabel *label in _detailListLabels) { + [label removeFromSuperview]; + } + _detailListLabels = @[]; +} + +- (NSArray *)_backgroundViewContraints { + CGFloat bottomPadding = _isLastItemBeforeAddRelativeButton ? -CellBottomPaddingBeforeAddRelativeButton : -CellBottomPadding; + + return @[ + [_backgroundView.topAnchor constraintEqualToAnchor:self.contentView.topAnchor], + [_backgroundView.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor constant:ContentLeftRightPadding], + [_backgroundView.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor constant:-ContentLeftRightPadding], + [_backgroundView.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor constant:bottomPadding] + ]; +} + +- (NSArray *)_titleLabelConstraints { + // _titleLabel becomes the first detailsLowerMostView, which sets `bottomAnchor` accordingly. + return @[ + [_titleLabel.topAnchor constraintEqualToAnchor:_backgroundView.topAnchor constant:CellTopBottomPadding], + [_titleLabel.leadingAnchor constraintEqualToAnchor:_backgroundView.leadingAnchor constant:CellLeftRightPadding], + [_titleLabel.trailingAnchor constraintEqualToAnchor:_optionsButton.leadingAnchor constant:-CellLeftRightPadding] + ]; +} + +- (NSArray *)_optionsButtonConstraints { + // leadingAnchor: set in _titleLabelConstraints + // bottomAnchor: ambiguous + + NSLayoutConstraint *widthConstraint = [_optionsButton.widthAnchor constraintEqualToConstant:OptionsButtonWidth]; + [widthConstraint setPriority:UILayoutPriorityDefaultLow]; + + return @[ + [_optionsButton.topAnchor constraintEqualToAnchor:_titleLabel.topAnchor], + widthConstraint, + [_optionsButton.trailingAnchor constraintEqualToAnchor:_backgroundView.trailingAnchor constant:-CellLeftRightPadding] + ]; +} + +- (NSArray *)_dividerConstraintsFromView:(UIView *)referenceView { + CGFloat separatorHeight = 1.0 / [UIScreen mainScreen].scale; + NSLayoutConstraint *heightConstraint = [_dividerView.heightAnchor constraintEqualToConstant:separatorHeight]; + [heightConstraint setPriority:UILayoutPriorityDefaultLow]; + return @[ + [_dividerView.leadingAnchor constraintEqualToAnchor:_backgroundView.leadingAnchor], + [_dividerView.trailingAnchor constraintEqualToAnchor:_backgroundView.trailingAnchor], + heightConstraint, + [_dividerView.topAnchor constraintEqualToAnchor: referenceView.bottomAnchor constant:DividerViewTopBottomPadding], + [_dividerView.bottomAnchor constraintEqualToAnchor:_conditionsLabel.topAnchor constant:-DividerViewTopBottomPadding] + ]; +} + +- (NSArray *)_conditionsLabelConstraints { + return @[ + [_conditionsLabel.leadingAnchor constraintEqualToAnchor:_backgroundView.leadingAnchor constant:CellLeftRightPadding], + [_conditionsLabel.trailingAnchor constraintEqualToAnchor:_backgroundView.trailingAnchor constant:-CellLeftRightPadding] + ]; +} + +- (NSArray *)_constraintsForLabel:(UILabel *)label relativeTo:(UIView *)referenceView { + return @[ + [label.leadingAnchor constraintEqualToAnchor:_backgroundView.leadingAnchor constant:CellLeftRightPadding], + [label.trailingAnchor constraintEqualToAnchor:_backgroundView.trailingAnchor constant:-CellLeftRightPadding], + [label.topAnchor constraintEqualToAnchor:referenceView.bottomAnchor constant:CellLabelTopPadding] + ]; +} + +- (void)setupConstraints { + [self _clearActiveConstraints]; + _viewConstraints = [NSMutableArray new]; + + // backgroundView constraints + [_viewConstraints addObjectsFromArray:[self _backgroundViewContraints]]; + + // titleLabel constraints + [_viewConstraints addObjectsFromArray:[self _titleLabelConstraints]]; + + // optionsButton constraints + [_viewConstraints addObjectsFromArray:[self _optionsButtonConstraints]]; + + // find lower most view to constrain the dividerView to + UIView *detailsLowerMostView = _titleLabel; + + _detailListLabels = [self getDetailLabels]; + + for (UILabel *label in _detailListLabels) { + [_backgroundView addSubview:label]; + [_viewConstraints addObjectsFromArray: [self _constraintsForLabel:label relativeTo:detailsLowerMostView]]; + detailsLowerMostView = label; + } + + // dividerView constraints + [_viewConstraints addObjectsFromArray: [self _dividerConstraintsFromView:detailsLowerMostView]]; + + // conditionsLabel constraints + [_viewConstraints addObjectsFromArray:[self _conditionsLabelConstraints]]; + + // find lower most view to constrain the backgroundView to + UIView *conditionsLowerMostView = _conditionsLabel; + + _conditionListLabels = [self getConditionLabels]; + + for (UILabel *label in _conditionListLabels) { + [_backgroundView addSubview:label]; + [_viewConstraints addObjectsFromArray: [self _constraintsForLabel:label relativeTo:conditionsLowerMostView]]; + conditionsLowerMostView = label; + } + + // set backgroundView's bottom anchor to lower most UILabel + NSLayoutConstraint *bottomConstraint = [conditionsLowerMostView.lastBaselineAnchor constraintEqualToAnchor:_backgroundView.bottomAnchor constant:-BackgroundViewBottomPadding]; + [bottomConstraint setPriority:UILayoutPriorityDefaultHigh]; + [_viewConstraints addObject:bottomConstraint]; + + [NSLayoutConstraint activateConstraints:_viewConstraints]; +} + +- (UILabel *)_baseLabel { + UILabel *label = [UILabel new]; + label.translatesAutoresizingMaskIntoConstraints = NO; + label.lineBreakMode = NSLineBreakByWordWrapping; + label.textAlignment = NSTextAlignmentNatural; + label.numberOfLines = 0; + return label; +} + +- (UILabel *)_primaryLabel { + UILabel *label = [self _baseLabel]; + label.font = [self titleLabelFont]; + return label; +} + +- (UILabel *)_secondaryLabel { + UILabel *label = [self _baseLabel]; + label.font = [self conditionsLabelFont]; + label.textColor = self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark ? [UIColor whiteColor] : [UIColor lightGrayColor]; + return label; +} + +- (NSArray *)getDetailLabels { + NSMutableArray *labels = [NSMutableArray new]; + + for (NSString *detailValue in _detailValues) { + UILabel *label = [self _secondaryLabel]; + label.text = detailValue; + [labels addObject:label]; + } + + return [labels copy]; +} + +- (NSArray *)getConditionLabels { + NSMutableArray *labels = [NSMutableArray new]; + + if (!_conditionValues || _conditionValues.count == 0) { + UILabel *noneSelectedLabel = [self _secondaryLabel]; + noneSelectedLabel.text = @""; + [labels addObject:noneSelectedLabel]; + } else { + for (NSString *conditionValue in _conditionValues) { + UILabel *label = [self _secondaryLabel]; + label.text = conditionValue; + [labels addObject:label]; + } + } + + return [labels copy]; +} + +- (void)handleContentViewEvent:(ORKFamilyHistoryEditDeleteViewEvent)event { + switch (event) { + case ORKFamilyHistoryEditDeleteViewEventEdit: + [_delegate familyHistoryRelatedPersonCell:self tappedOption:ORKFamilyHistoryTooltipOptionEdit]; + break; + + case ORKFamilyHistoryEditDeleteViewEventDelete: + [_delegate familyHistoryRelatedPersonCell:self tappedOption:ORKFamilyHistoryTooltipOptionDelete]; + break; + } +} + +- (void)setTitle:(NSString *)title { + _title = title; +} + +- (void)configureWithDetailValues:(NSArray *)detailValues + conditionsValues:(NSArray *)conditionsValues +isLastItemBeforeAddRelativeButton:(BOOL)isLastItemBeforeAddRelativeButton { + _detailValues = detailValues; + _conditionValues = conditionsValues; + _isLastItemBeforeAddRelativeButton = isLastItemBeforeAddRelativeButton; + + [self setupSubViews]; + [self setupConstraints]; +} + +- (UIFont *)titleLabelFont { + UIFontDescriptor *descriptor = [UIFontDescriptor preferredFontDescriptorWithTextStyle:UIFontTextStyleSubheadline]; + UIFontDescriptor *fontDescriptor = [descriptor fontDescriptorWithSymbolicTraits:(UIFontDescriptorTraitBold)]; + return [UIFont fontWithDescriptor:fontDescriptor size:[[fontDescriptor objectForKey: UIFontDescriptorSizeAttribute] doubleValue]]; +} + +- (UIFont *)conditionsLabelFont { + UIFontDescriptor *descriptor = [UIFontDescriptor preferredFontDescriptorWithTextStyle:UIFontTextStyleSubheadline]; + UIFontDescriptor *fontDescriptor = [descriptor fontDescriptorWithSymbolicTraits:(UIFontDescriptorTraitUIOptimized)]; + return [UIFont fontWithDescriptor:fontDescriptor size:[[fontDescriptor objectForKey: UIFontDescriptorSizeAttribute] doubleValue]]; +} + +- (void)prepareForReuse { + _title = @""; + _relativeID = @""; + + [_titleLabel removeFromSuperview]; + _titleLabel = nil; + + [_conditionsLabel removeFromSuperview]; + _conditionsLabel = nil; + + [_optionsButton removeFromSuperview]; + _optionsButton = nil; + + [_dividerView removeFromSuperview]; + _dividerView = nil; + + [_backgroundView removeFromSuperview]; + _backgroundView = nil; + + [self _clearActiveConstraints]; + [super prepareForReuse]; +} + +@end diff --git a/ResearchKitUI/Common/Step/Family History/Family History TableView Views/ORKFamilyHistoryTableFooterView.h b/ResearchKitUI/Common/Step/Family History/Family History TableView Views/ORKFamilyHistoryTableFooterView.h new file mode 100644 index 0000000000..a42381dc56 --- /dev/null +++ b/ResearchKitUI/Common/Step/Family History/Family History TableView Views/ORKFamilyHistoryTableFooterView.h @@ -0,0 +1,52 @@ +/* + Copyright (c) 2023, 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 + +NS_ASSUME_NONNULL_BEGIN + +@class ORKRelativeGroup; +@class ORKFamilyHistoryTableFooterView; + +@protocol ORKFamilyHistoryTableFooterViewDelegate + +- (void)ORKFamilyHistoryTableFooterView:(ORKFamilyHistoryTableFooterView *)footerView didSelectFooterForRelativeGroup:(NSString *)relativeGroup; + +@end + +@interface ORKFamilyHistoryTableFooterView: UIView + +- (instancetype)initWithTitle:(NSString *)title relativeGroupIdentifier:(NSString *)relativeGroupIdentifier delegate:(id)delegate; + +- (void)setExpanded:(BOOL)isExpanded; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ResearchKitUI/Common/Step/Family History/Family History TableView Views/ORKFamilyHistoryTableFooterView.m b/ResearchKitUI/Common/Step/Family History/Family History TableView Views/ORKFamilyHistoryTableFooterView.m new file mode 100644 index 0000000000..77b4890523 --- /dev/null +++ b/ResearchKitUI/Common/Step/Family History/Family History TableView Views/ORKFamilyHistoryTableFooterView.m @@ -0,0 +1,172 @@ +/* + Copyright (c) 2023, 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 "ORKFamilyHistoryTableFooterView.h" + +static const CGFloat CellLeftRightPadding = 12.0; +static const CGFloat CellTopCollapsedPadding = 10.0; +static const CGFloat CellBottomCollapsedPadding = 30.0; + +static const CGFloat CellTopExpandedPadding = 0.0; +static const CGFloat CellBottomExpandedPadding = 20.0; + +static const CGFloat ViewButtonLeadingPadding = 5.0; +static const CGFloat ViewButtonTopBottomPadding = 12.0; +static const CGFloat ViewLeftRightPadding = 16.0; + +@implementation ORKFamilyHistoryTableFooterView { + NSString *_relativeGroupIdentifier; + NSString *_title; + + UILabel *_titleLabel; + UIImageView *_iconImageview; + + UIButton *_viewButton; + + NSMutableArray *_viewConstraints; + NSLayoutConstraint *topConstraint; + NSLayoutConstraint *bottomConstraint; + __weak id _delegate; +} + +- (instancetype)initWithTitle:(NSString *)title relativeGroupIdentifier:(NSString *)relativeGroupIdentifier delegate:(id)delegate { + self = [super init]; + + if (self) { + _title = [title copy]; + _relativeGroupIdentifier = [relativeGroupIdentifier copy]; + _delegate = delegate; + + self.backgroundColor = [UIColor clearColor]; + + [self setupSubviews]; + [self setupConstraints]; + [self enableAccessibilitySupport]; + } + return self; +} + +- (void)setFrame:(CGRect)frame { + frame.origin.x += ViewLeftRightPadding; + frame.size.width -= 2 * ViewLeftRightPadding; + [super setFrame:frame]; +} + +- (void)setExpanded:(BOOL)isExpanded { + topConstraint.constant = isExpanded ? -CellTopExpandedPadding : -CellTopCollapsedPadding; + bottomConstraint.constant = isExpanded ? CellBottomCollapsedPadding : CellBottomExpandedPadding; + [self setNeedsUpdateConstraints]; +} + +- (void)setupSubviews { + _viewButton = [UIButton new]; + _viewButton.translatesAutoresizingMaskIntoConstraints = NO; + _viewButton.clipsToBounds = YES; + _viewButton.layer.cornerRadius = 12.0; + [_viewButton addTarget:self action:@selector(buttonWasPressed) forControlEvents:UIControlEventTouchUpInside]; + [self addSubview:_viewButton]; + + _titleLabel = [UILabel new]; + _titleLabel.text = [_title copy]; + _titleLabel.numberOfLines = 0; + _titleLabel.translatesAutoresizingMaskIntoConstraints = NO; + _titleLabel.lineBreakMode = NSLineBreakByWordWrapping; + _titleLabel.font = [self titleLabelFont]; + + _titleLabel.textAlignment = NSTextAlignmentLeft; + [_viewButton addSubview:_titleLabel]; + + _iconImageview = [UIImageView new]; + _iconImageview.image = [UIImage systemImageNamed:@"plus.circle.fill"]; + _iconImageview.translatesAutoresizingMaskIntoConstraints = NO; + _iconImageview.backgroundColor = [UIColor clearColor]; + _iconImageview.tintColor = [UIColor systemBlueColor]; + [_viewButton addSubview:_iconImageview]; + + [self updateViewColors]; +} + +- (void)updateViewColors { + _titleLabel.textColor = self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark ? [UIColor whiteColor] : [UIColor systemBlueColor]; + _viewButton.backgroundColor = [UIColor secondarySystemGroupedBackgroundColor]; +} + +- (void)enableAccessibilitySupport { + self.isAccessibilityElement = true; + self.accessibilityTraits = UIAccessibilityTraitButton; + self.accessibilityLabel = [_title copy]; + self.accessibilityHint = [_title copy]; +} + +- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection { + [super traitCollectionDidChange:previousTraitCollection]; + [self updateViewColors]; +} + +- (void)setupConstraints { + if (_viewConstraints.count > 0) { + [NSLayoutConstraint deactivateConstraints:_viewConstraints]; + } + + _viewConstraints = [NSMutableArray new]; + + [_viewConstraints addObject:[_viewButton.leadingAnchor constraintEqualToAnchor:self.leadingAnchor]]; + [_viewConstraints addObject:[_viewButton.trailingAnchor constraintEqualToAnchor:self.trailingAnchor]]; + + topConstraint = [self.topAnchor constraintEqualToAnchor:_viewButton.topAnchor constant:-CellTopCollapsedPadding]; + [_viewConstraints addObject:topConstraint]; + + bottomConstraint = [self.bottomAnchor constraintEqualToAnchor:_viewButton.bottomAnchor constant:CellBottomCollapsedPadding]; + [_viewConstraints addObject: bottomConstraint]; + + [_viewConstraints addObject:[_titleLabel.centerYAnchor constraintEqualToAnchor:_viewButton.centerYAnchor]]; + [_viewConstraints addObject:[_titleLabel.leadingAnchor constraintEqualToAnchor:_viewButton.leadingAnchor constant:CellLeftRightPadding]]; + [_viewConstraints addObject:[_titleLabel.topAnchor constraintEqualToAnchor:_viewButton.topAnchor constant:ViewButtonTopBottomPadding]]; + [_viewConstraints addObject:[_titleLabel.bottomAnchor constraintEqualToAnchor:_viewButton.bottomAnchor constant:-ViewButtonTopBottomPadding]]; + + [_viewConstraints addObject:[_titleLabel.trailingAnchor constraintEqualToAnchor:_iconImageview.leadingAnchor constant:ViewButtonLeadingPadding]]; + [_viewConstraints addObject:[_iconImageview.centerYAnchor constraintEqualToAnchor:_viewButton.centerYAnchor]]; + [_viewConstraints addObject:[_iconImageview.trailingAnchor constraintEqualToAnchor:_viewButton.trailingAnchor constant:-CellLeftRightPadding]]; + + [NSLayoutConstraint activateConstraints:_viewConstraints]; +} + +- (UIFont *)titleLabelFont { + UIFontDescriptor *descriptor = [UIFontDescriptor preferredFontDescriptorWithTextStyle:UIFontTextStyleBody]; + UIFontDescriptor *fontDescriptor = [descriptor fontDescriptorWithSymbolicTraits:(UIFontDescriptorTraitUIOptimized)]; + return [UIFont fontWithDescriptor:fontDescriptor size:[[fontDescriptor objectForKey: UIFontDescriptorSizeAttribute] doubleValue]]; +} + +- (void)buttonWasPressed { + [_delegate ORKFamilyHistoryTableFooterView:self didSelectFooterForRelativeGroup:_relativeGroupIdentifier]; +} + +@end + diff --git a/ResearchKitUI/Common/Step/Family History/Family History TableView Views/ORKFamilyHistoryTableHeaderView.h b/ResearchKitUI/Common/Step/Family History/Family History TableView Views/ORKFamilyHistoryTableHeaderView.h new file mode 100644 index 0000000000..ee8999516a --- /dev/null +++ b/ResearchKitUI/Common/Step/Family History/Family History TableView Views/ORKFamilyHistoryTableHeaderView.h @@ -0,0 +1,44 @@ +/* + Copyright (c) 2023, 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; + +NS_ASSUME_NONNULL_BEGIN + +@interface ORKFamilyHistoryTableHeaderView: UIView + +- (instancetype)initWithTitle:(NSString *)title + detailText:(nullable NSString *)text; + +- (void)setExpanded:(BOOL)isExpanded; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ResearchKitUI/Common/Step/Family History/Family History TableView Views/ORKFamilyHistoryTableHeaderView.m b/ResearchKitUI/Common/Step/Family History/Family History TableView Views/ORKFamilyHistoryTableHeaderView.m new file mode 100644 index 0000000000..9a1ab89acb --- /dev/null +++ b/ResearchKitUI/Common/Step/Family History/Family History TableView Views/ORKFamilyHistoryTableHeaderView.m @@ -0,0 +1,170 @@ +/* + Copyright (c) 2023, 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 "ORKFamilyHistoryTableHeaderView.h" + +static const CGFloat HeaderViewLabelTopBottomPadding = 6.0; +static const CGFloat HeaderViewLeftRightLabelPadding = 11.0; +static const CGFloat HeaderViewCollapsedBottomPadding = 0.0; +static const CGFloat HeaderViewExpandedBottomPadding = 10.0; +static const CGFloat CellLeftRightPadding = 8.0; +static const CGFloat MaxDetailLabelFont = 40.0; + +@implementation ORKFamilyHistoryTableHeaderView { + NSString *_title; + UILabel *_titleLabel; + NSString *_detailText; + UILabel *_detailTextLabel; + + NSMutableArray *_viewConstraints; +} + +- (instancetype)initWithTitle:(NSString *)title detailText:(nullable NSString *)detailText { + self = [super init]; + + if (self) { + _title = [title copy]; + _detailText = [detailText copy]; + + self.backgroundColor = [UIColor clearColor]; + + [self setupSubviews]; + [self setupConstraints]; + } + return self; +} + +- (void)setFrame:(CGRect)frame { + frame.origin.x += CellLeftRightPadding; + frame.size.width -= 2 * CellLeftRightPadding; + + [super setFrame:frame]; +} + +- (void)setupSubviews { + if (_titleLabel != nil) { + [_titleLabel removeFromSuperview]; + _titleLabel = nil; + } + + if (_detailTextLabel != nil) { + [_detailTextLabel removeFromSuperview]; + _detailTextLabel = nil; + } + + _titleLabel = [[UILabel alloc] init]; + _titleLabel.numberOfLines = 0; + _titleLabel.lineBreakMode = NSLineBreakByWordWrapping; + _titleLabel.translatesAutoresizingMaskIntoConstraints = NO; + _titleLabel.text = _title; + + _titleLabel.textAlignment = NSTextAlignmentLeft; + _titleLabel.font = [self titleLabelFont]; + [self addSubview:_titleLabel]; + + if (_detailText != nil) { + _detailTextLabel = [[UILabel alloc] init]; + _detailTextLabel.numberOfLines = 0; + _detailTextLabel.lineBreakMode = NSLineBreakByWordWrapping; + _detailTextLabel.translatesAutoresizingMaskIntoConstraints = NO; + _detailTextLabel.text = _detailText; + _detailTextLabel.textAlignment = NSTextAlignmentLeft; + _detailTextLabel.font = [self detailTextLabelFont]; + [self addSubview:_detailTextLabel]; + } + [self updateViewColors]; +} + +- (void)updateViewColors { + if (@available(iOS 12.0, *)) { + _detailTextLabel.textColor = self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark ? [UIColor whiteColor] : [UIColor blackColor]; + _titleLabel.textColor = self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark ? [UIColor whiteColor] : [UIColor blackColor]; + + } else { + _detailTextLabel.textColor = [UIColor blackColor]; + _titleLabel.textColor = [UIColor blackColor]; + } +} + +- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection { + [super traitCollectionDidChange:previousTraitCollection]; + [self updateViewColors]; +} + +- (void)setupConstraints { + if (_viewConstraints != nil) { + [NSLayoutConstraint deactivateConstraints:_viewConstraints]; + } + + _viewConstraints = [NSMutableArray new]; + + // titleLabel constraints + [_viewConstraints addObject:[_titleLabel.topAnchor constraintEqualToAnchor:self.topAnchor constant:HeaderViewLabelTopBottomPadding]]; + [_viewConstraints addObject:[_titleLabel.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:HeaderViewLeftRightLabelPadding]]; + [_viewConstraints addObject:[_titleLabel.trailingAnchor constraintEqualToAnchor:self.trailingAnchor constant:-HeaderViewLeftRightLabelPadding]]; + + UIView *bottomElementToConstraintViewTo; + + // detailLabel constraints if detailText was provided + if (_detailText != nil) { + [_viewConstraints addObject:[_detailTextLabel.topAnchor constraintEqualToAnchor:_titleLabel.bottomAnchor constant:HeaderViewLabelTopBottomPadding]]; + [_viewConstraints addObject:[_detailTextLabel.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:HeaderViewLeftRightLabelPadding]]; + [_viewConstraints addObject:[_detailTextLabel.trailingAnchor constraintEqualToAnchor:self.trailingAnchor constant:-HeaderViewLeftRightLabelPadding]]; + bottomElementToConstraintViewTo = _detailTextLabel; + } else { + bottomElementToConstraintViewTo = _titleLabel; + } + + // ORKFamilyHistoryTableHeaderView bottom constraint + [_viewConstraints addObject:[self.bottomAnchor constraintEqualToAnchor:bottomElementToConstraintViewTo.bottomAnchor constant:HeaderViewCollapsedBottomPadding]]; + + [NSLayoutConstraint activateConstraints:_viewConstraints]; +} + +- (void)setExpanded:(BOOL)isExpanded { + NSLayoutConstraint *bottomConstraint = [_viewConstraints lastObject]; + bottomConstraint.constant = isExpanded ? HeaderViewExpandedBottomPadding : HeaderViewCollapsedBottomPadding; + [self setNeedsUpdateConstraints]; +} + +- (UIFont *)titleLabelFont { + UIFontDescriptor *descriptor = [UIFontDescriptor preferredFontDescriptorWithTextStyle:UIFontTextStyleBody]; + UIFontDescriptor *fontDescriptor = [descriptor fontDescriptorWithSymbolicTraits:(UIFontDescriptorTraitBold)]; + return [UIFont fontWithDescriptor:fontDescriptor size:[[fontDescriptor objectForKey: UIFontDescriptorSizeAttribute] doubleValue]]; +} + +- (UIFont *)detailTextLabelFont { + UIFontDescriptor *descriptor = [UIFontDescriptor preferredFontDescriptorWithTextStyle:UIFontTextStyleSubheadline]; + double fontSize = [[descriptor objectForKey: UIFontDescriptorSizeAttribute] doubleValue]; + double suggestedFontSize = MIN(fontSize, MaxDetailLabelFont); + return [UIFont fontWithDescriptor:descriptor size: suggestedFontSize]; +} + +@end diff --git a/ResearchKitUI/Common/Step/Family History/ORKFamilyHistoryStepViewController.h b/ResearchKitUI/Common/Step/Family History/ORKFamilyHistoryStepViewController.h new file mode 100644 index 0000000000..c1cfd0d1bb --- /dev/null +++ b/ResearchKitUI/Common/Step/Family History/ORKFamilyHistoryStepViewController.h @@ -0,0 +1,41 @@ +/* + Copyright (c) 2023, 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 + + +NS_ASSUME_NONNULL_BEGIN + +ORK_CLASS_AVAILABLE +@interface ORKFamilyHistoryStepViewController : ORKStepViewController + +@end + +NS_ASSUME_NONNULL_END diff --git a/ResearchKitUI/Common/Step/Family History/ORKFamilyHistoryStepViewController.m b/ResearchKitUI/Common/Step/Family History/ORKFamilyHistoryStepViewController.m new file mode 100644 index 0000000000..ba16c4de23 --- /dev/null +++ b/ResearchKitUI/Common/Step/Family History/ORKFamilyHistoryStepViewController.m @@ -0,0 +1,802 @@ +/* + Copyright (c) 2023, 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 "ORKFamilyHistoryStepViewController.h" + +#import "ORKAccessibilityFunctions.h" +#import "ORKFamilyHistoryRelatedPersonCell.h" +#import "ORKFamilyHistoryStepViewController_Private.h" +#import "ORKFamilyHistoryTableFooterView.h" +#import "ORKFamilyHistoryTableHeaderView.h" +#import "ORKLearnMoreStepViewController.h" +#import "ORKNavigationContainerView_Internal.h" +#import "ORKReviewIncompleteCell.h" +#import "ORKStepContainerView.h" +#import "ORKStepContentView.h" +#import "ORKStepHeaderView_Internal.h" +#import "ORKStepViewController_Internal.h" +#import "ORKTableContainerView.h" +#import "ORKTaskViewController_Internal.h" + +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + + +@class ORKTaskViewController; + + +NSString * const ORKFamilyHistoryRelatedPersonCellIdentifier = @"ORKFamilyHistoryRelatedPersonCellIdentifier"; + +NSString * const ORKHealthConditionIDontKnowChoiceValue = @"do not know"; +NSString * const ORKHealthConditionNoneOfTheAboveChoiceValue = @"none of the above"; +NSString * const ORKHealthConditionPreferNotToAnswerChoiceValue = @"prefer not to answer"; + + +@interface ORKFamilyHistoryStepViewController () + +@property (nonatomic, strong) ORKTableContainerView *tableContainer; +@property (nonatomic, strong) UITableView *tableView; +@property (nonatomic, strong) ORKStepContentView *headerView; + +@end + + +@implementation ORKFamilyHistoryStepViewController { + NSArray *_constraints; + + NSArray *_relativeGroups; + NSArray *_relativeGroupOrderedTasks; + + NSMutableDictionary *> *_relatedPersons; + NSMutableArray *_displayedConditions; + NSArray *_conditionIdentifiersFromLastSession; + + NSMutableDictionary *_conditionsTextAndValues; + + NSArray *_conditionsWithinCurrentTask; + + BOOL _editingPreviousTask; + ORKRelatedPerson *_relativeForPresentedTask; +} + + +- (instancetype)ORKFamilyHistoryStepViewController_initWithResult:(ORKResult *)result { + ORKStepResult *stepResult = (ORKStepResult *)result; + if (stepResult && stepResult.results.count > 0) { + ORKFamilyHistoryResult *familyHistoryResult = (ORKFamilyHistoryResult *)stepResult.firstResult; + + if (familyHistoryResult) { + _relatedPersons = [NSMutableDictionary new]; + for (ORKRelatedPerson *relatedPerson in familyHistoryResult.relatedPersons) { + [self saveRelatedPerson:[relatedPerson copy]]; + } + + _conditionIdentifiersFromLastSession = [familyHistoryResult.displayedConditions copy]; + } + + } + + return self; +} + +- (instancetype)initWithStep:(ORKStep *)step result:(ORKResult *)result { + self = [super initWithStep:step]; + return [self ORKFamilyHistoryStepViewController_initWithResult:result]; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + [self stepDidChange]; + + _relatedPersons = _relatedPersons ? : [NSMutableDictionary new]; + _displayedConditions = [NSMutableArray new]; + _conditionsTextAndValues = [NSMutableDictionary new]; + + _relativeGroups = [[self familyHistoryStep].relativeGroups copy]; + + [self configureOrderedTasks]; + + [_tableView reloadData]; +} + +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear: animated]; + [_tableContainer setNeedsLayout]; +} + +- (void)stepDidChange { + [super stepDidChange]; + + [_tableContainer removeFromSuperview]; + _tableContainer = nil; + + if (self.isViewLoaded && self.step) { + _tableContainer = [[ORKTableContainerView alloc] initWithStyle:UITableViewStyleGrouped pinNavigationContainer:NO]; + _tableContainer.tableContainerDelegate = self; + [self.view addSubview:_tableContainer]; + _tableContainer.tapOffView = self.view; + + [self setupViews]; + } +} + +- (void)setupViews { + [self setupTableView]; + [self setupHeaderView]; + [self setupFooterViewIfNeeded]; + [self updateViewColors]; + + [self setupConstraints]; + [_tableContainer setNeedsLayout]; +} + +- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection { + [super traitCollectionDidChange:previousTraitCollection]; + + [self setupViews]; + [self updateViewColors]; +} + +- (void)updateNavBarBackgroundColor:(UIColor *)color { + UINavigationBarAppearance *appearance = [UINavigationBarAppearance new]; + [appearance configureWithOpaqueBackground]; + appearance.backgroundColor = color; + appearance.shadowImage = [UIImage new]; + appearance.shadowColor = [UIColor clearColor]; + + self.navigationController.navigationBar.scrollEdgeAppearance = appearance; + self.navigationController.navigationBar.compactAppearance = appearance; + self.navigationController.navigationBar.standardAppearance = appearance; + + if (@available(iOS 15.0, *)) { + self.navigationController.navigationBar.compactScrollEdgeAppearance = appearance; + } +} + +- (void)setupConstraints { + if (_constraints) { + [NSLayoutConstraint deactivateConstraints:_constraints]; + } + _tableContainer.translatesAutoresizingMaskIntoConstraints = NO; + _constraints = nil; + + + _constraints = @[ + [NSLayoutConstraint constraintWithItem:_tableContainer + attribute:NSLayoutAttributeTop + relatedBy:NSLayoutRelationEqual + toItem:self.view + attribute:NSLayoutAttributeTop + multiplier:1.0 + constant:0.0], + [NSLayoutConstraint constraintWithItem:_tableContainer + attribute:NSLayoutAttributeLeft + relatedBy:NSLayoutRelationEqual + toItem:self.view + attribute:NSLayoutAttributeLeft + multiplier:1.0 + constant:0.0], + [NSLayoutConstraint constraintWithItem:_tableContainer + attribute:NSLayoutAttributeRight + relatedBy:NSLayoutRelationEqual + toItem:self.view + attribute:NSLayoutAttributeRight + multiplier:1.0 + constant:0.0], + [NSLayoutConstraint constraintWithItem:_tableContainer + attribute:NSLayoutAttributeBottom + relatedBy:NSLayoutRelationEqual + toItem:self.view + attribute:NSLayoutAttributeBottom + multiplier:1.0 + constant:0.0] + ]; + [NSLayoutConstraint activateConstraints:_constraints]; + +} + +- (void)setupTableView { + _tableView = _tableContainer.tableView; + [_tableView registerClass:[ORKFamilyHistoryRelatedPersonCell class] forCellReuseIdentifier:ORKFamilyHistoryRelatedPersonCellIdentifier]; + _tableView.separatorColor = [UIColor clearColor]; + _tableView.delegate = self; + _tableView.dataSource = self; + _tableView.clipsToBounds = YES; + _tableView.rowHeight = UITableViewAutomaticDimension; + _tableView.sectionHeaderHeight = UITableViewAutomaticDimension; + _tableView.estimatedRowHeight = ORKGetMetricForWindow(ORKScreenMetricTableCellDefaultHeight, self.view.window); + _tableView.estimatedSectionHeaderHeight = 30.0; +} + +- (void)setupHeaderView { + _headerView = _tableContainer.stepContentView; + _headerView.stepTopContentImage = self.step.image; + _headerView.titleIconImage = self.step.iconImage; + _headerView.stepTitle = self.step.title; + _headerView.stepText = self.step.text; + _headerView.stepDetailText = self.step.detailText; + _headerView.stepHeaderTextAlignment = self.step.headerTextAlignment; + _headerView.bodyItems = self.step.bodyItems; + _tableContainer.stepTopContentImageContentMode = self.step.imageContentMode; +} + +- (ORKFamilyHistoryStep *)familyHistoryStep { + ORKFamilyHistoryStep *step = ORKDynamicCast(self.step, ORKFamilyHistoryStep); + + if (step == nil) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"the ORKFamilyHistoryStepViewController must be presented with a ORKFamilyHistoryStep" userInfo:nil]; + } + + return step; +} + +- (void)configureOrderedTasks { + NSMutableArray *relativeGroupOrderedTasks = [NSMutableArray new]; + + ORKFamilyHistoryStep *step = [self familyHistoryStep]; + + for (ORKRelativeGroup *relativeGroup in step.relativeGroups) { + NSMutableArray *steps = [NSMutableArray array]; + + // add formSteps from ORKRelativeGroup to steps array + + for (ORKFormStep *formStep in relativeGroup.formSteps) { + [steps addObject:[formStep copy]]; + } + + // configure and add health condition formStep to steps array + + NSMutableArray *formItems = [NSMutableArray new]; + + ORKTextChoiceAnswerFormat *textChoiceAnswerFormat = [self makeConditionsTextChoiceAnswerFormat:[step.conditionStepConfiguration.conditions copy]]; + ORKFormItem *healthConditionsFormItem = [[ORKFormItem alloc] initWithIdentifier:step.conditionStepConfiguration.conditionsFormItemIdentifier + text:ORKLocalizedString(@"FAMILY_HISTORY_CONDITIONS_FORM_ITEM_TEXT", @"") + answerFormat:textChoiceAnswerFormat]; + + + healthConditionsFormItem.showsProgress = YES; + + [formItems addObject:healthConditionsFormItem]; + [formItems addObjectsFromArray:step.conditionStepConfiguration.formItems]; + + ORKFormStep *conditionFormStep = [[ORKFormStep alloc] initWithIdentifier:step.conditionStepConfiguration.stepIdentifier]; + conditionFormStep.title = ORKLocalizedString(@"FAMILY_HISTORY_CONDITIONS_STEP_TITLE", @""); + conditionFormStep.detailText = ORKLocalizedString(@"FAMILY_HISTORY_CONDITIONS_STEP_DESCRIPTION_TEMP", @""); + conditionFormStep.optional = NO; + conditionFormStep.formItems = [formItems copy]; + + [steps addObject:conditionFormStep]; + + ORKNavigableOrderedTask *orderedTask = [[ORKNavigableOrderedTask alloc] initWithIdentifier:relativeGroup.identifier steps:steps]; + [relativeGroupOrderedTasks addObject:orderedTask]; + } + + _relativeGroupOrderedTasks = [relativeGroupOrderedTasks copy]; +} + +- (ORKTextChoiceAnswerFormat *)makeConditionsTextChoiceAnswerFormat:(NSArray *)healthConditions { + NSMutableArray *conditionsWithinCurrentTask = _conditionsWithinCurrentTask ? [_conditionsWithinCurrentTask mutableCopy] : [NSMutableArray new]; + + NSMutableArray *textChoices = [NSMutableArray new]; + for (ORKHealthCondition *healthCondition in healthConditions) { + + if (![conditionsWithinCurrentTask containsObject:healthCondition.identifier]) { + [conditionsWithinCurrentTask addObject:healthCondition.identifier]; + } + + ORKTextChoice *textChoice = [[ORKTextChoice alloc] initWithText:healthCondition.displayName + detailText:nil + value:healthCondition.value + exclusive:NO]; + + [textChoices addObject:textChoice]; + + _conditionsTextAndValues[(NSString *)healthCondition.value] = healthCondition.displayName; + } + + _conditionsWithinCurrentTask = [conditionsWithinCurrentTask copy]; + + ORKTextChoice *noneOfTheAboveTextChoice = [[ORKTextChoice alloc] initWithText:ORKLocalizedString(@"FAMILY_HISTORY_NONE_OF_THE_ABOVE", @"") + detailText:nil + value:ORKHealthConditionNoneOfTheAboveChoiceValue + exclusive:YES]; + + ORKTextChoice *idkTextChoice = [[ORKTextChoice alloc] initWithText:ORKLocalizedString(@"FAMILY_HISTORY_I_DONT_KNOW", @"") + detailText:nil + value:ORKHealthConditionIDontKnowChoiceValue + exclusive:YES]; + + ORKTextChoice *preferNotToAnswerTextChoice = [[ORKTextChoice alloc] initWithText:ORKLocalizedString(@"FAMILY_HISTORY_PREFER_NOT_TO_ANSWER", @"") + detailText:nil + value:ORKHealthConditionPreferNotToAnswerChoiceValue + exclusive:YES]; + + [textChoices addObject:noneOfTheAboveTextChoice]; + [textChoices addObject:idkTextChoice]; + [textChoices addObject:preferNotToAnswerTextChoice]; + + _conditionsTextAndValues[(NSString *)noneOfTheAboveTextChoice.value] = noneOfTheAboveTextChoice.text; + _conditionsTextAndValues[(NSString *)idkTextChoice.value] = idkTextChoice.text; + _conditionsTextAndValues[(NSString *)preferNotToAnswerTextChoice.value] = preferNotToAnswerTextChoice.text; + + ORKTextChoiceAnswerFormat *textChoiceAnswerFormat = [[ORKTextChoiceAnswerFormat alloc] initWithStyle:ORKChoiceAnswerStyleMultipleChoice + textChoices:textChoices]; + + return textChoiceAnswerFormat; +} + +- (void)presentNewOrderedTaskForRelativeGroup:(ORKRelativeGroup *)relativeGroup { + ORKNavigableOrderedTask *taskToPresent = [self taskForRelativeGroup:relativeGroup]; + + ORKTaskViewController *taskViewController = [[ORKTaskViewController alloc] initWithTask:taskToPresent taskRunUUID:nil]; + taskViewController.modalPresentationStyle = UIModalPresentationAutomatic; + taskViewController.delegate = self; + + [self presentViewController:taskViewController animated:YES completion:nil]; +} + +- (ORKNavigableOrderedTask *)taskForRelativeGroup:(ORKRelativeGroup *)relativeGroup { + ORKNavigableOrderedTask *task; + + for (ORKNavigableOrderedTask *orderedTask in _relativeGroupOrderedTasks) { + if ([orderedTask.identifier isEqual:relativeGroup.identifier]) { + task = orderedTask; + break; + } + } + + if (task == nil) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"An orderedTask was not found for relative group `%@`", relativeGroup.name] userInfo:nil]; + } + + return [task copy]; +} + +- (ORKRelativeGroup *)relativeGroupForRelatedPerson:(ORKRelatedPerson *)relatedPerson { + ORKRelativeGroup *relativeGroup; + + for (ORKRelativeGroup *group in _relativeGroups) { + if ([group.identifier isEqual:relatedPerson.groupIdentifier]) { + relativeGroup = group; + break; + } + } + + if (relativeGroup == nil) { + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"An relative group was not found for related person `%@`", relatedPerson.identifier] userInfo:nil]; + } + + return [relativeGroup copy]; +} + +- (ORKRelatedPerson *)relatedPersonAtIndexPath:(NSIndexPath *)indexPath { + ORKRelativeGroup *relativeGroup = _relativeGroups[indexPath.section]; + return _relatedPersons[relativeGroup.identifier][indexPath.row]; +} + +- (void)saveRelatedPerson:(ORKRelatedPerson *)relatedPerson { + // check if array for relativeGroup is initialized + if (!_relatedPersons[relatedPerson.groupIdentifier]) { + _relatedPersons[relatedPerson.groupIdentifier] = [NSMutableArray new]; + } + + [_relatedPersons[relatedPerson.groupIdentifier] addObject:relatedPerson]; + +} + + + +- (BOOL)didReachMaxForRelativeGroup:(ORKRelativeGroup *)relativeGroup { + return _relatedPersons[relativeGroup.identifier].count >= relativeGroup.maxAllowed; +} + +- (NSDictionary *> *)getDetailInfoTextAndValuesForRelativeGroup:(ORKRelativeGroup *)relativeGroup { + NSMutableDictionary *> *detailInfoTextAndValues = [NSMutableDictionary new]; + + // parse all formSteps of the relativeGroup and check if any of its formItems are a choice type. If yes, we'll need to grab the text values from the textChoices for presentation in the tableView as opposed to presenting the value of the formItem + for (ORKFormStep *formStep in relativeGroup.formSteps) { + + for (ORKFormItem *formItem in formStep.formItems) { + + for (NSString *identifier in relativeGroup.detailTextIdentifiers) { + if ([identifier isEqual:formItem.identifier]) { + + detailInfoTextAndValues[identifier] = [NSMutableDictionary new]; + + // check if formItem.answerFormat is of type ORKTextChoiceAnswerFormat, ORKTextScaleAnswerFormat, or ORKValuePickerAnswerFormat + NSArray *textChoices = [NSArray new]; + + if ([formItem.answerFormat isKindOfClass:[ORKTextChoiceAnswerFormat class]]) { + ORKTextChoiceAnswerFormat *textChoiceAnswerFormat = (ORKTextChoiceAnswerFormat *)formItem.answerFormat; + textChoices = textChoiceAnswerFormat.textChoices; + } else if ([formItem.answerFormat isKindOfClass:[ORKTextScaleAnswerFormat class]]) { + ORKTextScaleAnswerFormat *textScaleAnswerFormat = (ORKTextScaleAnswerFormat *)formItem.answerFormat; + textChoices = textScaleAnswerFormat.textChoices; + } else if ([formItem.answerFormat isKindOfClass:[ORKValuePickerAnswerFormat class]]) { + ORKValuePickerAnswerFormat *valuePickerAnswerFormat = (ORKValuePickerAnswerFormat *)formItem.answerFormat; + textChoices = valuePickerAnswerFormat.textChoices; + } + + for (ORKTextChoice *textChoice in textChoices) { + if ([textChoice.value isKindOfClass:[NSString class]]) { + NSString *stringValue = (NSString *)textChoice.value; + detailInfoTextAndValues[identifier][stringValue] = textChoice.text; + } + } + } + } + } + } + + return [detailInfoTextAndValues copy]; +} + +- (NSArray *)flattenRelatedPersonArrays { + NSMutableArray *relatedPersons = [NSMutableArray new]; + + for (NSString *key in _relatedPersons) { + [relatedPersons addObjectsFromArray:_relatedPersons[key]]; + } + + return [relatedPersons copy]; +} + +- (void)notifyDelegateOnResultChange { + [super notifyDelegateOnResultChange]; + + if (self.hasNextStep == NO) { + self.continueButtonItem = self.internalDoneButtonItem; + } else { + self.continueButtonItem = self.internalContinueButtonItem; + } + + self.skipButtonItem = self.internalSkipButtonItem; +} + +- (ORKStepResult *)result { + ORKStepResult *stepResult = [super result]; + + NSMutableArray *results = [NSMutableArray arrayWithArray:stepResult.results]; + ORKFamilyHistoryResult *familyHistoryResult = [[ORKFamilyHistoryResult alloc] initWithIdentifier:[self step].identifier]; + familyHistoryResult.startDate = stepResult.startDate; + familyHistoryResult.endDate = stepResult.endDate; + familyHistoryResult.relatedPersons = [self flattenRelatedPersonArrays]; + familyHistoryResult.displayedConditions = [_displayedConditions copy]; + [results addObject:familyHistoryResult]; + + stepResult.results = [results copy]; + + return stepResult; +} + +- (void)resultUpdated { + // For subclasses +} + +- (nonnull UITableViewCell *)currentFirstResponderCellForTableContainerView:(nonnull ORKTableContainerView *)tableContainerView { + return [UITableViewCell new]; +} + +#pragma mark ORKTaskViewControllerDelegate + +- (void)taskViewController:(ORKTaskViewController *)taskViewController didFinishWithReason:(ORKTaskFinishReason)reason error:(NSError *)error { + [self dismissViewControllerAnimated:YES completion:^{ + switch (reason) { + case ORKTaskFinishReasonFailed: + case ORKTaskFinishReasonDiscarded: + break; + case ORKTaskFinishReasonSaved: + case ORKTaskFinishReasonCompleted: + case ORKTaskFinishReasonEarlyTermination: + [self handleRelatedPersonTaskResult:taskViewController.result taskIdentifier:taskViewController.task.identifier]; + [self updateDisplayedConditionsFromTaskResult:taskViewController.result]; + break; + } + + self->_editingPreviousTask = NO; + self->_relativeForPresentedTask = nil; + }]; +} + +- (void)taskViewController:(ORKTaskViewController *)taskViewController learnMoreButtonPressedWithStep:(ORKLearnMoreInstructionStep *)learnMoreStep forStepViewController:(ORKStepViewController *)stepViewController { + ORKLearnMoreStepViewController *learnMoreStepViewController = [[ORKLearnMoreStepViewController alloc] initWithStep:learnMoreStep result:nil]; + [stepViewController presentViewController:[[UINavigationController alloc] initWithRootViewController:learnMoreStepViewController] animated:YES completion:nil]; +} + +#pragma mark ORKFamilyHistoryRelatedPersonCellDelegate + +- (void)familyHistoryRelatedPersonCell:(ORKFamilyHistoryRelatedPersonCell *)relatedPersonCell tappedOption:(ORKFamilyHistoryTooltipOption)option { + NSIndexPath *indexPath = [_tableView indexPathForCell:relatedPersonCell]; + ORKRelatedPerson *currentRelatedPerson = [self relatedPersonAtIndexPath:indexPath]; + + if (currentRelatedPerson) { + switch (option) { + case ORKFamilyHistoryTooltipOptionEdit: { + // edit flow for ORKRelatedPerson + ORKRelativeGroup *relativeGroup = [self relativeGroupForRelatedPerson:currentRelatedPerson]; + ORKNavigableOrderedTask *relatedPersonTask = [self taskForRelativeGroup:relativeGroup]; + + _editingPreviousTask = YES; + _relativeForPresentedTask = [currentRelatedPerson copy]; + + ORKTaskViewController *taskVC = [[ORKTaskViewController alloc] initWithTask:relatedPersonTask + ongoingResult:currentRelatedPerson.taskResult + restoreAtFirstStep:YES + defaultResultSource:nil + delegate:self]; + + + [self presentViewController:taskVC animated:YES completion:nil]; + break; + } + + case ORKFamilyHistoryTooltipOptionDelete: { + // delete flow for ORKRelatedPerson + UIAlertController *deleteAlert = [UIAlertController alertControllerWithTitle:ORKLocalizedString(@"FAMILY_HISTORY_DELETE_ENTRY_TITLE", @"") + message:nil + preferredStyle:UIAlertControllerStyleActionSheet]; + + UIAlertAction* unfollowAction = [UIAlertAction actionWithTitle:ORKLocalizedString(@"FAMILY_HISTORY_DELETE_ENTRY", @"") + style:UIAlertActionStyleDestructive + handler:^(UIAlertAction * action) { + [self->_relatedPersons[currentRelatedPerson.groupIdentifier] removeObject:currentRelatedPerson]; + NSIndexSet *section = [NSIndexSet indexSetWithIndex:indexPath.section]; + [self->_tableView reloadSections:section withRowAnimation:UITableViewRowAnimationAutomatic]; + [self resultUpdated]; + }]; + + UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:ORKLocalizedString(@"FAMILY_HISTORY_CANCEL", @"") + style:UIAlertActionStyleCancel + handler:nil]; + + [deleteAlert addAction:unfollowAction]; + [deleteAlert addAction:cancelAction]; + [self presentViewController:deleteAlert animated:YES completion:nil]; + break; + } + } + } +} + +#pragma mark ORKFamilyHistoryTableFooterViewDelegate + +- (void)ORKFamilyHistoryTableFooterView:(ORKFamilyHistoryTableFooterView *)footerView didSelectFooterForRelativeGroup:(NSString *)groupIdentifier { + for (ORKRelativeGroup *relativeGroup in _relativeGroups) { + if ([relativeGroup.identifier isEqual:groupIdentifier]) { + if (![self didReachMaxForRelativeGroup:[relativeGroup copy]]) { + [self presentNewOrderedTaskForRelativeGroup:[relativeGroup copy]]; + } + } + } +} + +@end + +@implementation ORKFamilyHistoryStepViewController (ORKFamilyHistoryReviewSupport) + +- (void)updateViewColors { + UIColor *updateColor = self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark ? [UIColor systemGray6Color] : [UIColor systemGroupedBackgroundColor];; + self.view.backgroundColor = updateColor; + self.tableView.backgroundColor = updateColor; + [self updateNavBarBackgroundColor: updateColor]; +} + +- (void)setupFooterViewIfNeeded { + _navigationFooterView = _tableContainer.navigationFooterView; + _navigationFooterView.skipButtonItem = self.skipButtonItem; + _navigationFooterView.continueEnabled = YES; + _navigationFooterView.continueButtonItem = self.continueButtonItem; + _navigationFooterView.optional = self.step.optional; + + [_navigationFooterView removeStyling]; +} + +- (void)handleRelatedPersonTaskResult:(ORKTaskResult *)taskResult taskIdentifier:(NSString *)identifier { + ORKFamilyHistoryStep *familyHistoryStep = [self familyHistoryStep]; + + // If the user is editing a previous task, just update the result of the relatedPerson + if (_editingPreviousTask && _relativeForPresentedTask) { + _relativeForPresentedTask.taskResult = taskResult; + + NSInteger index = 0; + + for (ORKRelatedPerson *relatedPerson in _relatedPersons[identifier]) { + if ([relatedPerson.identifier isEqual:_relativeForPresentedTask.identifier]) { + break; + } + + index += 1; + } + + _relatedPersons[identifier][index] = [_relativeForPresentedTask copy]; + + + [_tableView reloadData]; + } else { + + // create new relatedPerson object and attach taskResult + for (ORKRelativeGroup *relativeGroup in familyHistoryStep.relativeGroups) { + if ([relativeGroup.identifier isEqual:identifier]) { + ORKRelatedPerson *relatedPerson = [[ORKRelatedPerson alloc] initWithIdentifier:[NSUUID new].UUIDString + groupIdentifier:identifier + identifierForCellTitle:relativeGroup.identifierForCellTitle + taskResult:taskResult]; + + [self saveRelatedPerson:[relatedPerson copy]]; + [_tableView reloadData]; + break; + } + } + } + + [self resultUpdated]; + + [_tableContainer setNeedsLayout]; +} + +- (NSInteger)numberOfRowsForRelativeGroupInSection:(NSInteger)section { + ORKRelativeGroup *relativeGroup = _relativeGroups[section]; + return _relatedPersons[relativeGroup.identifier].count; +} + +- (void)updateDisplayedConditionsFromTaskResult:(ORKTaskResult *)taskResult { + ORKFamilyHistoryStep *step = [self familyHistoryStep]; + + ORKStepResult *stepResult = (ORKStepResult *)[taskResult resultForIdentifier:step.conditionStepConfiguration.stepIdentifier]; + + // if stepResult is nil, then choiceQuestionResult will also be nil here + ORKChoiceQuestionResult *choiceQuestionResult = (ORKChoiceQuestionResult *)[stepResult resultForIdentifier:step.conditionStepConfiguration.conditionsFormItemIdentifier]; + + // if choiceQuestionResult is nil, then choiceQuestionResult.choiceAnswers is nil + NSArray *conditionsIdentifiers = choiceQuestionResult.choiceAnswers != nil ? _conditionsWithinCurrentTask : [NSArray new]; + + for (NSString *conditionIdentifier in conditionsIdentifiers) { + if (![_displayedConditions containsObject:conditionIdentifier]) { + [_displayedConditions addObject:conditionIdentifier]; + } + } +} + + +#pragma mark UITableViewDataSource + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return _relativeGroups.count; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return [self numberOfRowsForRelativeGroupInSection:section]; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + + ORKRelativeGroup *relativeGroup = _relativeGroups[indexPath.section]; + + // present a related person cell + ORKFamilyHistoryRelatedPersonCell *cell = [tableView dequeueReusableCellWithIdentifier:ORKFamilyHistoryRelatedPersonCellIdentifier]; + [cell setSelectionStyle:UITableViewCellSelectionStyleNone]; + + ORKFamilyHistoryStep *familyHistoryStep = [self familyHistoryStep]; + + BOOL didReachMaxNumberOfRelatives = [self didReachMaxForRelativeGroup:relativeGroup]; + BOOL shouldAddExtraSpaceBelowCell = ([self numberOfRowsForRelativeGroupInSection:indexPath.section] == (indexPath.row + 1)) && !didReachMaxNumberOfRelatives; + ORKRelatedPerson *relatedPerson = [self relatedPersonAtIndexPath:indexPath]; + + + NSString *title = [relatedPerson getTitleValueWithIdentifier:relativeGroup.identifierForCellTitle]; + + cell.title = title != nil ? title : [NSString stringWithFormat:@"%@ %ld", relativeGroup.name, indexPath.row + 1]; + cell.relativeID = [relatedPerson.identifier copy]; + NSArray *detailValues = [relatedPerson getDetailListValuesWithIdentifiers:relativeGroup.detailTextIdentifiers + displayInfoKeyAndValues:[self getDetailInfoTextAndValuesForRelativeGroup:relativeGroup]]; + + NSArray *conditionValues = [relatedPerson getConditionsListWithStepIdentifier:familyHistoryStep.conditionStepConfiguration.stepIdentifier + formItemIdentifier:familyHistoryStep.conditionStepConfiguration.conditionsFormItemIdentifier + conditionsKeyValues:[_conditionsTextAndValues copy]]; + [cell configureWithDetailValues:detailValues conditionsValues:conditionValues isLastItemBeforeAddRelativeButton:shouldAddExtraSpaceBelowCell]; + cell.delegate = self; + + return cell; +} + +#pragma mark UITableViewDelegate + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + [tableView deselectRowAtIndexPath:indexPath animated:NO]; +} + +- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { + return UITableViewAutomaticDimension; +} + +- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { + ORKRelativeGroup *relativeGroup = _relativeGroups[section]; + + ORKFamilyHistoryTableHeaderView *headerView = (ORKFamilyHistoryTableHeaderView *)[tableView dequeueReusableHeaderFooterViewWithIdentifier:@(section).stringValue]; + + if (headerView == nil) { + headerView = [[ORKFamilyHistoryTableHeaderView alloc] initWithTitle:relativeGroup.sectionTitle detailText:relativeGroup.sectionDetailText]; + } + + BOOL isExpanded = _relatedPersons[relativeGroup.identifier].count > 0; + [headerView setExpanded:isExpanded]; + + return headerView; +} + +- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section { + ORKRelativeGroup *relativeGroup = _relativeGroups[section]; + + if ([self didReachMaxForRelativeGroup:relativeGroup]) { + return 0; + } + + return UITableViewAutomaticDimension; +} + +- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section { + ORKFamilyHistoryTableFooterView *footerView = (ORKFamilyHistoryTableFooterView *)[tableView dequeueReusableHeaderFooterViewWithIdentifier:@(section).stringValue]; + ORKRelativeGroup *relativeGroup = _relativeGroups[section]; + + if (footerView == nil) { + footerView = [[ORKFamilyHistoryTableFooterView alloc] initWithTitle:[NSString stringWithFormat:ORKLocalizedString(@"FAMILY_HISTORY_ADD", @"") ,relativeGroup.name] + relativeGroupIdentifier:[relativeGroup.identifier copy] + delegate:self]; + } + + BOOL isExpanded = _relatedPersons[relativeGroup.identifier].count > 0; + [footerView setExpanded:isExpanded]; + + return footerView; +} + +- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { + return UITableViewAutomaticDimension; +} + + +@end diff --git a/ResearchKitUI/Common/Step/Family History/ORKFamilyHistoryStepViewController_Private.h b/ResearchKitUI/Common/Step/Family History/ORKFamilyHistoryStepViewController_Private.h new file mode 100644 index 0000000000..9cecd0cc88 --- /dev/null +++ b/ResearchKitUI/Common/Step/Family History/ORKFamilyHistoryStepViewController_Private.h @@ -0,0 +1,44 @@ +/* + Copyright (c) 2023, 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 + +NS_ASSUME_NONNULL_BEGIN +@interface ORKFamilyHistoryStepViewController (ORKFamilyHistoryReviewSupport) + +- (void)setupFooterViewIfNeeded; +- (void)handleRelatedPersonTaskResult:(ORKTaskResult *)taskResult taskIdentifier:(NSString *)identifier; +- (void)updateDisplayedConditionsFromTaskResult:(ORKTaskResult *)taskResult; +- (NSInteger)numberOfRowsForRelativeGroupInSection:(NSInteger)section; +- (void)updateViewColors; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ResearchKitUI/Common/Step/Form Step/ORKAnswerFormat+FormStepViewControllerAdditions.m b/ResearchKitUI/Common/Step/Form Step/ORKAnswerFormat+FormStepViewControllerAdditions.m index 8f1db37274..b6bf4cf756 100644 --- a/ResearchKitUI/Common/Step/Form Step/ORKAnswerFormat+FormStepViewControllerAdditions.m +++ b/ResearchKitUI/Common/Step/Form Step/ORKAnswerFormat+FormStepViewControllerAdditions.m @@ -52,6 +52,8 @@ - (nullable Class)formStepViewControllerCellClass { matchesType = matchesType || (type == ORKQuestionTypeMultiplePicker); matchesType = matchesType || (type == ORKQuestionTypeHeight); matchesType = matchesType || (type == ORKQuestionTypeWeight); + matchesType = matchesType || (type == ORKQuestionTypeAge); + matchesType = matchesType || (type == ORKQuestionTypeYear); result = matchesType ? [ORKFormItemPickerCell class] : result; } @@ -68,7 +70,7 @@ - (nullable Class)formStepViewControllerCellClass { result = matchesType ? [ORKFormItemScaleCell class] : result; } -#if !TARGET_OS_VISION +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && !TARGET_OS_VISION if (result == nil) { BOOL matchesType = NO; matchesType = matchesType || (type == ORKQuestionTypeLocation); diff --git a/ResearchKitUI/Common/Step/Form Step/ORKFormStepViewController.m b/ResearchKitUI/Common/Step/Form Step/ORKFormStepViewController.m index 829f1f3731..cabc4cce75 100644 --- a/ResearchKitUI/Common/Step/Form Step/ORKFormStepViewController.m +++ b/ResearchKitUI/Common/Step/Form Step/ORKFormStepViewController.m @@ -33,6 +33,8 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE #import "ORKCaption1Label.h" #import "ORKChoiceViewCell_Internal.h" +#import "ORKChoiceViewCell+ORKColorChoice.h" +#import "ORKColorChoiceCellGroup.h" #import "ORKFormItemCell.h" #import "ORKFormSectionTitleLabel.h" #import "ORKStepHeaderView_Internal.h" @@ -219,6 +221,7 @@ - (instancetype)initWithSectionIndex:(NSUInteger)index; @property (nonatomic, strong) ORKTextChoiceCellGroup *textChoiceCellGroup; +@property (nonatomic, strong) ORKColorChoiceCellGroup *colorChoiceCellGroup; - (void)addFormItem:(ORKFormItem *)item; @@ -262,8 +265,23 @@ - (void)addFormItem:(ORKFormItem *)item { } else { + if ([[item impliedAnswerFormat] isKindOfClass:[ORKColorChoiceAnswerFormat class]]) { + _hasChoiceRows = YES; + ORKColorChoiceAnswerFormat *colorChoiceAnswerFormat = (ORKColorChoiceAnswerFormat *)[item impliedAnswerFormat]; + + _colorChoiceCellGroup = [[ORKColorChoiceCellGroup alloc] initWithColorChoiceAnswerFormat:colorChoiceAnswerFormat + answer:nil + beginningIndexPath:[NSIndexPath indexPathForRow:0 inSection:_index] + immediateNavigation:NO]; + + [colorChoiceAnswerFormat.colorChoices enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + ORKTableCellItem *cellItem = [[ORKTableCellItem alloc] initWithFormItem:item choiceIndex:idx]; + [(NSMutableArray *)self.items addObject:cellItem]; + }]; + + return; + } - ORKTableCellItem *cellItem = [[ORKTableCellItem alloc] initWithFormItem:item]; [(NSMutableArray *)self.items addObject:cellItem]; } @@ -406,7 +424,10 @@ @implementation ORKFormStepViewController { } - (instancetype)ORKFormStepViewController_initWithResult:(ORKResult *)result { +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION _defaultSource = [ORKAnswerDefaultSource sourceWithHealthStore:[HKHealthStore new]]; +#endif + if (result) { NSAssert([result isKindOfClass:[ORKStepResult class]], @"Expect a ORKStepResult instance"); @@ -460,6 +481,8 @@ - (void)viewDidLayoutSubviews { - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [self updateAnsweredSections]; + +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION NSMutableSet *types = [NSMutableSet set]; for (ORKFormItem *item in [self answerableFormItems]) { ORKAnswerFormat *format = [item answerFormat]; @@ -487,6 +510,7 @@ - (void)viewWillAppear:(BOOL)animated { if (!refreshDefaultsPending) { [self refreshDefaults]; } +#endif // Reset skipped flag - result can now be non-empty _skipped = NO; @@ -567,6 +591,8 @@ - (void)updateDefaults:(NSMutableDictionary *)defaults { } - (void)refreshDefaults { + // defaults only come from HealthKit + NSArray *formItems = [self allFormItems]; ORKAnswerDefaultSource *source = _defaultSource; ORKWeakTypeOf(self) weakSelf = self; @@ -592,9 +618,7 @@ - (void)refreshDefaults { ORKStrongTypeOf(weakSelf) strongSelf = weakSelf; [strongSelf updateDefaults:defaults]; }); - }); - } - (void)removeAnswerForIdentifier:(NSString *)identifier { @@ -859,7 +883,7 @@ - (void)buildDataSource:(UITableViewDiffableDataSource)coordinator { diff --git a/ResearchKitUI/Common/Step/ORKStep+ResearchKitUI.m b/ResearchKitUI/Common/Step/ORKStep+ResearchKitUI.m index 2671a36004..f38919b5f3 100644 --- a/ResearchKitUI/Common/Step/ORKStep+ResearchKitUI.m +++ b/ResearchKitUI/Common/Step/ORKStep+ResearchKitUI.m @@ -66,6 +66,14 @@ - (ORKStepViewController *)makeViewControllerWithResult:(ORKResult *)result { @end +@implementation ORKFamilyHistoryStep (ViewControllerProviding) + +- (ORKStepViewController *)makeViewControllerWithResult:(ORKResult *)result { + return [[ORKFamilyHistoryStepViewController alloc] initWithStep:self result:result]; +} + +@end + @implementation ORKFormStep (ViewControllerProviding) - (ORKStepViewController *)makeViewControllerWithResult:(ORKResult *)result { diff --git a/ResearchKitUI/Common/Step/ORKStepViewController.h b/ResearchKitUI/Common/Step/ORKStepViewController.h index d739432dee..2c91ec2fde 100644 --- a/ResearchKitUI/Common/Step/ORKStepViewController.h +++ b/ResearchKitUI/Common/Step/ORKStepViewController.h @@ -273,6 +273,14 @@ ORK_CLASS_AVAILABLE */ @property (nonatomic, assign) BOOL continueButtonEnabled; +/** + The enabled state of the skip button. + + The default state is enabled. Setting the skip button to disabled will add a visual treatment + to the button, and prevent it from being selectable. + */ +@property (nonatomic, assign) BOOL skipButtonEnabled; + /** A localized string that represents the title of the Learn More button. diff --git a/ResearchKitUI/Common/Step/ORKStepViewController.m b/ResearchKitUI/Common/Step/ORKStepViewController.m index ab652027c9..f38ff56be5 100644 --- a/ResearchKitUI/Common/Step/ORKStepViewController.m +++ b/ResearchKitUI/Common/Step/ORKStepViewController.m @@ -358,6 +358,10 @@ - (void)setContinueButtonEnabled:(BOOL)continueButtonEnabled { _navigationFooterView.continueEnabled = continueButtonEnabled; } +- (void)setSkipButtonEnabled:(BOOL)skipButtonEnabled { + _navigationFooterView.skipEnabled = skipButtonEnabled; +} + - (void)setNavigationDetailText:(NSString *)navigationDetailText { _navigationFooterView.navigationDetailText = navigationDetailText; } diff --git a/ResearchKitUI/Common/Step/Table Step/ORKTableStepViewController.m b/ResearchKitUI/Common/Step/Table Step/ORKTableStepViewController.m index a12a568c29..18cce54efb 100644 --- a/ResearchKitUI/Common/Step/Table Step/ORKTableStepViewController.m +++ b/ResearchKitUI/Common/Step/Table Step/ORKTableStepViewController.m @@ -42,6 +42,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE #import "ORKTableStep.h" #import "ORKStepContentView.h" +#import "ORKStepContentView_Private.h" #import "ORKBodyItem.h" #import "ORKHelpers_Internal.h" #import "ORKSkin.h" @@ -75,7 +76,6 @@ - (void)viewWillAppear:(BOOL)animated { if (_tableContainer) { [_tableContainer sizeHeaderToFit]; [_tableContainer resizeFooterToFitUsingMinHeight:NO]; - [_tableContainer layoutIfNeeded]; } if (_tableView) { @@ -85,6 +85,7 @@ - (void)viewWillAppear:(BOOL)animated { - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; + [_tableContainer layoutIfNeeded]; UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil); } @@ -182,6 +183,10 @@ - (void)stepDidChange { if (self.tableStepRef.pinNavigationContainer == NO) { [_navigationFooterView removeStyling]; } + + if ([self tableStepRef].bottomPadding) { + [_headerView setStepContentViewBottomConstraint:[self tableStepRef].bottomPadding.doubleValue]; + } } } diff --git a/ResearchKitUI/Common/Task/ORKTaskViewController.h b/ResearchKitUI/Common/Task/ORKTaskViewController.h index 63acf8150f..f8f7255968 100644 --- a/ResearchKitUI/Common/Task/ORKTaskViewController.h +++ b/ResearchKitUI/Common/Task/ORKTaskViewController.h @@ -379,7 +379,7 @@ ORK_CLASS_AVAILABLE - (instancetype)initWithTask:(id)task ongoingResult:(nullable ORKTaskResult *)ongoingResult defaultResultSource:(nullable id)defaultResultSource - delegate:(id)delegate NS_DESIGNATED_INITIALIZER; + delegate:(nullable id)delegate NS_DESIGNATED_INITIALIZER; /** Creates a new task view controller that starts the task at the step that has the specified step identifier. @@ -399,7 +399,7 @@ ORK_CLASS_AVAILABLE ongoingResult:(nullable ORKTaskResult *)ongoingResult restoreAtFirstStep:(BOOL)restoreAtFirstStep defaultResultSource:(nullable id)defaultResultSource - delegate:(id)delegate; + delegate:(nullable id)delegate; /** The delegate for the task view controller. @@ -575,6 +575,8 @@ ORK_CLASS_AVAILABLE */ @property (nonatomic, assign) BOOL discardable; +@property (nonatomic, assign) BOOL skipSaveResultsConfirmation; + @property (nonatomic) ORKTaskViewControllerReviewMode reviewMode; @property (nonatomic, nullable) ORKInstructionStep * reviewInstructionStep; diff --git a/ResearchKitUI/Common/Task/ORKTaskViewController.m b/ResearchKitUI/Common/Task/ORKTaskViewController.m index df7eac7f75..2b3566a7ac 100644 --- a/ResearchKitUI/Common/Task/ORKTaskViewController.m +++ b/ResearchKitUI/Common/Task/ORKTaskViewController.m @@ -30,7 +30,6 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE #import "ORKTaskViewController.h" -#import "ORKBorderedButton.h" #import "ORKInstructionStepViewController_Internal.h" #import "ORKFormStepViewController.h" #import "ORKReviewStepViewController_Internal.h" @@ -52,13 +51,18 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE #import "ORKHelpers_Internal.h" #import "ORKObserver.h" #import "ORKSkin.h" +#import "ORKBorderedButton.h" #import "ORKTaskReviewViewController.h" +@import AVFoundation; +@import CoreMotion; + + +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION #import #import -@import AVFoundation; -@import CoreMotion; + typedef void (^_ORKLocationAuthorizationRequestHandler)(BOOL success); @@ -70,8 +74,10 @@ - (instancetype)initWithHandler:(_ORKLocationAuthorizationRequestHandler)handler - (void)resume; @end +#endif +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION @implementation ORKLocationAuthorizationRequester { CLLocationManager *_manager; _ORKLocationAuthorizationRequestHandler _handler; @@ -135,7 +141,7 @@ - (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatu } @end - +#endif /// An interface for managing a task in a view. /// @@ -144,8 +150,6 @@ @interface ORKTaskViewController () *_requestedHealthTypesForRead; NSSet *_requestedHealthTypesForWrite; @@ -208,8 +212,8 @@ - (instancetype)commonInitWithTask:(id)task taskRunUUID:(NSUUID *)taskR self.showsProgressInNavigationBar = YES; self.discardable = NO; + self.skipSaveResultsConfirmation = NO; self.progressMode = ORKTaskViewControllerProgressModeQuestionsPerStep; - _saveable = NO; _managedResults = [NSMutableDictionary dictionary]; _managedStepIdentifiers = [NSMutableArray array]; @@ -220,7 +224,6 @@ - (instancetype)commonInitWithTask:(id)task taskRunUUID:(NSUUID *)taskR (void)[self taskRunUUID]; self.restorationClass = [ORKTaskViewController class]; - return self; } @@ -275,7 +278,8 @@ - (instancetype)initWithTask:(id)task for (ORKResult *stepResult in ongoingResult.results) { NSString *stepResultIdentifier = stepResult.identifier; if ([task stepWithIdentifier:stepResultIdentifier] == nil) { - @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"ongoingResults has results for identifiers not found within the task steps" userInfo:nil]; + ORK_Log_Error("ongoingResults has results for identifiers not found within the task steps, skipping adding result for step %@", stepResultIdentifier); + continue; } [_managedStepIdentifiers addObject:stepResultIdentifier]; _managedResults[stepResultIdentifier] = stepResult; @@ -343,16 +347,24 @@ - (void)requestHealthStoreAccessWithReadTypes:(NSSet *)readTypes writeTypes:(NSSet *)writeTypes handler:(void (^)(void))handler { NSParameterAssert(handler != nil); - if ((![HKHealthStore isHealthDataAvailable]) || (!readTypes && !writeTypes)) { + BOOL needsHealthKitAuthRequest = NO; + +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION + needsHealthKitAuthRequest = ([HKHealthStore isHealthDataAvailable]); + needsHealthKitAuthRequest = needsHealthKitAuthRequest && ((readTypes != nil) || (writeTypes != nil)); +#endif + + if (needsHealthKitAuthRequest == NO) { _requestedHealthTypesForRead = nil; _requestedHealthTypesForWrite = nil; - handler(); + dispatch_async(dispatch_get_main_queue(), handler); return; } _requestedHealthTypesForRead = readTypes; _requestedHealthTypesForWrite = writeTypes; +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION __block HKHealthStore *healthStore = [HKHealthStore new]; [healthStore requestAuthorizationToShareTypes:writeTypes readTypes:readTypes completion:^(BOOL success, NSError *error) { ORK_Log_Error("Health access: error=%@", error); @@ -361,6 +373,8 @@ - (void)requestHealthStoreAccessWithReadTypes:(NSSet *)readTypes // Clear self-ref. healthStore = nil; }]; +#endif + } #if TARGET_OS_IOS @@ -419,6 +433,7 @@ - (void)requestCameraAccessWithHandler:(void (^)(BOOL success))handler { }]; } +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION - (void)requestLocationAccessWithHandler:(void (^)(BOOL success))handler { NSParameterAssert(handler != nil); @@ -433,6 +448,7 @@ - (void)requestLocationAccessWithHandler:(void (^)(BOOL success))handler { [requester resume]; } +#endif - (ORKPermissionMask)desiredPermissions { ORKPermissionMask permissions = ORKPermissionNone; @@ -452,6 +468,7 @@ - (void)requestHealthAuthorizationWithCompletion:(void (^)(void))completion { return; } +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION NSSet *readTypes = nil; if ([self.task respondsToSelector:@selector(requestedHealthKitTypesForReading)]) { readTypes = [self.task requestedHealthKitTypesForReading]; @@ -461,19 +478,22 @@ - (void)requestHealthAuthorizationWithCompletion:(void (^)(void))completion { if ([self.task respondsToSelector:@selector(requestedHealthKitTypesForWriting)]) { writeTypes = [self.task requestedHealthKitTypesForWriting]; } - +#endif + ORKPermissionMask permissions = [self desiredPermissions]; dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ dispatch_async(dispatch_get_main_queue(), ^{ +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION ORK_Log_Debug("Requesting health access"); [self requestHealthStoreAccessWithReadTypes:readTypes writeTypes:writeTypes handler:^{ dispatch_semaphore_signal(semaphore); }]; +#endif }); dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); if (permissions & ORKPermissionCoreMotionAccelerometer) { @@ -507,6 +527,8 @@ - (void)requestHealthAuthorizationWithCompletion:(void (^)(void))completion { dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); } + +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION if (permissions & ORKPermissionCoreLocation) { dispatch_async(dispatch_get_main_queue(), ^{ ORK_Log_Debug("Requesting location access"); @@ -522,6 +544,7 @@ - (void)requestHealthAuthorizationWithCompletion:(void (^)(void))completion { dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); } +#endif if (permissions & ORKPermissionCamera) { dispatch_async(dispatch_get_main_queue(), ^{ ORK_Log_Debug("Requesting camera access"); @@ -667,7 +690,7 @@ - (void)viewWillAppear:(BOOL)animated { // Clear endDate if current TaskVC got presented again _dismissedDate = nil; - if ([self shouldDismissWithSwipe] == NO) { + if ([self isSafeToSkipConfirmation] == NO) { self.modalInPresentation = YES; } @@ -1165,6 +1188,28 @@ - (ORKStepViewController *)viewControllerForStep:(ORKStep *)step isPreviousViewC return stepViewController; } +- (nullable ORKResult *)getCurrentStepResult:(ORKStep *)step { + ORKResult *result = [self.result resultForIdentifier:step.identifier]; + if (result) { + return result; + } + ORKStepResult *previousResult = _managedResults[step.identifier]; + + // Check the default source first + BOOL alwaysCheckForDefaultResult = ([self.defaultResultSource respondsToSelector:@selector(alwaysCheckForDefaultResult)] && + [self.defaultResultSource alwaysCheckForDefaultResult]); + if ((previousResult == nil) || alwaysCheckForDefaultResult) { + result = [self.defaultResultSource stepResultForStepIdentifier:step.identifier]; + } + + // If nil, assign to the previous result (if available) otherwise create new instance + if (!result) { + result = previousResult ? : [[ORKStepResult alloc] initWithIdentifier:step.identifier]; + } + + return result; +} + - (ORKStepViewController *)viewControllerForStep:(ORKStep *)step { return [self viewControllerForStep:step isPreviousViewController:NO]; } @@ -1199,21 +1244,9 @@ - (void)didFinishWithReason:(ORKTaskFinishReason)reason error:(nullable NSError // no-op } -- (void)presentCancelOptions:(BOOL)saveable sender:(id)sender { - - if ([self.delegate respondsToSelector:@selector(taskViewControllerShouldConfirmCancel:)] && - ![self.delegate taskViewControllerShouldConfirmCancel:self]) { - dispatch_async(dispatch_get_main_queue(), ^{ - [self finishWithReason:ORKTaskFinishReasonDiscarded error:nil]; - }); - return; - } - - BOOL supportSaving = NO; - if ([self.delegate respondsToSelector:@selector(taskViewControllerSupportsSaveAndRestore:)]) { - supportSaving = [self.delegate taskViewControllerSupportsSaveAndRestore:self]; - } - +- (void)showRestorationStateAlertControllerWithSender:(id)sender + saveable:(BOOL)saveable + supportSaving:(BOOL)supportSaving { UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet]; @@ -1227,6 +1260,9 @@ - (void)presentCancelOptions:(BOOL)saveable sender:(id)sender { alert.popoverPresentationController.barButtonItem = sender; } + + NSString *discardTitle = saveable ? ORKLocalizedString(@"BUTTON_OPTION_DISCARD", nil) : ORKLocalizedString(@"BUTTON_OPTION_STOP_TASK", nil); + if (supportSaving && saveable) { [alert addAction:[UIAlertAction actionWithTitle:ORKLocalizedString(@"BUTTON_OPTION_SAVE", nil) style:UIAlertActionStyleDefault @@ -1237,57 +1273,88 @@ - (void)presentCancelOptions:(BOOL)saveable sender:(id)sender { }]]; } - NSString *discardTitle = saveable ? ORKLocalizedString(@"BUTTON_OPTION_DISCARD", nil) : ORKLocalizedString(@"BUTTON_OPTION_STOP_TASK", nil); - [alert addAction:[UIAlertAction actionWithTitle:discardTitle - style:UIAlertActionStyleDestructive - handler:^(UIAlertAction *action) { - dispatch_async(dispatch_get_main_queue(), ^{ - [self finishWithReason:ORKTaskFinishReasonDiscarded error:nil]; - }); - }]]; - + style:UIAlertActionStyleDestructive + handler:^(UIAlertAction *action) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self finishWithReason:ORKTaskFinishReasonDiscarded error:nil]; + }); + }]]; + [alert addAction:[UIAlertAction actionWithTitle:ORKLocalizedString(@"BUTTON_CANCEL", nil) style:UIAlertActionStyleCancel handler:nil]]; - [self presentViewController:alert animated:YES completion:nil]; } +- (void)presentCancelOptionsWithSender:(id)sender { + BOOL saveable = [self hasSaveableResults]; + if ([self.delegate respondsToSelector:@selector(taskViewControllerShouldConfirmCancel:)] && + ![self.delegate taskViewControllerShouldConfirmCancel:self]) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self finishWithReason:ORKTaskFinishReasonDiscarded error:nil]; + }); + return; + } + + BOOL supportSaving = NO; + if ([self.delegate respondsToSelector:@selector(taskViewControllerSupportsSaveAndRestore:)]) { + supportSaving = [self.delegate taskViewControllerSupportsSaveAndRestore:self]; + } + + if (_skipSaveResultsConfirmation && supportSaving && saveable) { + [self finishWithReason:ORKTaskFinishReasonSaved error:nil]; + } else { + [self showRestorationStateAlertControllerWithSender:self + saveable:saveable + supportSaving:supportSaving]; + } +} + - (IBAction)cancelAction:(UIBarButtonItem *)sender { if (self.discardable) { [self finishWithReason:ORKTaskFinishReasonDiscarded error:nil]; } else { - [self presentCancelOptions:_saveable sender:sender]; + [self presentCancelOptionsWithSender:sender]; } } -- (BOOL)shouldDismissWithSwipe { - // Should we also include visualConsentStep here? Others? - BOOL isCurrentInstructionStep = [self.currentStepViewController.step isKindOfClass:[ORKInstructionStep class]]; +/// Compute whether it's safe to discard results because there wouldn't be any user data to lose. When `isSafeToSkipConfirmation` returns +/// YES, we know there's no need to present a confirmation dialog about potential data loss from ending the task. +- (BOOL)isSafeToSkipConfirmation { + BOOL result = NO; + + ORKStep *currentStep = self.currentStepViewController.step; + + // true if currentStep is NOT capable of contributing results, AND there are no other saveable results + result = result || (([self.currentStepViewController.step isKindOfClass:[ORKInstructionStep class]]) && ([self hasSaveableResults] == NO)); + + // true if currentStep is a standalone reviewStep. No results will be lost by dismissing. + result = result || (ORKDynamicCast(currentStep, ORKReviewStep).isStandalone); + + // true if currentStep is in readOnly mode. Nothing can be lost. + result = result || self.currentStepViewController.readOnlyMode; - // [self result] would not include any results beyond current step. - // Use _managedResults to get the completed result set. + return result; +} + +- (BOOL)hasSaveableResults { + /* [self result] would not include any results beyond current step, because managedResultsArray + [self result] depends on only iterates over _managedStepIdentifiers. If the user hits the back button + during the task, the _managedStepIdentifiers is truncated so it ends with the current step. This is even + thought _managedResults still retains results from a step that comes *after* the current one. + + So, instead, use _managedResults to check all completed results for isSaveable. + */ NSArray *results = _managedResults.allValues; - _saveable = NO; - for (ORKStepResult *result in results) { - if ([result isSaveable]) { - _saveable = YES; - break; - } - } - BOOL isStandaloneReviewStep = NO; - if ([self.currentStepViewController.step isKindOfClass:[ORKReviewStep class]]) { - ORKReviewStep *reviewStep = (ORKReviewStep *)self.currentStepViewController.step; - isStandaloneReviewStep = reviewStep.isStandalone; + for (ORKStepResult *stepResult in results) { + if ([stepResult isSaveable]) { + return YES; + } } - if ((isCurrentInstructionStep && _saveable == NO) || isStandaloneReviewStep || self.currentStepViewController.readOnlyMode) { - return YES; - } else { - return NO; - } + return NO; } - (IBAction)learnMoreAction:(id)sender { @@ -1628,8 +1695,13 @@ - (void)decodeRestorableStateWithCoder:(NSCoder *)coder { } if ([_task respondsToSelector:@selector(stepWithIdentifier:)]) { +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION _requestedHealthTypesForRead = [coder decodeObjectOfClasses:[NSSet setWithArray:@[NSSet.self, HKObjectType.self]] forKey:_ORKRequestedHealthTypesForReadRestoreKey]; _requestedHealthTypesForWrite = [coder decodeObjectOfClasses:[NSSet setWithArray:@[NSSet.self, HKObjectType.self]] forKey:_ORKRequestedHealthTypesForWriteRestoreKey]; +#else + _requestedHealthTypesForRead = nil; + _requestedHealthTypesForWrite = nil; +#endif _presentedDate = [coder decodeObjectOfClass:[NSDate class] forKey:_ORKPresentedDate]; _lastBeginningInstructionStepIdentifier = [coder decodeObjectOfClass:[NSString class] forKey:_ORKLastBeginningInstructionStepIdentifierKey]; _restoredStepIdentifier = [coder decodeObjectOfClass:[NSString class] forKey:_ORKStepIdentifierRestoreKey]; diff --git a/ResearchKitUI/Common/Task/ORKTaskViewController_Internal.h b/ResearchKitUI/Common/Task/ORKTaskViewController_Internal.h index e9fe513408..bce1b51ae3 100644 --- a/ResearchKitUI/Common/Task/ORKTaskViewController_Internal.h +++ b/ResearchKitUI/Common/Task/ORKTaskViewController_Internal.h @@ -31,7 +31,11 @@ #import #import +#import + +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION #import +#endif NS_ASSUME_NONNULL_BEGIN diff --git a/ResearchKitUI/ResearchKitUI.docc/ResearchKitUI.md b/ResearchKitUI/ResearchKitUI.docc/ResearchKitUI.md new file mode 100644 index 0000000000..4e00b181d5 --- /dev/null +++ b/ResearchKitUI/ResearchKitUI.docc/ResearchKitUI.md @@ -0,0 +1,53 @@ +# ``ResearchKitUI`` + +Present ResearchKit model objects. + +## Overview + +ResearchKitUI contains UI to present your configured ResearchKit task. + +## Topics + +### View Controllers + +- ``ORKCompletionStepViewController`` +- ``ORKCustomStepViewController`` +- ``ORKFormStepViewController`` +- ``ORKInstructionStepViewController`` +- ``ORKLearnMoreStepViewController`` +- ``ORKLoginStepViewController`` +- ``ORKPageStepViewController`` +- ``ORKPasscodeViewController`` +- ``ORKPDFViewerStepViewController`` +- ``ORKReviewViewController`` +- ``ORKStepViewController`` +- ``ORKTableStepViewController`` +- ``ORKTaskViewController`` +- ``ORKVerificationStepViewController`` +- ``ORKWaitStepViewController`` +- ``ORKWebViewStepViewController`` + +### Delegates + +- ``ORKCustomSignatureFooterViewStatusDelegate`` +- ``ORKPasscodeDelegate`` +- ``ORKReviewViewControllerDelegate`` +- ``ORKSignatureViewDelegate`` +- ``ORKStepViewControllerDelegate`` +- ``ORKTaskViewControllerDelegate`` +- ``ORKWebViewStepDelegate`` + +### Other + +- ``ORKBorderedButton`` +- ``ORKBorderedButtonDisabledStyle`` +- ``ORKCompletionStepIdentifier`` +- ``ORKContinueButton`` +- ``ORKPlaybackButton`` +- ``ORKSignatureView`` +- ``ORKStepViewControllerNavigationDirection`` +- ``ORKTaskViewControllerFinishReason`` +- ``ORKTaskViewControllerProgressMode`` +- ``ORKTaskViewControllerReviewMode`` +- ``ORKTextButton`` +- ``SwiftUIViewFactory`` diff --git a/ResearchKitUI/ResearchKitUI.h b/ResearchKitUI/ResearchKitUI.h index 4c1733f277..7cfc609518 100644 --- a/ResearchKitUI/ResearchKitUI.h +++ b/ResearchKitUI/ResearchKitUI.h @@ -35,6 +35,7 @@ #import #import #import +#import #import #import #import @@ -47,6 +48,9 @@ #import #import #import +#import +#import +#import #import #import #import @@ -118,4 +122,3 @@ #import #import #import - diff --git a/ResearchKitUI/ResearchKitUI.modulemap b/ResearchKitUI/ResearchKitUI.modulemap index ac669dcd68..bc24f67f20 100644 --- a/ResearchKitUI/ResearchKitUI.modulemap +++ b/ResearchKitUI/ResearchKitUI.modulemap @@ -1,6 +1,7 @@ framework module ResearchKitUI { umbrella header "ResearchKitUI.h" module iOS { + exclude header "ORKColorChoiceCellGroup.h" } export * module * { export * } diff --git a/ResearchKitUI/ResearchKitUI_Private.h b/ResearchKitUI/ResearchKitUI_Private.h index ae3a6ca407..49e09aa60e 100644 --- a/ResearchKitUI/ResearchKitUI_Private.h +++ b/ResearchKitUI/ResearchKitUI_Private.h @@ -31,6 +31,7 @@ #import #import #import +#import #import #import #import diff --git a/ResearchKitUI/Stale/ORKQuestionStepViewController.m b/ResearchKitUI/Stale/ORKQuestionStepViewController.m index 8ae287a8c7..7dde83e217 100644 --- a/ResearchKitUI/Stale/ORKQuestionStepViewController.m +++ b/ResearchKitUI/Stale/ORKQuestionStepViewController.m @@ -147,7 +147,9 @@ - (instancetype)initWithStep:(ORKStep *)step result:(ORKResult *)result { - (instancetype)initWithStep:(ORKStep *)step { self = [super initWithStep:step]; if (self) { +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION _defaultSource = [ORKAnswerDefaultSource sourceWithHealthStore:[HKHealthStore new]]; +#endif } return self; } @@ -447,7 +449,7 @@ - (void)viewWillAppear:(BOOL)animated { [self.taskViewController setRegisteredScrollView:_tableView]; } - +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION NSMutableSet *types = [NSMutableSet set]; ORKAnswerFormat *format = [[self questionStep] answerFormat]; HKObjectType *objType = [format healthKitObjectTypeForAuthorization]; @@ -472,7 +474,7 @@ - (void)viewWillAppear:(BOOL)animated { if (!scheduledRefresh) { [self refreshDefaults]; } - +#endif if (_tableView) { [_tableView reloadData]; } @@ -811,7 +813,7 @@ - (ORKSurveyAnswerCell *)answerCellForTableView:(UITableView *)tableView { @(ORKQuestionTypeWeight) : [ORKSurveyAnswerCellForPicker class], @(ORKQuestionTypeMultiplePicker) : [ORKSurveyAnswerCellForPicker class], @(ORKQuestionTypeInteger): [ORKSurveyAnswerCellForNumber class], - #if !TARGET_OS_VISION + #if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && !TARGET_OS_VISION @(ORKQuestionTypeLocation): [ORKSurveyAnswerCellForLocation class], #endif @(ORKQuestionTypeSES): [ORKSurveyAnswerCellForSES class]}; diff --git a/samples/ORKCatalog/ORKCatalog.xcodeproj/project.pbxproj b/samples/ORKCatalog/ORKCatalog.xcodeproj/project.pbxproj index feb9608e69..a8caf57134 100644 --- a/samples/ORKCatalog/ORKCatalog.xcodeproj/project.pbxproj +++ b/samples/ORKCatalog/ORKCatalog.xcodeproj/project.pbxproj @@ -19,6 +19,7 @@ 51F210FE2B49D04F0072B536 /* TaskListRowConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F210FD2B49D04F0072B536 /* TaskListRowConstants.swift */; }; 863CCD821ACF545E009FD3B4 /* HealthKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 863CCD811ACF545E009FD3B4 /* HealthKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 86B89AA51AB2C0A5001626A4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86B89A9C1AB2C0A5001626A4 /* AppDelegate.swift */; }; + 8C67958C2BC3B09B00DE7286 /* TaskListRowConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F210FD2B49D04F0072B536 /* TaskListRowConstants.swift */; }; 9622283924F04DBF0056E74D /* TaskScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9622283824F04DBF0056E74D /* TaskScreen.swift */; }; 9622283B24F04DFE0056E74D /* CommonElements.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9622283A24F04DFE0056E74D /* CommonElements.swift */; }; 9622283D24F04E6F0056E74D /* AllowScreens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9622283C24F04E6F0056E74D /* AllowScreens.swift */; }; @@ -148,7 +149,6 @@ BC9A27391B82741900BDA84D /* ResultTableViewProviders.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ResultTableViewProviders.swift; path = Results/ResultTableViewProviders.swift; sourceTree = ""; }; BC9A273A1B82741900BDA84D /* ResultViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ResultViewController.swift; path = Results/ResultViewController.swift; sourceTree = ""; }; BC9A273B1B82741900BDA84D /* TextImageTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TextImageTableViewCell.swift; path = Results/TextImageTableViewCell.swift; sourceTree = ""; }; - CA6A0D8B288F01D10048C1EF /* ResearchKitUI (iOS).framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = "ResearchKitUI (iOS).framework"; sourceTree = BUILT_PRODUCTS_DIR; }; CA6A0D8E288F04260048C1EF /* ResearchKitUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ResearchKitUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; CA9B6DF728A2BF51000A7603 /* ResearchKitActiveTask.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ResearchKitActiveTask.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -301,7 +301,6 @@ children = ( CA9B6DF728A2BF51000A7603 /* ResearchKitActiveTask.framework */, CA6A0D8E288F04260048C1EF /* ResearchKitUI.framework */, - CA6A0D8B288F01D10048C1EF /* ResearchKitUI (iOS).framework */, BC2A3CD81C58F1CA00DA64B7 /* ResearchKit.framework */, 863CCD811ACF545E009FD3B4 /* HealthKit.framework */, ); @@ -396,12 +395,11 @@ }; 869230BD1AAA890A00BFE11B = { CreatedOnToolsVersion = 6.2; - LastSwiftMigration = 1020; + LastSwiftMigration = 1530; ProvisioningStyle = Automatic; }; 9622282924F04D780056E74D = { CreatedOnToolsVersion = 11.6; - DevelopmentTeam = 7JYUG8QGJ3; ProvisioningStyle = Automatic; TestTargetID = 869230BD1AAA890A00BFE11B; }; @@ -537,6 +535,7 @@ files = ( 9622283924F04DBF0056E74D /* TaskScreen.swift in Sources */, 9622283B24F04DFE0056E74D /* CommonElements.swift in Sources */, + 8C67958C2BC3B09B00DE7286 /* TaskListRowConstants.swift in Sources */, 9622284124F04EEF0056E74D /* PreSubmissionTests.swift in Sources */, 9622283F24F04EBA0056E74D /* Helpers.swift in Sources */, 9622283D24F04E6F0056E74D /* AllowScreens.swift in Sources */, @@ -879,7 +878,7 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; DEBUG_INFORMATION_FORMAT = dwarf; - DEVELOPMENT_TEAM = 7JYUG8QGJ3; + DEVELOPMENT_TEAM = ""; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ORKCatalogUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -909,7 +908,7 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = 7JYUG8QGJ3; + DEVELOPMENT_TEAM = ""; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ORKCatalogUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( diff --git a/samples/ORKCatalog/ORKCatalog/Results/ResultTableViewProviders.swift b/samples/ORKCatalog/ORKCatalog/Results/ResultTableViewProviders.swift index 3263ce15e3..ffe44a23e5 100644 --- a/samples/ORKCatalog/ORKCatalog/Results/ResultTableViewProviders.swift +++ b/samples/ORKCatalog/ORKCatalog/Results/ResultTableViewProviders.swift @@ -87,9 +87,10 @@ func resultTableViewProviderForResult(_ result: ORKResult?, delegate: ResultProv case is ORKDateQuestionResult: providerType = DateQuestionResultTableViewProvider.self +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION case is ORKLocationQuestionResult: providerType = LocationQuestionResultTableViewProvider.self - +#endif case is ORKNumericQuestionResult: providerType = NumericQuestionResultTableViewProvider.self @@ -444,6 +445,7 @@ class DateQuestionResultTableViewProvider: ResultTableViewProvider { } } +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION /// Table view provider specific to an `ORKLocationQuestionResult` instance. class LocationQuestionResultTableViewProvider: ResultTableViewProvider { // MARK: ResultTableViewProvider @@ -462,7 +464,7 @@ class LocationQuestionResultTableViewProvider: ResultTableViewProvider { return rows } } - +#endif /// Table view provider specific to an `ORKNumericQuestionResult` instance. class NumericQuestionResultTableViewProvider: ResultTableViewProvider { // MARK: ResultTableViewProvider @@ -1388,3 +1390,5 @@ class SignatureResultTableViewProvider: ResultTableViewProvider { return UITableView.automaticDimension } } + + diff --git a/samples/ORKCatalog/ORKCatalog/Results/ResultViewController.swift b/samples/ORKCatalog/ORKCatalog/Results/ResultViewController.swift index e2c7b7caca..1edb675e47 100644 --- a/samples/ORKCatalog/ORKCatalog/Results/ResultViewController.swift +++ b/samples/ORKCatalog/ORKCatalog/Results/ResultViewController.swift @@ -49,7 +49,6 @@ class ResultViewController: UITableViewController { // MARK: Properties var result: ORKResult? - var currentResult: ORKResult? var resultTableViewProvider: UITableViewDataSource & UITableViewDelegate = resultTableViewProviderForResult(nil, delegate: nil) @@ -128,3 +127,4 @@ extension ResultViewController: ResultProviderDelegate { } } + diff --git a/samples/ORKCatalog/ORKCatalog/TaskListRowConstants.swift b/samples/ORKCatalog/ORKCatalog/TaskListRowConstants.swift index 4fa9c2ddbe..0a773d90bb 100644 --- a/samples/ORKCatalog/ORKCatalog/TaskListRowConstants.swift +++ b/samples/ORKCatalog/ORKCatalog/TaskListRowConstants.swift @@ -266,6 +266,7 @@ enum Identifier { case speechRecognitionTask case speechInNoiseTask case stroopTask + case timedWalkWithTurnAroundTask case toneAudiometryTask case dBHLToneAudiometryTask @@ -287,9 +288,20 @@ enum Identifier { case webViewTask case webViewStep - // 3DModelStep tasks + // 3DModelStep tasks. case usdzModelStep case usdzModelTask + + // ORKColorChoice tasks. + case colorChoiceQuestionTask + case colorChoiceQuestionStep + case colorChoiceQuestionStepSwatchOnly + case colorChoiceQuestionFormItem + + // Family History tasks. + case familyHistoryStep + case familyHistoryTask + } @@ -355,4 +367,6 @@ enum TaskListRowStrings { static var loremIpsumLongText: String { return "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam adhuc, meo fortasse vitio, quid ego quaeram non perspicis. Plane idem, inquit, et maxima quidem, qua fieri nulla maior potest. Quonam, inquit, modo? An potest, inquit ille, quicquam esse suavius quam nihil dolere? Cave putes quicquam esse verius. Quonam, inquit, modo?" } + + } diff --git a/samples/ORKCatalog/ORKCatalog/TaskListRowSteps.swift b/samples/ORKCatalog/ORKCatalog/TaskListRowSteps.swift index a587fbf84e..70523dba95 100644 --- a/samples/ORKCatalog/ORKCatalog/TaskListRowSteps.swift +++ b/samples/ORKCatalog/ORKCatalog/TaskListRowSteps.swift @@ -30,6 +30,7 @@ import ResearchKitActiveTask + enum TaskListRowSteps { // MARK: - ORKFormStep Examples @@ -72,6 +73,7 @@ enum TaskListRowSteps { return booleanQuestionFormStep } +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION static var bloodTypeExample: ORKFormStep { let bloodType = HKCharacteristicType.characteristicType(forIdentifier: HKCharacteristicTypeIdentifier.bloodType)! let bloodTypeAnswerFormat = ORKHealthKitCharacteristicTypeAnswerFormat(characteristicType: bloodType) @@ -89,6 +91,7 @@ enum TaskListRowSteps { return bloodTypeFormStep } +#endif static var continuousScaleWithPercentExample: ORKFormStep { // The second step is a scale control that allows continuous movement with a percent formatter. @@ -245,6 +248,7 @@ enum TaskListRowSteps { return step } +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION static var heartRateExample: ORKFormStep { let heartRateType = HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate)! let heartRateAnswerFormat = ORKHealthKitQuantityTypeAnswerFormat(quantityType: heartRateType, @@ -263,6 +267,7 @@ enum TaskListRowSteps { return heartRateFormStep } +#endif static var heightExample: ORKFormStep { let stepIdentifier = String(describing: Identifier.heightQuestionFormStep1) @@ -275,6 +280,7 @@ enum TaskListRowSteps { return formStep } +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION static var heightHealthKitExample: ORKFormStep { let stepIdentifier = String(describing: Identifier.heightQuestionFormStep4) let heightAnswerFormat = ORKHealthKitQuantityTypeAnswerFormat(quantityType: HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.height)!, unit: HKUnit.meterUnit(with: .centi), style: .decimal) @@ -285,6 +291,7 @@ enum TaskListRowSteps { return formStep } +#endif static var heightMetricSystemExample: ORKFormStep { let stepIdentifier = String(describing: Identifier.heightQuestionFormStep2) @@ -340,6 +347,7 @@ enum TaskListRowSteps { return imageChoiceFormStep } +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION static var locationExample: ORKFormStep { let locationAnswerFormat = ORKLocationAnswerFormat() let locationFormItem = ORKFormItem(identifier: String(describing: Identifier.locationQuestionFormItem), @@ -353,6 +361,7 @@ enum TaskListRowSteps { return locationFormStep } +#endif static var textAnswerExample: ORKFormStep { let textAnswerFormat = ORKTextAnswerFormat() @@ -574,6 +583,7 @@ enum TaskListRowSteps { return formStep } +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION static var weightHealthKitBodyMassExample: ORKFormStep { let stepIdentifier = String(describing: Identifier.weightQuestionFormStep7) let weightAnswerFormat = ORKHealthKitQuantityTypeAnswerFormat(quantityType: HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.bodyMass)!, unit: HKUnit.gramUnit(with: .kilo), style: .decimal) @@ -584,6 +594,7 @@ enum TaskListRowSteps { return formStep } +#endif static var weightMetricSystemExample: ORKFormStep { let stepIdentifier = String(describing: Identifier.weightQuestionFormStep2) @@ -727,6 +738,8 @@ enum TaskListRowSteps { return webViewStep } + +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION static var requestPermissionsStepExample: ORKRequestPermissionsStep { let healthKitTypesToWrite: Set = [ HKObjectType.quantityType(forIdentifier: .bodyMassIndex)!, @@ -746,6 +759,7 @@ enum TaskListRowSteps { return requestPermissionsStep } +#endif static var consentCompletionStepExample: ORKCompletionStep { let completionStep = ORKCompletionStep(identifier: "completionId") @@ -784,6 +798,198 @@ enum TaskListRowSteps { return usdzModelStep } + // MARK: - ORKCompletionStep + + static var completionStepExample: ORKCompletionStep { + let completionStep = ORKCompletionStep(identifier: String(describing: Identifier.completionStep)) + completionStep.iconImage = UIImage(systemName: "checkmark.circle.fill") + completionStep.title = "Task Completed" + completionStep.text = "Thank you for completing the task." + + return completionStep + } + + // MARK: - ORKFamilyHistoryStep + + static var familyHistoryStepExample: ORKFamilyHistoryStep { + // create ORKHealthConditions + + let healthConditions = [ + ORKHealthCondition(identifier: "healthConditionIdentifier1", displayName: "Diabetes", value: "Diabetes" as NSString), + ORKHealthCondition(identifier: "healthConditionIdentifier2", displayName: "Heart Attack", value: "Heart Attack" as NSString), + ORKHealthCondition(identifier: "healthConditionIdentifier3", displayName: "Stroke", value: "Stroke" as NSString) + ] + + // add ORKHealthConditions to ORKConditionStepConfiguration object + + let conditionStepConfiguration = ORKConditionStepConfiguration(stepIdentifier: "FamilyHistoryConditionStepIdentifier", conditionsFormItemIdentifier: "HealthConditionsFormItemIdentifier", conditions: healthConditions, formItems: []) + + // create formItems and formStep for parent relative group + let learnMoreInstructionStep01 = ORKLearnMoreInstructionStep(identifier: "LearnMoreInstructionStep01") + learnMoreInstructionStep01.title = NSLocalizedString("Learn more title", comment: "") + learnMoreInstructionStep01.text = NSLocalizedString("Learn more text", comment: "") + let learnMoreItem01 = ORKLearnMoreItem(text: nil, learnMoreInstructionStep: learnMoreInstructionStep01) + + let relativeNameSectionHeaderFormItem = ORKFormItem(sectionTitle: "Add a label to identify this family member", detailText: "Instead of their full name, please use a nickname, alias, or initials. Your response will only be saved on your device.", learnMoreItem: learnMoreItem01, showsProgress: true) + relativeNameSectionHeaderFormItem.tagText = "OPTIONAL" + let parentTextEntryAnswerFormat = ORKAnswerFormat.textAnswerFormat() + parentTextEntryAnswerFormat.multipleLines = false + parentTextEntryAnswerFormat.maximumLength = 3 + + let parentNameFormItem = ORKFormItem(identifier: "ParentNameIdentifier", text: "enter optional name", answerFormat: parentTextEntryAnswerFormat) + parentNameFormItem.isOptional = true + + let sexAtBirthOptions = [ + ORKTextChoice(text: "Female", value: "Female" as NSString), + ORKTextChoice(text: "Male", value: "Male" as NSString), + ORKTextChoice(text: "Intersex", value: "Intersex" as NSString), + ORKTextChoice(text: "I don't know", value: "i_dont_know" as NSString), + ORKTextChoice(text: "I prefer not to answer", value: "i_prefer_not_to_answer" as NSString) + ] + + let parentSexAtBirthChoiceAnswerFormat = ORKAnswerFormat.choiceAnswerFormat(with: .singleChoice, textChoices: sexAtBirthOptions) + let parentSextAtBirthFormItem = ORKFormItem(identifier: "ParentSexAtBirthIdentifier", text: "What was the sex assigned on their original birth certificate?", answerFormat: parentSexAtBirthChoiceAnswerFormat) + parentSextAtBirthFormItem.isOptional = false + + let vitalStatusOptions = [ + ORKTextChoice(text: "Living", value: "living" as NSString), + ORKTextChoice(text: "Deceased", value: "deceased" as NSString), + ORKTextChoice(text: "I don't know", value: "dont_know" as NSString), + ORKTextChoice(text: "I prefer not to answer", value: "prefer_not_to_answer" as NSString), + ] + + let parentVitalStatusChoiceAnswerFormat = ORKAnswerFormat.choiceAnswerFormat(with: .singleChoice, textChoices: vitalStatusOptions) + let parentVitalStatusFormItem = ORKFormItem(identifier: "ParentVitalStatusIdentifier", text: "What is their current vital status?", answerFormat: parentVitalStatusChoiceAnswerFormat) + parentVitalStatusFormItem.isOptional = false + + let parentFormStep = ORKFormStep(identifier: "ParentSurveyIdentifier") + let visibilityRule = ORKPredicateFormItemVisibilityRule( + predicate: ORKResultPredicate.predicateForChoiceQuestionResult( + with: .init(stepIdentifier: parentFormStep.identifier, resultIdentifier: parentVitalStatusFormItem.identifier), + expectedAnswerValue: NSString(string: "living") + ) + ) + + let parentAgePickerSectionHeaderFormItem = ORKFormItem(identifier: "ParentAgeSectionHeaderIdentifier", text: "What is their approximate birth year?", answerFormat: nil) + parentAgePickerSectionHeaderFormItem.visibilityRule = visibilityRule + + let parentAgePickerAnswerFormat = ORKAgeAnswerFormat( + minimumAge: 18, + maximumAge: 90, + minimumAgeCustomText: "18 or younger", + maximumAgeCustomText: "90 or older", + showYear: true, + useYearForResult: true, + treatMinAgeAsRange: true, + treatMaxAgeAsRange: false, + defaultValue: 30) + parentAgePickerAnswerFormat.shouldShowDontKnowButton = true + + let parentAgeFormItem = ORKFormItem(identifier: "ParentAgeFormItemIdentifier", text: nil, answerFormat: parentAgePickerAnswerFormat) + parentAgeFormItem.isOptional = false + parentAgeFormItem.visibilityRule = visibilityRule + + parentFormStep.isOptional = false + parentFormStep.title = "Parent" + parentFormStep.detailText = "Answer these questions to the best of your ability." + parentFormStep.formItems = [ + relativeNameSectionHeaderFormItem, + parentNameFormItem, + parentSextAtBirthFormItem, + parentVitalStatusFormItem, + parentAgePickerSectionHeaderFormItem, + parentAgeFormItem + ] + + // create formItems and formStep for siblings relative group + + let siblingTextEntryAnswerFormat = ORKAnswerFormat.textAnswerFormat() + siblingTextEntryAnswerFormat.multipleLines = false + siblingTextEntryAnswerFormat.placeholder = "enter optional name" + siblingTextEntryAnswerFormat.maximumLength = 3 + + let siblingNameFormItem = ORKFormItem(identifier: "SiblingNameIdentifier", text: "Name or Nickname", answerFormat: siblingTextEntryAnswerFormat) + siblingNameFormItem.isOptional = false + + let siblingSexAtBirthChoiceAnswerFormat = ORKAnswerFormat.choiceAnswerFormat(with: .singleChoice, textChoices: sexAtBirthOptions) + let siblingSextAtBirthFormItem = ORKFormItem(identifier: "SiblingSexAtBirthIdentifier", text: "What was the sex assigned on their original birth certificate?", answerFormat: siblingSexAtBirthChoiceAnswerFormat) + siblingSextAtBirthFormItem.isOptional = false + + let siblingVitalStatusChoiceAnswerFormat = ORKAnswerFormat.choiceAnswerFormat(with: .singleChoice, textChoices: vitalStatusOptions) + let siblingVitalStatusFormItem = ORKFormItem(identifier: "SiblingVitalStatusIdentifier", text: "What is their current vital status?", answerFormat: siblingVitalStatusChoiceAnswerFormat) + siblingVitalStatusFormItem.isOptional = false + + let siblingAgePickerAnswerFormat = ORKAgeAnswerFormat( + minimumAge: 18, + maximumAge: 90, + minimumAgeCustomText: "18 or younger", + maximumAgeCustomText: "90 or older", + showYear: true, + useYearForResult: true, + treatMinAgeAsRange: true, + treatMaxAgeAsRange: false, + defaultValue: 30) + siblingAgePickerAnswerFormat.shouldShowDontKnowButton = true + + let siblingAgePickerSectionHeaderFormItem = ORKFormItem(identifier: "SiblingAgeSectionHeaderIdentifier", text: "What is their approximate birth year?", answerFormat: nil) + + let siblingAgeFormItem = ORKFormItem(identifier: "SiblingAgeFormItemIdentifier", text: nil, answerFormat: siblingAgePickerAnswerFormat) + siblingAgeFormItem.isOptional = false + + let siblingFormStep = ORKFormStep(identifier: "SiblingSurveyIdentifier") + let siblingAgeVisibilityRule = ORKPredicateFormItemVisibilityRule( + predicate: ORKResultPredicate.predicateForChoiceQuestionResult( + with: .init(stepIdentifier: siblingFormStep.identifier, resultIdentifier: siblingVitalStatusFormItem.identifier), + expectedAnswerValue: NSString(string: "living") + ) + ) + siblingAgePickerSectionHeaderFormItem.visibilityRule = siblingAgeVisibilityRule + siblingAgeFormItem.visibilityRule = siblingAgeVisibilityRule + + siblingFormStep.title = "Sibling" + siblingFormStep.detailText = "Answer these questions to the best of your ability." + siblingFormStep.formItems = [ + relativeNameSectionHeaderFormItem, + siblingNameFormItem, + siblingSextAtBirthFormItem, + siblingVitalStatusFormItem, + siblingAgePickerSectionHeaderFormItem, + siblingAgeFormItem + ] + + // create ORKRelativeGroups + + let relativeGroups = [ + ORKRelativeGroup(identifier: "ParentGroupIdentifier", + name: "Biological Parent", + sectionTitle: "Biological Parents", + sectionDetailText: "Include your blood-related parents.", + identifierForCellTitle: "ParentNameIdentifier", + maxAllowed: 2, + formSteps: [parentFormStep], + detailTextIdentifiers: ["ParentSexAtBirthIdentifier", "ParentVitalStatusIdentifier", "ParentAgeFormItemIdentifier"]), + ORKRelativeGroup(identifier: "SiblingGroupIdentifier", + name: "Sibling", + sectionTitle: "Biological Siblings", + sectionDetailText: "Include all siblings who share one or both of your blood-related parents.", + identifierForCellTitle: "SiblingNameIdentifier", + maxAllowed: 10, + formSteps: [siblingFormStep], + detailTextIdentifiers: ["SiblingSexAtBirthIdentifier", "SiblingVitalStatusIdentifier", "SiblingAgeFormItemIdentifier"]) + ] + + // create ORKFamilyHistoryStep and add to a ORKOrderedTask + + let familyHistoryStep = ORKFamilyHistoryStep(identifier: String(describing: Identifier.familyHistoryStep)) + familyHistoryStep.title = "Family Health History" + familyHistoryStep.detailText = "The overview of your biological family members can inform health risks and lifestyle." + familyHistoryStep.conditionStepConfiguration = conditionStepConfiguration + familyHistoryStep.relativeGroups = relativeGroups + + return familyHistoryStep + } + + // MARK: - Helpers private static var formItemSectionHeaderExample: ORKFormItem { diff --git a/samples/ORKCatalog/ORKCatalog/Tasks/TaskListRow.swift b/samples/ORKCatalog/ORKCatalog/Tasks/TaskListRow.swift index a4817ce05d..4d15b7dddf 100644 --- a/samples/ORKCatalog/ORKCatalog/Tasks/TaskListRow.swift +++ b/samples/ORKCatalog/ORKCatalog/Tasks/TaskListRow.swift @@ -76,7 +76,9 @@ enum TaskListRow: Int, CustomStringConvertible { case dateTimeQuestion case date3DayLimitQuestionTask case imageChoiceQuestion +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION case locationQuestion +#endif case numericQuestion case scaleQuestion case textQuestion @@ -121,7 +123,9 @@ enum TaskListRow: Int, CustomStringConvertible { case walkBackAndForth case heightQuestion case weightQuestion +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION case healthQuantity +#endif case kneeRangeOfMotion case shoulderRangeOfMotion case trailMaking @@ -131,6 +135,9 @@ enum TaskListRow: Int, CustomStringConvertible { case consentTask case consentDoc case usdzModel + case ageQuestion + case colorChoiceQuestion + case familyHistory class TaskListRowSection { @@ -146,7 +153,7 @@ enum TaskListRow: Int, CustomStringConvertible { /// Returns an array of all the task list row enum cases. static var sections: [ TaskListRowSection ] { - let defaultSections = [ + var defaultSections = [ TaskListRowSection(title: "Surveys", rows: [ .dontknowSurvey, @@ -158,15 +165,16 @@ enum TaskListRow: Int, CustomStringConvertible { ]), TaskListRowSection(title: "Survey Questions", rows: [ + .ageQuestion, .booleanQuestion, + .colorChoiceQuestion, .customBooleanQuestion, .dateTimeQuestion, .dateQuestion, .date3DayLimitQuestionTask, - .healthQuantity, + .familyHistory, .heightQuestion, .imageChoiceQuestion, - .locationQuestion, .numericQuestion, .scaleQuestion, .textChoiceQuestion, @@ -230,6 +238,26 @@ enum TaskListRow: Int, CustomStringConvertible { ])] + #if ORK_FEATURE_HEALTHKIT_AUTHORIZATION + let healthSections:[TaskListRowSection] = [ + TaskListRowSection(title: "Health", rows: + [ + .healthQuantity + ]) + ] + defaultSections = defaultSections + healthSections + #endif + +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION + let locationSections:[TaskListRowSection] = [ + TaskListRowSection(title: "Location", rows: + [ + .locationQuestion, + ]) + ] + defaultSections = defaultSections + locationSections +#endif + return defaultSections } @@ -272,15 +300,16 @@ enum TaskListRow: Int, CustomStringConvertible { case .weightQuestion: return NSLocalizedString("Weight Question", comment: "") - +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION case .healthQuantity: return NSLocalizedString("Health Quantity Question", comment: "") - +#endif case .imageChoiceQuestion: return NSLocalizedString("Image Choice Question", comment: "") - +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION case .locationQuestion: return NSLocalizedString("Location Question", comment: "") +#endif case .numericQuestion: return NSLocalizedString("Numeric Question", comment: "") @@ -435,6 +464,16 @@ enum TaskListRow: Int, CustomStringConvertible { case .usdzModel: return NSLocalizedString("USDZ Model", comment: "") + case .ageQuestion: + return NSLocalizedString("Age Question", comment: "") + + case .colorChoiceQuestion: + return NSLocalizedString("Color Choice Question", comment: "") + + case .familyHistory: + return NSLocalizedString("Family History Step", comment: "") + + case .surveyWithMultipleOptions: return NSLocalizedString("Survey With Multiple Options", comment: "") } @@ -483,15 +522,19 @@ enum TaskListRow: Int, CustomStringConvertible { case .weightQuestion: return weightQuestionTask - + +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION case .healthQuantity: return healthQuantityTypeTask +#endif case .imageChoiceQuestion: return imageChoiceQuestionTask +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION case .locationQuestion: return locationQuestionTask +#endif case .numericQuestion: return numericQuestionTask @@ -643,6 +686,15 @@ enum TaskListRow: Int, CustomStringConvertible { case .usdzModel: return usdzModel + case .ageQuestion: + return ageQuestionTask + + case .colorChoiceQuestion: + return colorChoiceQuestionTask + + case .familyHistory: + return familyHistoryTask + case .textChoiceQuestionWithImageTask: return textChoiceQuestionWithImageTask @@ -786,7 +838,7 @@ enum TaskListRow: Int, CustomStringConvertible { formItem06.placeholder = formItem06Text - let appleChoices: [ORKTextChoice] = [ORKTextChoice(text: "Granny Smith", value: 1 as NSNumber), + let appleChoices: [ORKTextChoice] = [ORKTextChoice(text: "Granny Smith", value: 1 as NSNumber), ORKTextChoice(text: "Honeycrisp", value: 2 as NSNumber), ORKTextChoice(text: "Fuji", value: 3 as NSNumber), ORKTextChoice(text: "McIntosh", value: 10 as NSNumber), @@ -954,17 +1006,20 @@ enum TaskListRow: Int, CustomStringConvertible { let informedConsentInstructionStep = TaskListRowSteps.informedConsentStepExample let webViewStep = TaskListRowSteps.webViewStepExample let consentSharingFormStep = TaskListRowSteps.informedConsentSharingStepExample - let requestPermissionStep = TaskListRowSteps.requestPermissionsStepExample - let consentCompletionStep = TaskListRowSteps.consentCompletionStepExample - let steps: [ORKStep] = [ + var steps: [ORKStep] = [ welcomeInstructionStep, informedConsentInstructionStep, webViewStep, consentSharingFormStep, - requestPermissionStep, - consentCompletionStep ] +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION + let requestPermissionStep = TaskListRowSteps.requestPermissionsStepExample + steps.append(requestPermissionStep) +#endif + + let consentCompletionStep = TaskListRowSteps.consentCompletionStepExample + steps.append(consentCompletionStep) return ORKOrderedTask(identifier: String(describing: Identifier.consentTask), steps: steps) } @@ -1060,9 +1115,14 @@ enum TaskListRow: Int, CustomStringConvertible { let step1 = TaskListRowSteps.heightExample let step2 = TaskListRowSteps.heightMetricSystemExample let step3 = TaskListRowSteps.heightUSCSystemExample - let step4 = TaskListRowSteps.heightHealthKitExample - return ORKOrderedTask(identifier: String(describing: Identifier.heightQuestionTask), steps: [step1, step2, step3, step4]) + var steps = [step1, step2, step3] + +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION + let step4 = TaskListRowSteps.heightHealthKitExample + steps.append(contentsOf:[step4]) +#endif + return ORKOrderedTask(identifier: String(describing: Identifier.heightQuestionTask), steps: steps) } /// This task demonstrates a question asking for the user weight. @@ -1073,17 +1133,24 @@ enum TaskListRow: Int, CustomStringConvertible { let step4 = TaskListRowSteps.weightMetricSystemHighPrecisionExample let step5 = TaskListRowSteps.weightUSCSystemExample let step6 = TaskListRowSteps.weightUSCSystemHighPrecisionExample - let step7 = TaskListRowSteps.weightHealthKitBodyMassExample - return ORKOrderedTask(identifier: String(describing: Identifier.weightQuestionTask), steps: [step1, step2, step3, step4, step5, step6, step7]) + var steps = [step1, step2, step3, step4, step5, step6] + +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION + let step7 = TaskListRowSteps.weightHealthKitBodyMassExample + steps.append(contentsOf:[step7]) +#endif + return ORKOrderedTask(identifier: String(describing: Identifier.weightQuestionTask), steps: steps) } +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION private var healthQuantityTypeTask: ORKTask { - let heartRateQuestion = TaskListRowSteps.heartRateExample + let heartRateQuestion = TaskListRowSteps.heartRateExample let bloodTypeQuestion = TaskListRowSteps.bloodTypeExample return ORKOrderedTask(identifier: String(describing: Identifier.healthQuantityTask), steps: [heartRateQuestion, bloodTypeQuestion]) } +#endif /** This task demonstrates a survey question involving picking from a series of @@ -1096,13 +1163,15 @@ enum TaskListRow: Int, CustomStringConvertible { return ORKOrderedTask(identifier: String(describing: Identifier.imageChoiceQuestionTask), steps: [questionStep1, questionStep2]) } - + +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION /// This task presents just a single location question. private var locationQuestionTask: ORKTask { let locationFormStep = TaskListRowSteps.locationExample return ORKOrderedTask(identifier: String(describing: Identifier.locationQuestionTask), steps: [locationFormStep]) } +#endif /// This task presents a few different ORKReviewSteps private var reviewTask: ORKTask { @@ -1132,7 +1201,7 @@ enum TaskListRow: Int, CustomStringConvertible { Note that the unit is just a string, prompting the user to enter the value in the expected unit. The unit string propagates into the result object. */ - private var numericQuestionTask: ORKTask { + private var numericQuestionTask: ORKTask { let questionStep1 = TaskListRowSteps.decimalExample let questionStep2 = TaskListRowSteps.decimalNoUnitExample let questionStep3 = TaskListRowSteps.decimalWithDisplayUnitExample @@ -1145,7 +1214,7 @@ enum TaskListRow: Int, CustomStringConvertible { } /// This task presents two options for questions displaying a scale control. - private var scaleQuestionTask: ORKTask { + private var scaleQuestionTask: ORKTask { let questionStep1 = TaskListRowSteps.scaleExample let questionStep2 = TaskListRowSteps.continuousScaleWithPercentExample let questionStep3 = TaskListRowSteps.verticalScaleWithPercentExample @@ -1342,6 +1411,10 @@ enum TaskListRow: Int, CustomStringConvertible { let motionActivityPermissionType = ORKMotionActivityPermissionType() + + var permissionTypes = [notificationsPermissionType, motionActivityPermissionType] + +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION let healthKitTypesToWrite: Set = [ HKObjectType.quantityType(forIdentifier: .bodyMassIndex)!, HKObjectType.quantityType(forIdentifier: .activeEnergyBurned)!, @@ -1353,16 +1426,26 @@ enum TaskListRow: Int, CustomStringConvertible { HKObjectType.workoutType()] - let healthKitPermissionType = ORKHealthKitPermissionType(sampleTypesToWrite: healthKitTypesToWrite, - objectTypesToRead: healthKitTypesToRead) - + let healthKitPermissionType = ORKHealthKitPermissionType( + sampleTypesToWrite: healthKitTypesToWrite, + objectTypesToRead: healthKitTypesToRead + ) + + permissionTypes.append(healthKitPermissionType) +#endif + +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION let locationPermissionType = ORKLocationPermissionType() + permissionTypes.append(locationPermissionType) +#endif let requestPermissionsStep = ORKRequestPermissionsStep( identifier: String(describing: Identifier.requestPermissionsStep), - permissionTypes: [notificationsPermissionType, motionActivityPermissionType, healthKitPermissionType, locationPermissionType]) + permissionTypes: permissionTypes) requestPermissionsStep.title = "Health Data Request" + requestPermissionsStep.detailText = "Some details here" + requestPermissionsStep.useExtendedPadding = false requestPermissionsStep.text = "Please review the health data types below and enable sharing to contribute to the study." return ORKOrderedTask(identifier: String(describing: Identifier.requestPermissionsStep), steps: [requestPermissionsStep]) @@ -1658,7 +1741,7 @@ enum TaskListRow: Int, CustomStringConvertible { /// This task presents the Speech in Noise pre-defined active task. private var speechInNoiseTask: ORKTask { - return ORKOrderedTask.speechInNoiseTask(withIdentifier: String(describing: Identifier.speechInNoiseTask), intendedUseDescription: nil, options: []) + return ORKOrderedTask.speechInNoiseTask(withIdentifier: String(describing: Identifier.speechInNoiseTask), intendedUseDescription: TaskListRowStrings.exampleDescription, options: []) } /// This task presents the Stroop pre-defined active task. @@ -1763,5 +1846,140 @@ enum TaskListRow: Int, CustomStringConvertible { let usdzModelStep = TaskListRowSteps.usdzModelExample return ORKOrderedTask(identifier: String(describing: Identifier.usdzModelTask), steps: [usdzModelStep]) } - + + /// This task demonstrates a question asking for the user age. + private var ageQuestionTask: ORKTask { + let ageFormItemSectionHeader1 = ORKFormItem(sectionTitle: "What is your age?", detailText: "Age question with default values.", learnMoreItem: nil, showsProgress: true) + + + // age picker example 1 + let answerFormat = ORKAgeAnswerFormat() + answerFormat.shouldShowDontKnowButton = true + answerFormat.customDontKnowButtonText = "Prefer not to answer" + let ageFormItem = ORKFormItem(identifier: String(describing: Identifier.ageQuestionFormItem), text: nil, answerFormat: answerFormat) + + ageFormItem.isOptional = true + + let step = ORKFormStep(identifier: String(describing: Identifier.ageQuestionFormStep), title: "Title here", text: "Default age picker.") + step.formItems = [ageFormItemSectionHeader1, ageFormItem] + + // age picker example 2 + let ageFormItemSectionHeader2 = ORKFormItem(sectionTitle: "What is your age?", detailText: "Age question with custom min/max values.", learnMoreItem: nil, showsProgress: true) + + let answerFormat2 = ORKAgeAnswerFormat(minimumAge: 18, maximumAge: 90) + let ageFormItem2 = ORKFormItem(identifier: String(describing: Identifier.ageQuestionFormItem2), text: nil, answerFormat: answerFormat2) + ageFormItem2.isOptional = false + + let step2 = ORKFormStep(identifier: String(describing: Identifier.ageQuestionFormStep2), title: "Title here", text: "Age picker with modified min and max ages.") + step2.formItems = [ageFormItemSectionHeader2, ageFormItem2] + + // age picker example 3 + let ageFormItemSectionHeader3 = ORKFormItem(sectionTitle: "What is your age?", detailText: "Age question that shows year in choices and passes back year for the result.", learnMoreItem: nil, showsProgress: true) + + let answerFormat3 = ORKAgeAnswerFormat( + minimumAge: 18, + maximumAge: 80, + minimumAgeCustomText: "18 or younger", + maximumAgeCustomText: "80 or older", + showYear: true, + useYearForResult: true, + defaultValue: 40) + + let ageFormItem3 = ORKFormItem(identifier: String(describing: Identifier.ageQuestionFormItem3), text: nil, answerFormat: answerFormat3) + ageFormItem3.isOptional = false + + let step3 = ORKFormStep(identifier: String(describing: Identifier.ageQuestionFormStep3), title: "Title here", text: "Age picker with modified min and max ages.") + step3.formItems = [ageFormItemSectionHeader3, ageFormItem3] + + + // age picker example 4 + let ageFormItemSectionHeader4 = ORKFormItem(sectionTitle: "What was your age in the year 2000?", detailText: "Age question that passes back sentinel values for the result if the minimum (-1) or maximum (-2) values are selected.", learnMoreItem: nil, showsProgress: true) + + let answerFormat4 = ORKAgeAnswerFormat( + minimumAge: 1, + maximumAge: 60, + minimumAgeCustomText: "Under a year old", + maximumAgeCustomText: "60 or older", + showYear: true, + useYearForResult: false, + treatMinAgeAsRange: true, + treatMaxAgeAsRange: true, + defaultValue: 30) + + answerFormat4.relativeYear = 2000 + + let ageFormItem4 = ORKFormItem(identifier: String(describing: Identifier.ageQuestionFormItem4), text: nil, answerFormat: answerFormat4) + ageFormItem4.isOptional = false + + let step4 = ORKFormStep(identifier: String(describing: Identifier.ageQuestionFormStep4), title: "Title here", text: "Age picker with utilizing a updated relative year.") + step4.formItems = [ageFormItemSectionHeader4, ageFormItem4] + + let completionStep = ORKCompletionStep(identifier: "completionStepIdentifier") + completionStep.title = "Task complete" + + return ORKOrderedTask(identifier: String(describing: Identifier.ageQuestionTask), steps: [step, step2, step3, step4, completionStep]) + } + + private var colorChoiceQuestionTask: ORKTask { + let colorChoiceOneText = NSLocalizedString("Choice 1", comment: "") + let colorChoiceTwoText = NSLocalizedString("Choice 2", comment: "") + let colorChoiceThreeText = NSLocalizedString("Choice 3", comment: "") + let colorChoiceFourText = NSLocalizedString("Choice 4", comment: "") + let colorChoiceFiveText = NSLocalizedString("Choice 5", comment: "") + let colorChoiceSixText = NSLocalizedString("Choice 6", comment: "") + let colorChoiceSevenText = NSLocalizedString("None of the above", comment: "") + + let colorOne = UIColor(red: 244/255, green: 208/255, blue: 176/255, alpha: 1.0) + let colorTwo = UIColor(red: 232/255, green: 180/255, blue: 143/255, alpha: 1.0) + let colorThree = UIColor(red: 211/255, green: 158/255, blue: 124/255, alpha: 1.0) + let colorFour = UIColor(red: 187/255, green: 119/255, blue: 80/255, alpha: 1.0) + let colorFive = UIColor(red: 165/255, green: 93/255, blue: 43/255, alpha: 1.0) + let colorSix = UIColor(red: 60/255, green: 32/255, blue: 29/255, alpha: 1.0) + + let colorChoices = [ + ORKColorChoice(color: colorOne, text: colorChoiceOneText, detailText: nil, value: "choice_1" as NSString), + ORKColorChoice(color: colorTwo, text: colorChoiceTwoText, detailText: nil, value: "choice_2" as NSString), + ORKColorChoice(color: colorThree, text: colorChoiceThreeText, detailText: nil, value: "choice_3" as NSString), + ORKColorChoice(color: colorFour, text: colorChoiceFourText, detailText: nil, value: "choice_4" as NSString), + ORKColorChoice(color: colorFive, text: colorChoiceFiveText, detailText: nil, value: "choice_5" as NSString), + ORKColorChoice(color: colorSix, text: colorChoiceSixText, detailText: nil, value: "choice_6" as NSString), + ORKColorChoice(color: nil, text: colorChoiceSevenText, detailText: nil, value: "choice_7" as NSString) + ] + + let answerFormat = ORKAnswerFormat.choiceAnswerFormat(with: .singleChoice, colorChoices: colorChoices) + let formItem = ORKFormItem(identifier: String(describing: Identifier.colorChoiceQuestionFormItem), text: TaskListRowStrings.exampleQuestionText, answerFormat: answerFormat) + formItem.detailText = "Select your favorite color from the offerings below" + let formStep = ORKFormStep(identifier: String(describing: Identifier.colorChoiceQuestionStep), title: NSLocalizedString("Color Choice", comment: ""), text: TaskListRowStrings.exampleDetailText) + + formStep.formItems = [formItem] + + let colorChoicesSwatchOnly = [ + ORKColorChoice(color: colorOne, text: nil, detailText: nil, value: "choice_1" as NSString), + ORKColorChoice(color: colorTwo, text: nil, detailText: nil, value: "choice_2" as NSString), + ORKColorChoice(color: colorThree, text: nil, detailText: nil, value: "choice_3" as NSString), + ORKColorChoice(color: colorFour, text: nil, detailText: nil, value: "choice_4" as NSString), + ORKColorChoice(color: colorFive, text: nil, detailText: nil, value: "choice_5" as NSString), + ORKColorChoice(color: colorSix, text: nil, detailText: nil, value: "choice_6" as NSString), + ] + + let answerFormatSwatchOnly = ORKAnswerFormat.choiceAnswerFormat(with: .singleChoice, colorChoices: colorChoicesSwatchOnly) + let formItemSwatchOnly = ORKFormItem(identifier: String(describing: Identifier.colorChoiceQuestionFormItem), text: TaskListRowStrings.exampleQuestionText, answerFormat: answerFormatSwatchOnly) + + let formStepSwatchOnly = ORKFormStep(identifier: String(describing: Identifier.colorChoiceQuestionStepSwatchOnly), title: NSLocalizedString("Color Choice No Text", comment: ""), text: TaskListRowStrings.exampleDetailText) + + formStepSwatchOnly.formItems = [formItemSwatchOnly] + + return ORKOrderedTask(identifier: String(describing: Identifier.colorChoiceQuestionTask), steps: [formStep, formStepSwatchOnly]) + } + + private var familyHistoryTask: ORKTask { + + let familyHistoryStep = TaskListRowSteps.familyHistoryStepExample + + let completionStep = ORKCompletionStep(identifier: "FamilyHistoryCompletionStep") + completionStep.title = "All Done" + + return ORKOrderedTask(identifier: String(describing: Identifier.familyHistoryStep), steps: [familyHistoryStep, completionStep]) + } + } diff --git a/samples/ORKCatalog/ORKCatalog/Tasks/TaskListViewController.swift b/samples/ORKCatalog/ORKCatalog/Tasks/TaskListViewController.swift index 228b1f659c..5749562989 100644 --- a/samples/ORKCatalog/ORKCatalog/Tasks/TaskListViewController.swift +++ b/samples/ORKCatalog/ORKCatalog/Tasks/TaskListViewController.swift @@ -49,6 +49,11 @@ class TaskListViewController: UITableViewController, ORKTaskViewControllerDelega var waitStepViewController: ORKWaitStepViewController? var waitStepUpdateTimer: Timer? var waitStepProgress: CGFloat = 0.0 + + + // In-memory store for taskViewController restoration data + var restorationDataByTaskID: [String:Data] = [:] + // MARK: Types enum TableViewCellIdentifier: String { @@ -104,7 +109,6 @@ class TaskListViewController: UITableViewController, ORKTaskViewControllerDelega // Present the task view controller that the user asked for. let taskListRow = TaskListRow.sections[(indexPath as NSIndexPath).section].rows[(indexPath as NSIndexPath).row] - displayTaskViewController(taskListRow: taskListRow) } @@ -116,13 +120,25 @@ class TaskListViewController: UITableViewController, ORKTaskViewControllerDelega Passing `nil` for the `taskRunUUID` lets the task view controller generate an identifier for this run of the task. */ - let taskViewController = ORKTaskViewController(task: task, taskRun: nil) + var taskViewController = ORKTaskViewController(task: task, taskRun: nil) // Make sure we receive events from `taskViewController`. taskViewController.delegate = self // Assign a directory to store `taskViewController` output. taskViewController.outputDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! + if let restorationData = restorationDataByTaskID[task.identifier] { + + // we have data we can use to recreate the state of a previous taskViewController + taskViewController = ORKTaskViewController(task: task, restorationData: restorationData, delegate: self, error: nil) + } else { + + // making a brand new taskViewController + taskViewController = ORKTaskViewController(task: task, ongoingResult: nil, defaultResultSource: nil, delegate: self) + + // Assign a directory to store `taskViewController` output. + taskViewController.outputDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! + } /* We present the task directly, but it is also possible to use segues. The task property of the task view controller can be set any time before @@ -168,6 +184,28 @@ class TaskListViewController: UITableViewController, ORKTaskViewControllerDelega storePDFIfConsentTaskDetectedIn(taskViewController: taskViewController) taskResultFinishedCompletionHandler?(taskViewController.result) + switch (reason) { + case .saved: + saveRestorationData(for: taskViewController); + break; + + case .discarded: + /* If the user chose to discard the edits, we also remove previous restorationData. + This way, if the user launches the same task again, it'll behave like it's been + launched for the first time. + */ + resetRestorationData(for: taskViewController); + break; + + case .completed, .earlyTermination, .failed: + // For any other reason, we also reset restoration data + resetRestorationData(for: taskViewController); + break; + + default: + break; + } + taskViewController.dismiss(animated: true, completion: nil) } @@ -198,6 +236,10 @@ class TaskListViewController: UITableViewController, ORKTaskViewControllerDelega } } + + func taskViewControllerSupportsSaveAndRestore(_ taskViewController: ORKTaskViewController) -> Bool { + return true + } func delay(_ delay: Double, closure: @escaping () -> Void ) { let delayTime = DispatchTime.now() + delay @@ -223,6 +265,26 @@ class TaskListViewController: UITableViewController, ORKTaskViewControllerDelega self.waitStepUpdateTimer?.invalidate() } } + + /* Once saved in-memory, the user can later bring up the same task and start off where they left off. + This works only until the app relaunches since we don't save the restorationData to disk + */ + func saveRestorationData(for taskViewController: ORKTaskViewController) { + guard let taskID = taskViewController.task?.identifier else { + return + } + + restorationDataByTaskID[taskID] = taskViewController.restorationData + } + + func resetRestorationData(for taskViewController: ORKTaskViewController) { + guard let taskID = taskViewController.task?.identifier else { + return + } + + restorationDataByTaskID[taskID] = nil + } } + diff --git a/samples/ORKCatalog/ResearchKit-Shared.xcconfig b/samples/ORKCatalog/ResearchKit-Shared.xcconfig index e5bee9a39f..28c4ac0f2b 100644 --- a/samples/ORKCatalog/ResearchKit-Shared.xcconfig +++ b/samples/ORKCatalog/ResearchKit-Shared.xcconfig @@ -1,13 +1,40 @@ - // ResearchKit-Shared.xcconfig // ORKCatalog // // Copyright © 2021 researchkit.org. All rights reserved. // -ORK_CATALOG_VERSION_NUMBER = 3.0 +ORK_CATALOG_VERSION_NUMBER = 3.1 ORK_CATALOG_BUILD_NUMBER = $(ORK_CATALOG_BUILD_NUMBER_CI_$(CI)) // ORK_CATALOG_BUILD_NUMBER_CI_TRUE or ORK_CATALOG_BUILD_NUMBER_CI_ ORK_CATALOG_BUILD_NUMBER_CI_TRUE = $(CI_BUILD_NUMBER) ORK_CATALOG_BUILD_NUMBER_CI_FALSE = $(ORK_CATALOG_VERSION_NUMBER) // when not in CI, just assume the build number is our version number ORK_CATALOG_BUILD_NUMBER_CI_ = $(ORK_CATALOG_BUILD_NUMBER_CI_FALSE) + +ORK_FEATURE_HEALTHKIT_AUTHORIZATION = 1 +ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION = 1 + +// GCC Preprocessor definitions +ORK_FEATURE_HEALTHKIT_DEFINITIONS_GCC = $(ORK_FEATURE_HEALTHKIT_DEFINITIONS_GCC_$(ORK_FEATURE_HEALTHKIT_AUTHORIZATION)) +ORK_FEATURE_HEALTHKIT_DEFINITIONS_GCC_0 = ORK_FEATURE_HEALTHKIT_AUTHORIZATION=0 +ORK_FEATURE_HEALTHKIT_DEFINITIONS_GCC_1 = ORK_FEATURE_HEALTHKIT_AUTHORIZATION=1 + +ORK_FEATURE_CLLOCATIONMANAGER_DEFINITIONS_GCC = $(ORK_FEATURE_CLLOCATIONMANAGER_DEFINITIONS_GCC_$(ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION)) +ORK_FEATURE_CLLOCATIONMANAGER_DEFINITIONS_GCC_0 = ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION=0 +ORK_FEATURE_CLLOCATIONMANAGER_DEFINITIONS_GCC_1 = ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION=1 + +// Swift active compilation conditions +ORK_FEATURE_HEALTHKIT_DEFINITIONS_SWIFT = $(ORK_FEATURE_HEALTHKIT_DEFINITIONS_SWIFT_$(ORK_FEATURE_HEALTHKIT_AUTHORIZATION)) +ORK_FEATURE_HEALTHKIT_DEFINITIONS_SWIFT_0 = +ORK_FEATURE_HEALTHKIT_DEFINITIONS_SWIFT_1 = ORK_FEATURE_HEALTHKIT_AUTHORIZATION + +ORK_FEATURE_CLLOCATIONMANAGER_DEFINITIONS_SWIFT = $(ORK_FEATURE_CLLOCATIONMANAGER_DEFINITIONS_SWIFT_$(ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION)) +ORK_FEATURE_CLLOCATIONMANAGER_DEFINITIONS_SWIFT_0 = +ORK_FEATURE_CLLOCATIONMANAGER_DEFINITIONS_SWIFT_1 = ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION + + +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) $(ORK_FEATURE_HEALTHKIT_DEFINITIONS_GCC) $(ORK_FEATURE_CLLOCATIONMANAGER_DEFINITIONS_GCC) + +SWIFT_ACTIVE_COMPILATION_CONDITIONS = $(inherited) $(ORK_FEATURE_HEALTHKIT_DEFINITIONS_SWIFT) $(ORK_FEATURE_CLLOCATIONMANAGER_DEFINITIONS_SWIFT) + + diff --git a/samples/ORKCatalog/readme.md b/samples/ORKCatalog/readme.md index 8a6b2c3c31..eaf96e42f0 100644 --- a/samples/ORKCatalog/readme.md +++ b/samples/ORKCatalog/readme.md @@ -27,7 +27,6 @@ The *ORKCatalog* sample app is written in *Swift*. For more conceptual information about the *ResearchKit framework*, see the [ResearchKit Framework Programming Guide](http://researchkit.github.io/docs/docs/Overview/GuideOverview.html). - ## Build Requirements + Xcode 7.0.