From b15ff610f69fdf601ce8bb905c1f70e7a712b251 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Mon, 28 Oct 2024 12:41:55 -0700 Subject: [PATCH 01/12] fix: Bridging Objc HealthKit classes to swift --- ResearchKit/Common/ORKHealthAnswerFormat.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ResearchKit/Common/ORKHealthAnswerFormat.h b/ResearchKit/Common/ORKHealthAnswerFormat.h index 78368865a3..47656ed701 100644 --- a/ResearchKit/Common/ORKHealthAnswerFormat.h +++ b/ResearchKit/Common/ORKHealthAnswerFormat.h @@ -37,6 +37,9 @@ NS_ASSUME_NONNULL_BEGIN +@class ORKHealthKitCharacteristicTypeAnswerFormat; +@class ORKHealthKitCharacteristicTypeAnswerFormat; + /** An enumeration of biological sex options. */ From dcce2e006ca410237cd595d1297be290cd082f52 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Wed, 30 Oct 2024 18:26:11 -0700 Subject: [PATCH 02/12] Use ORKHealthKitQuantityTypeAnswerFormat --- ResearchKit/Common/ORKHealthAnswerFormat.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ResearchKit/Common/ORKHealthAnswerFormat.h b/ResearchKit/Common/ORKHealthAnswerFormat.h index 47656ed701..56d022c07c 100644 --- a/ResearchKit/Common/ORKHealthAnswerFormat.h +++ b/ResearchKit/Common/ORKHealthAnswerFormat.h @@ -38,7 +38,7 @@ NS_ASSUME_NONNULL_BEGIN @class ORKHealthKitCharacteristicTypeAnswerFormat; -@class ORKHealthKitCharacteristicTypeAnswerFormat; +@class ORKHealthKitQuantityTypeAnswerFormat; /** An enumeration of biological sex options. From 12d59051c182eac1136bc046ca09887705c2bb92 Mon Sep 17 00:00:00 2001 From: lmillan1 Date: Sat, 16 Nov 2024 11:58:11 -0800 Subject: [PATCH 03/12] remove iOS support for ResearchKit to fix watchOS build --- ResearchKit.xcodeproj/project.pbxproj | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/ResearchKit.xcodeproj/project.pbxproj b/ResearchKit.xcodeproj/project.pbxproj index 877a4d3302..5cbf9f1aee 100644 --- a/ResearchKit.xcodeproj/project.pbxproj +++ b/ResearchKit.xcodeproj/project.pbxproj @@ -11,7 +11,6 @@ isa = PBXAggregateTarget; buildConfigurationList = 0BC672D92BD9C52D005798AC /* Build configuration list for PBXAggregateTarget "ResearchKitAllTargets" */; buildPhases = ( - 0BC672E82BD9C69C005798AC /* ShellScript */, ); dependencies = ( 0BC672E02BD9C541005798AC /* PBXTargetDependency */, @@ -4728,7 +4727,6 @@ FF919A631E81D04D005C2A1E /* ORKSignatureResult.h in Headers */, 03EDD58024CA6B1D006245E9 /* ORKNotificationPermissionType.h in Headers */, 5192BF8B2AE1BBB0006E43FB /* ORKSecondaryTaskStep.h in Headers */, - 5192BF8C2AE1BF16006E43FB /* (null) in Headers */, 24A4DA141B8D1115009C797A /* ORKPasscodeStep.h in Headers */, FA7A9D331B0843A9005A2BEA /* ORKConsentSignatureFormatter.h in Headers */, 5DABE5AE24DA173F00570C57 /* ResearchKit_Prefix.pch in Headers */, @@ -6096,9 +6094,9 @@ IPHONEOS_DEPLOYMENT_TARGET = 13.0; MODULEMAP_PRIVATE_FILE = ResearchKit/ResearchKit_Private.modulemap; "OTHER_SWIFT_FLAGS[arch=*]" = "$(inherited) -runtime-compatibility-version none"; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator watchos watchsimulator xros xrsimulator"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; SUPPORTS_MACCATALYST = NO; - TARGETED_DEVICE_FAMILY = "1,2,4,7"; + TARGETED_DEVICE_FAMILY = "1,2,7"; WATCHOS_DEPLOYMENT_TARGET = 10.0; XROS_DEPLOYMENT_TARGET = 1.0; }; @@ -6110,9 +6108,9 @@ buildSettings = { IPHONEOS_DEPLOYMENT_TARGET = 13.0; MODULEMAP_PRIVATE_FILE = ResearchKit/ResearchKit_Private.modulemap; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator watchos watchsimulator xros xrsimulator"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; SUPPORTS_MACCATALYST = NO; - TARGETED_DEVICE_FAMILY = "1,2,4,7"; + TARGETED_DEVICE_FAMILY = "1,2,7"; WATCHOS_DEPLOYMENT_TARGET = 10.0; XROS_DEPLOYMENT_TARGET = 1.0; }; From 64fe2b36bb28cbbad875aa80a22b12e15f332ebd Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Fri, 6 Dec 2024 09:49:00 -0800 Subject: [PATCH 04/12] fix: Use accent color instead of hardcoded blue --- ResearchKitSwiftUI/ResearchFormStepContentView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ResearchKitSwiftUI/ResearchFormStepContentView.swift b/ResearchKitSwiftUI/ResearchFormStepContentView.swift index 83c5f70642..d36e9df21f 100644 --- a/ResearchKitSwiftUI/ResearchFormStepContentView.swift +++ b/ResearchKitSwiftUI/ResearchFormStepContentView.swift @@ -111,7 +111,7 @@ private struct ResearchFormStepButtonStyle: ButtonStyle { .foregroundStyle(.white) .frame(height: 50) .background( - isEnabled ? Color.blue : Color.blue.opacity(0.5), + isEnabled ? Color.accentColor : Color.accentColor.opacity(0.5), in: RoundedRectangle(cornerRadius: 12) ) #elseif os(visionOS) From 5e5c159a50dc7c279bbf640de5ccabe5ce8596e4 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Fri, 6 Dec 2024 12:52:33 -0800 Subject: [PATCH 05/12] feat: Use localization and add more strings --- .../en.lproj/ResearchKitSwiftUI.strings | 10 ++++++++- ResearchKitSwiftUI/NavigationalLayout.swift | 21 ++++++++++++++++++- ResearchKitSwiftUI/ResearchForm.swift | 18 +++++++++++++++- .../ResearchFormStepContentView.swift | 5 +++-- .../project.pbxproj | 4 ++-- .../Question Views/HeightQuestion.swift | 2 +- .../Question Views/ImageChoiceQuestion.swift | 6 +++--- .../Question Views/TextQuestion.swift | 2 +- .../Question Views/WeightQuestion.swift | 4 ++-- .../SwiftUIKit/DoneKeyboardToolbar.swift | 2 +- 10 files changed, 59 insertions(+), 15 deletions(-) diff --git a/ResearchKitSwiftUI/Localized/en.lproj/ResearchKitSwiftUI.strings b/ResearchKitSwiftUI/Localized/en.lproj/ResearchKitSwiftUI.strings index 9b9af639ac..68ed628d72 100644 --- a/ResearchKitSwiftUI/Localized/en.lproj/ResearchKitSwiftUI.strings +++ b/ResearchKitSwiftUI/Localized/en.lproj/ResearchKitSwiftUI.strings @@ -31,4 +31,12 @@ "BUTTON_CANCEL" = "Cancel"; "BUTTON_NEXT" = "Next"; "BUTTON_DONE" = "Done"; -"QUESTION_PROGRESS" = "%D OF %D"; +"BUTTON_CLEAR" = "Clear"; +"IMAGE_SELECT_ALL" = "Select all that apply"; +"IMAGE_TAP_SELECT" = "Tap to select"; +"NAVIGATION_TITLE" = "%D of %D"; +"PICKER_TAP" = "Tap Here"; +"QUESTION_HEIGHT" = "Select Height"; +"QUESTION_PROGRESS" = "Question %D of %D"; +"QUESTION_WEIGHT" = "Select Weight"; +"SLIDER_CONFIG_FOR" = "Slider for"; diff --git a/ResearchKitSwiftUI/NavigationalLayout.swift b/ResearchKitSwiftUI/NavigationalLayout.swift index 3db5a33e1b..43cdc444ac 100644 --- a/ResearchKitSwiftUI/NavigationalLayout.swift +++ b/ResearchKitSwiftUI/NavigationalLayout.swift @@ -71,7 +71,12 @@ struct NavigationalLayout: View { } content: { firstStep } - .navigationTitle("1 of \(steps.count)") + .navigationTitle( + navigationBarTitle( + firstValue: 1, + secondValue: steps.count + ) + ) .navigationDestination(for: Subview.ID.self) { subviewID in ResearchFormStepContentView( isLastStep: isLastStep(for: subviewID) @@ -103,6 +108,20 @@ struct NavigationalLayout: View { #endif } + private func navigationBarTitle( + firstValue: Int, + secondValue: Int + ) -> String { + String( + format: NSLocalizedString( + "NAVIGATION_TITLE", + comment: "" + ), + firstValue, + secondValue + ) + } + private func handle( formCompletion completion: ResearchFormCompletion, with result: ResearchFormResult diff --git a/ResearchKitSwiftUI/ResearchForm.swift b/ResearchKitSwiftUI/ResearchForm.swift index b2eee61edf..1be1dbb676 100644 --- a/ResearchKitSwiftUI/ResearchForm.swift +++ b/ResearchKitSwiftUI/ResearchForm.swift @@ -149,7 +149,10 @@ public struct ResearchFormStep: View { { let questionNumber = questionIndex + 1 Text( - "Question \(questionNumber) of \(questions.count)" + questionProgress( + firstValue: questionNumber, + secondValue: questions.count + ) ) .foregroundColor(.secondary) .font(.footnote) @@ -212,6 +215,19 @@ public struct ResearchFormStep: View { } } + private func questionProgress( + firstValue: Int, + secondValue: Int + ) -> String { + String( + format: NSLocalizedString( + "QUESTION_PROGRESS", + comment: "" + ), + firstValue, + secondValue + ) + } } extension ResearchFormStep where Header == EmptyView { diff --git a/ResearchKitSwiftUI/ResearchFormStepContentView.swift b/ResearchKitSwiftUI/ResearchFormStepContentView.swift index d36e9df21f..27bc1e3ddd 100644 --- a/ResearchKitSwiftUI/ResearchFormStepContentView.swift +++ b/ResearchKitSwiftUI/ResearchFormStepContentView.swift @@ -65,7 +65,7 @@ struct ResearchFormStepContentView: View { Button { onStepCompletion?(.discarded) } label: { - Text("Cancel") + Text("BUTTON_CANCEL") } } } @@ -78,7 +78,8 @@ struct ResearchFormStepContentView: View { onStepCompletion?(.saved(managedFormResult)) } } label: { - Text(isLastStep ? "Done" : "Next") + let buttonString: LocalizedStringKey = isLastStep ? "BUTTON_DONE" : "BUTTON_NEXT" + Text(buttonString) .fontWeight(.bold) .frame(maxWidth: maxWidthForDoneButton) .padding(.vertical, 8) diff --git a/ResearchKitSwiftUI/ResearchKitSwiftUI.xcodeproj/project.pbxproj b/ResearchKitSwiftUI/ResearchKitSwiftUI.xcodeproj/project.pbxproj index 6b8a8482fb..8bfc93b4db 100644 --- a/ResearchKitSwiftUI/ResearchKitSwiftUI.xcodeproj/project.pbxproj +++ b/ResearchKitSwiftUI/ResearchKitSwiftUI.xcodeproj/project.pbxproj @@ -642,7 +642,7 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator watchos watchsimulator xros xrsimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; - SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OBJC_BRIDGING_HEADER = ""; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,4,7"; @@ -690,7 +690,7 @@ SUPPORTED_PLATFORMS = "iphoneos iphonesimulator watchos watchsimulator xros xrsimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; - SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OBJC_BRIDGING_HEADER = ""; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,4,7"; diff --git a/ResearchKitSwiftUI/ResearchKitSwiftUI/Step Views/Question Views/HeightQuestion.swift b/ResearchKitSwiftUI/ResearchKitSwiftUI/Step Views/Question Views/HeightQuestion.swift index 57b3fc7eea..f6315a856f 100644 --- a/ResearchKitSwiftUI/ResearchKitSwiftUI/Step Views/Question Views/HeightQuestion.swift +++ b/ResearchKitSwiftUI/ResearchKitSwiftUI/Step Views/Question Views/HeightQuestion.swift @@ -169,7 +169,7 @@ public struct HeightQuestion: View { QuestionCard { Question(title: title, detail: detail) { HStack { - Text("Select Height") + Text("QUESTION_HEIGHT") .foregroundStyle(Color.primary) .frame(maxWidth: .infinity, alignment: .leading) Button { diff --git a/ResearchKitSwiftUI/ResearchKitSwiftUI/Step Views/Question Views/ImageChoiceQuestion.swift b/ResearchKitSwiftUI/ResearchKitSwiftUI/Step Views/Question Views/ImageChoiceQuestion.swift index 8749588a57..6324af98ea 100644 --- a/ResearchKitSwiftUI/ResearchKitSwiftUI/Step Views/Question Views/ImageChoiceQuestion.swift +++ b/ResearchKitSwiftUI/ResearchKitSwiftUI/Step Views/Question Views/ImageChoiceQuestion.swift @@ -418,7 +418,7 @@ public struct ImageChoiceQuestion: View { Text(strings.joined(separator: ", ")) .foregroundStyle(.primary) } else { - Text("Tap to select") + Text("IMAGE_TAP_SELECT") .foregroundStyle(.secondary) } } @@ -470,7 +470,7 @@ public struct ImageChoiceQuestion: View { @ViewBuilder private func multipleSelectionHeader() -> some View { #if !os(watchOS) - Text("SELECT ALL THAT APPLY") + Text("IMAGE_SELECT_ALL") .font(.caption) .fontWeight(.semibold) .foregroundStyle(.secondary) @@ -478,7 +478,7 @@ public struct ImageChoiceQuestion: View { .padding([.horizontal, .top]) Divider() #else - Text("SELECT ALL THAT APPLY") + Text("IMAGE_SELECT_ALL") .lineLimit(1) .minimumScaleFactor(0.5) .font(.caption2) diff --git a/ResearchKitSwiftUI/ResearchKitSwiftUI/Step Views/Question Views/TextQuestion.swift b/ResearchKitSwiftUI/ResearchKitSwiftUI/Step Views/Question Views/TextQuestion.swift index 70cd251bea..5e1463659c 100644 --- a/ResearchKitSwiftUI/ResearchKitSwiftUI/Step Views/Question Views/TextQuestion.swift +++ b/ResearchKitSwiftUI/ResearchKitSwiftUI/Step Views/Question Views/TextQuestion.swift @@ -190,7 +190,7 @@ public struct TextQuestion: View { Button { resolvedResult.wrappedValue = .none } label: { - Text("Clear") + Text("BUTTON_CLEAR") } } } diff --git a/ResearchKitSwiftUI/ResearchKitSwiftUI/Step Views/Question Views/WeightQuestion.swift b/ResearchKitSwiftUI/ResearchKitSwiftUI/Step Views/Question Views/WeightQuestion.swift index ce0c580079..97a933329f 100644 --- a/ResearchKitSwiftUI/ResearchKitSwiftUI/Step Views/Question Views/WeightQuestion.swift +++ b/ResearchKitSwiftUI/ResearchKitSwiftUI/Step Views/Question Views/WeightQuestion.swift @@ -234,7 +234,7 @@ public struct WeightQuestion: View { QuestionCard { Question(title: title, detail: detail) { HStack { - Text("Select Weight") + Text("QUESTION_WEIGHT") .foregroundStyle(Color.primary) .frame(maxWidth: .infinity, alignment: .leading) Button { @@ -512,7 +512,7 @@ struct WeightPickerView: View { .tag(i) } } label: { - Text("Tap Here") + Text("PICKER_TAP") } .pickerStyle(.wheel) } diff --git a/ResearchKitSwiftUI/ResearchKitSwiftUI/SwiftUIKit/DoneKeyboardToolbar.swift b/ResearchKitSwiftUI/ResearchKitSwiftUI/SwiftUIKit/DoneKeyboardToolbar.swift index f07e8fc690..2ad481765e 100644 --- a/ResearchKitSwiftUI/ResearchKitSwiftUI/SwiftUIKit/DoneKeyboardToolbar.swift +++ b/ResearchKitSwiftUI/ResearchKitSwiftUI/SwiftUIKit/DoneKeyboardToolbar.swift @@ -64,7 +64,7 @@ struct DoneKeyboardToolbar: ViewModifier { if condition() { Spacer() - Button("Done") { + Button("BUTTON_DONE") { action() } } From 27ce74ad240792efc62619d83818195fd7a2ad5a Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Fri, 6 Dec 2024 13:01:06 -0800 Subject: [PATCH 06/12] use computed property --- ResearchKitSwiftUI/ResearchFormStepContentView.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ResearchKitSwiftUI/ResearchFormStepContentView.swift b/ResearchKitSwiftUI/ResearchFormStepContentView.swift index 27bc1e3ddd..9aabb48657 100644 --- a/ResearchKitSwiftUI/ResearchFormStepContentView.swift +++ b/ResearchKitSwiftUI/ResearchFormStepContentView.swift @@ -42,6 +42,10 @@ struct ResearchFormStepContentView: View { @State private var doneButtonEnabled: Bool = true + private var buttonString: LocalizedStringKey { + isLastStep ? "BUTTON_DONE" : "BUTTON_NEXT" + } + init( isLastStep: Bool, onStepCompletion: ((ResearchFormCompletion) -> Void)? = nil, @@ -78,7 +82,6 @@ struct ResearchFormStepContentView: View { onStepCompletion?(.saved(managedFormResult)) } } label: { - let buttonString: LocalizedStringKey = isLastStep ? "BUTTON_DONE" : "BUTTON_NEXT" Text(buttonString) .fontWeight(.bold) .frame(maxWidth: maxWidthForDoneButton) From 77157d98fae1c15564144b0f3d099422546499cf Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Fri, 6 Dec 2024 16:10:40 -0800 Subject: [PATCH 07/12] replace more hardcoded blue --- .../ResearchKitSwiftUI/Slider/SliderValueForegroundStyle.swift | 2 +- .../Step Views/BodyItemIconForegroundStyle.swift | 2 +- .../Question Views/Supporting Views/StickyScrollView.swift | 2 +- .../Question Views/Supporting Views/TextChoiceOption.swift | 2 +- .../ResearchKitSwiftUI/Step Views/StepIconForegroundStyle.swift | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ResearchKitSwiftUI/ResearchKitSwiftUI/Slider/SliderValueForegroundStyle.swift b/ResearchKitSwiftUI/ResearchKitSwiftUI/Slider/SliderValueForegroundStyle.swift index 997b2bb0aa..1b89e30c9a 100644 --- a/ResearchKitSwiftUI/ResearchKitSwiftUI/Slider/SliderValueForegroundStyle.swift +++ b/ResearchKitSwiftUI/ResearchKitSwiftUI/Slider/SliderValueForegroundStyle.swift @@ -45,7 +45,7 @@ struct SliderValueForegroundStyle: ShapeStyle { #if os(visionOS) Color(.label) #else - .blue + Color.accentColor #endif } } diff --git a/ResearchKitSwiftUI/ResearchKitSwiftUI/Step Views/BodyItemIconForegroundStyle.swift b/ResearchKitSwiftUI/ResearchKitSwiftUI/Step Views/BodyItemIconForegroundStyle.swift index 3048b73e3b..fc30b85456 100644 --- a/ResearchKitSwiftUI/ResearchKitSwiftUI/Step Views/BodyItemIconForegroundStyle.swift +++ b/ResearchKitSwiftUI/ResearchKitSwiftUI/Step Views/BodyItemIconForegroundStyle.swift @@ -45,7 +45,7 @@ struct BodyItemIconForegroundStyle: ShapeStyle { #if os(visionOS) .white #else - .blue + Color.accentColor #endif } } diff --git a/ResearchKitSwiftUI/ResearchKitSwiftUI/Step Views/Question Views/Supporting Views/StickyScrollView.swift b/ResearchKitSwiftUI/ResearchKitSwiftUI/Step Views/Question Views/Supporting Views/StickyScrollView.swift index e2d420f0cd..706c990b11 100644 --- a/ResearchKitSwiftUI/ResearchKitSwiftUI/Step Views/Question Views/Supporting Views/StickyScrollView.swift +++ b/ResearchKitSwiftUI/ResearchKitSwiftUI/Step Views/Question Views/Supporting Views/StickyScrollView.swift @@ -301,7 +301,7 @@ struct ToolbarButton: ButtonStyle { } var buttonColor: Color { - return isDisabled ? .gray : .blue + return isDisabled ? .gray : .accentColor } public func makeBody(configuration: Configuration) -> some View { diff --git a/ResearchKitSwiftUI/ResearchKitSwiftUI/Step Views/Question Views/Supporting Views/TextChoiceOption.swift b/ResearchKitSwiftUI/ResearchKitSwiftUI/Step Views/Question Views/Supporting Views/TextChoiceOption.swift index d06977a0bb..2c6db0032f 100644 --- a/ResearchKitSwiftUI/ResearchKitSwiftUI/Step Views/Question Views/Supporting Views/TextChoiceOption.swift +++ b/ResearchKitSwiftUI/ResearchKitSwiftUI/Step Views/Question Views/Supporting Views/TextChoiceOption.swift @@ -56,7 +56,7 @@ struct TextChoiceOption: View { Image(systemName: isSelected ? "checkmark.circle.fill" : "circle") .imageScale(.large) - .foregroundColor(isSelected ? .blue : deselectedCheckmarkColor) + .foregroundColor(isSelected ? .accentColor : deselectedCheckmarkColor) .font(.body) } .padding(.vertical, 8) diff --git a/ResearchKitSwiftUI/ResearchKitSwiftUI/Step Views/StepIconForegroundStyle.swift b/ResearchKitSwiftUI/ResearchKitSwiftUI/Step Views/StepIconForegroundStyle.swift index 8e48a7d1a4..a6b432fc77 100644 --- a/ResearchKitSwiftUI/ResearchKitSwiftUI/Step Views/StepIconForegroundStyle.swift +++ b/ResearchKitSwiftUI/ResearchKitSwiftUI/Step Views/StepIconForegroundStyle.swift @@ -45,7 +45,7 @@ struct StepIconForegroundStyle: ShapeStyle { #if os(visionOS) .white #else - .blue + Color.accentColor #endif } From d122e8141f4a26f279094af87c5c5072079faec7 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Fri, 21 Feb 2025 10:36:49 -0800 Subject: [PATCH 08/12] feat: Swap in ResearchKit 3.1.1 --- ResearchKit.xcodeproj/project.pbxproj | 381 +++++++++--------- .../xcschemes/ResearchKitTests.xcscheme | 30 -- ResearchKit/Common/ORKAnswerFormat.h | 17 +- ResearchKit/Common/ORKAnswerFormat.m | 36 +- ResearchKit/Common/ORKAnswerFormat_Internal.h | 18 +- ResearchKit/Common/ORKAnswerFormat_Private.h | 7 + ResearchKit/Common/ORKBodyItem.m | 7 +- ResearchKit/Common/ORKBodyItem_Internal.h | 2 - ResearchKit/Common/ORKCollectionResult.h | 3 + .../Common/ORKCollectionResult_Private.h | 4 + ResearchKit/Common/ORKCompletionStep.h | 3 +- ResearchKit/Common/ORKDataCollectionManager.h | 3 - ResearchKit/Common/ORKDevice.h | 3 + ResearchKit/Common/ORKDevice_Private.h | 3 + .../Common/ORKEarlyTerminationConfiguration.h | 4 + ResearchKit/Common/ORKErrors.h | 5 + ResearchKit/Common/ORKFormStep.h | 6 +- ResearchKit/Common/ORKFormStep.m | 15 +- ResearchKit/Common/ORKHealthAnswerFormat.h | 10 +- ResearchKit/Common/ORKHelpers_Internal.h | 14 +- ResearchKit/Common/ORKHelpers_Private.h | 4 + ResearchKit/Common/ORKInstructionStep.h | 6 + .../Common/ORKMotionActivityPermissionType.m | 35 +- ResearchKit/Common/ORKOrderedTask.h | 5 + ResearchKit/Common/ORKOrderedTask.m | 24 +- ResearchKit/Common/ORKPageStep.h | 4 + ResearchKit/Common/ORKPermissionType.h | 3 +- ResearchKit/Common/ORKPermissionType.m | 4 + ResearchKit/Common/ORKQuestionResult.h | 17 +- ResearchKit/Common/ORKQuestionResult.m | 24 +- .../Common/ORKQuestionResult_Private.h | 11 +- ResearchKit/Common/ORKResult.h | 4 + ResearchKit/Common/ORKResult_Private.h | 3 + ResearchKit/Common/ORKSkin.h | 4 + ResearchKit/Common/ORKSkin_Private.h | 4 + ResearchKit/Common/ORKStep.h | 15 +- ResearchKit/Common/ORKStep_Private.h | 3 + ResearchKit/Common/ORKTask.h | 4 + ResearchKit/Common/ORKTypes.h | 4 + ResearchKit/Common/ORKVideoCaptureStep.h | 2 - ResearchKit/Common/ORKWebViewStep.h | 2 - .../ResearchKit/ResearchKit-Shared.xcconfig | 2 +- ResearchKit/Onboarding/ORKRegistrationStep.m | 9 +- .../API Collections/AnswerFormats.md | 2 +- ResearchKit/ResearchKit.h | 49 ++- ResearchKit/ResearchKit_Private.h | 25 +- ResearchKit/Stale/ORKQuestionStep.h | 4 + ResearchKit/Stale/ORKQuestionStep.m | 12 +- ResearchKit/Stale/ORKQuestionStep_Private.h | 6 +- .../ORKdBHLToneAudiometryStepViewController.m | 22 +- ResearchKitTests/ORKAnswerFormatTests.m | 2 - ResearchKitTests/ORKJSONSerializationTests.m | 4 +- .../ORKColorChoiceCellGroup.h | 3 - .../Control Views/ORKLocationSelectionView.m | 4 +- .../SwiftUI Views/SwiftUIViewFactory.swift | 2 +- .../SwiftUI Views/TextChoiceView.swift | 2 +- .../ORKRequestPermissionsStepViewController.m | 17 +- 57 files changed, 507 insertions(+), 411 deletions(-) diff --git a/ResearchKit.xcodeproj/project.pbxproj b/ResearchKit.xcodeproj/project.pbxproj index 5cbf9f1aee..7feb41e180 100644 --- a/ResearchKit.xcodeproj/project.pbxproj +++ b/ResearchKit.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 60; + objectVersion = 54; objects = { /* Begin PBXAggregateTarget section */ @@ -23,19 +23,19 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ - 00B1F7852241503900D022FE /* Speech.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00B1F7842241503900D022FE /* Speech.framework */; platformFilter = ios; }; - 00C2668E23022CD400337E0B /* ORKCustomStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 00C2668C23022CD400337E0B /* ORKCustomStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - 00C2668F23022CD400337E0B /* ORKCustomStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 00C2668D23022CD400337E0B /* ORKCustomStep.m */; platformFilter = ios; }; + 00B1F7852241503900D022FE /* Speech.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00B1F7842241503900D022FE /* Speech.framework */; }; + 00C2668E23022CD400337E0B /* ORKCustomStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 00C2668C23022CD400337E0B /* ORKCustomStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 00C2668F23022CD400337E0B /* ORKCustomStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 00C2668D23022CD400337E0B /* ORKCustomStep.m */; }; 03057F492518ECDC00C4EC5B /* ORKAudioStepViewControllerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 03057F482518ECDC00C4EC5B /* ORKAudioStepViewControllerTests.m */; }; - 031A0FC124CF4ECD000E4455 /* ORKSensorPermissionType.h in Headers */ = {isa = PBXBuildFile; fileRef = 031A0FBF24CF4ECD000E4455 /* ORKSensorPermissionType.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - 031A0FC224CF4ECD000E4455 /* ORKSensorPermissionType.m in Sources */ = {isa = PBXBuildFile; fileRef = 031A0FC024CF4ECD000E4455 /* ORKSensorPermissionType.m */; platformFilter = ios; }; + 031A0FC124CF4ECD000E4455 /* ORKSensorPermissionType.h in Headers */ = {isa = PBXBuildFile; fileRef = 031A0FBF24CF4ECD000E4455 /* ORKSensorPermissionType.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 031A0FC224CF4ECD000E4455 /* ORKSensorPermissionType.m in Sources */ = {isa = PBXBuildFile; fileRef = 031A0FC024CF4ECD000E4455 /* ORKSensorPermissionType.m */; }; 0324C1D825439E1800BBE77B /* ORKVideoInstructionStepViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0324C1D725439E1800BBE77B /* ORKVideoInstructionStepViewControllerTests.swift */; }; - 036B1E8D25351BAD008483DF /* ORKMotionActivityPermissionType.h in Headers */ = {isa = PBXBuildFile; fileRef = 036B1E8B25351BAD008483DF /* ORKMotionActivityPermissionType.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - 036B1E8E25351BAD008483DF /* ORKMotionActivityPermissionType.m in Sources */ = {isa = PBXBuildFile; fileRef = 036B1E8C25351BAD008483DF /* ORKMotionActivityPermissionType.m */; platformFilter = ios; }; - 03BD9EA3253E62A0008ADBE1 /* ORKBundleAsset.h in Headers */ = {isa = PBXBuildFile; fileRef = 03BD9EA1253E62A0008ADBE1 /* ORKBundleAsset.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - 03BD9EA4253E62A0008ADBE1 /* ORKBundleAsset.m in Sources */ = {isa = PBXBuildFile; fileRef = 03BD9EA2253E62A0008ADBE1 /* ORKBundleAsset.m */; platformFilter = ios; }; - 03EDD58024CA6B1D006245E9 /* ORKNotificationPermissionType.h in Headers */ = {isa = PBXBuildFile; fileRef = 03EDD57E24CA6B1D006245E9 /* ORKNotificationPermissionType.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - 03EDD58124CA6B1D006245E9 /* ORKNotificationPermissionType.m in Sources */ = {isa = PBXBuildFile; fileRef = 03EDD57F24CA6B1D006245E9 /* ORKNotificationPermissionType.m */; platformFilter = ios; }; + 036B1E8D25351BAD008483DF /* ORKMotionActivityPermissionType.h in Headers */ = {isa = PBXBuildFile; fileRef = 036B1E8B25351BAD008483DF /* ORKMotionActivityPermissionType.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 036B1E8E25351BAD008483DF /* ORKMotionActivityPermissionType.m in Sources */ = {isa = PBXBuildFile; fileRef = 036B1E8C25351BAD008483DF /* ORKMotionActivityPermissionType.m */; }; + 03BD9EA3253E62A0008ADBE1 /* ORKBundleAsset.h in Headers */ = {isa = PBXBuildFile; fileRef = 03BD9EA1253E62A0008ADBE1 /* ORKBundleAsset.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 03BD9EA4253E62A0008ADBE1 /* ORKBundleAsset.m in Sources */ = {isa = PBXBuildFile; fileRef = 03BD9EA2253E62A0008ADBE1 /* ORKBundleAsset.m */; }; + 03EDD58024CA6B1D006245E9 /* ORKNotificationPermissionType.h in Headers */ = {isa = PBXBuildFile; fileRef = 03EDD57E24CA6B1D006245E9 /* ORKNotificationPermissionType.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 03EDD58124CA6B1D006245E9 /* ORKNotificationPermissionType.m in Sources */ = {isa = PBXBuildFile; fileRef = 03EDD57F24CA6B1D006245E9 /* ORKNotificationPermissionType.m */; }; 0B0852742BD872C400149963 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 0B0852732BD872C400149963 /* PrivacyInfo.xcprivacy */; }; 0B0852762BD872D800149963 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 0B0852752BD872D800149963 /* PrivacyInfo.xcprivacy */; }; 0B0852782BD872EA00149963 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 0B0852772BD872EA00149963 /* PrivacyInfo.xcprivacy */; }; @@ -49,11 +49,11 @@ 0BD9B6112B75992800A64EF9 /* Sentence5.wav in Resources */ = {isa = PBXBuildFile; fileRef = 7118AC6320BF6A3A00D7A6BB /* Sentence5.wav */; }; 0BD9B6122B75992C00A64EF9 /* Sentence6.wav in Resources */ = {isa = PBXBuildFile; fileRef = 7118AC6220BF6A3A00D7A6BB /* Sentence6.wav */; }; 0BD9B6132B75992F00A64EF9 /* Sentence7.wav in Resources */ = {isa = PBXBuildFile; fileRef = 7118AC6020BF6A3900D7A6BB /* Sentence7.wav */; }; - 0BE9D5272947EA4900DA0625 /* ORKConsentDocument+ORKInstructionStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 0BE9D5252947EA4900DA0625 /* ORKConsentDocument+ORKInstructionStep.m */; platformFilter = ios; }; + 0BE9D5272947EA4900DA0625 /* ORKConsentDocument+ORKInstructionStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 0BE9D5252947EA4900DA0625 /* ORKConsentDocument+ORKInstructionStep.m */; }; 0BFD27562B8D1D3B00B540E8 /* ORKJSONSerializationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 51AF19562B583BBA00D3B399 /* ORKJSONSerializationTests.m */; }; - 10FF9ADB1B7BA78400ECB5B4 /* ORKOrderedTask_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 10FF9AD91B7BA78400ECB5B4 /* ORKOrderedTask_Private.h */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; }; - 12F339BF26A1F09A000665E4 /* ORKLocationPermissionType.m in Sources */ = {isa = PBXBuildFile; fileRef = 12F339BD26A1F09A000665E4 /* ORKLocationPermissionType.m */; platformFilter = ios; }; - 12F339C026A1F09A000665E4 /* ORKLocationPermissionType.h in Headers */ = {isa = PBXBuildFile; fileRef = 12F339BE26A1F09A000665E4 /* ORKLocationPermissionType.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + 10FF9ADB1B7BA78400ECB5B4 /* ORKOrderedTask_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 10FF9AD91B7BA78400ECB5B4 /* ORKOrderedTask_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 12F339BF26A1F09A000665E4 /* ORKLocationPermissionType.m in Sources */ = {isa = PBXBuildFile; fileRef = 12F339BD26A1F09A000665E4 /* ORKLocationPermissionType.m */; }; + 12F339C026A1F09A000665E4 /* ORKLocationPermissionType.h in Headers */ = {isa = PBXBuildFile; fileRef = 12F339BE26A1F09A000665E4 /* ORKLocationPermissionType.h */; settings = {ATTRIBUTES = (Public, ); }; }; 1483DBB5220125BE004C26B6 /* ORKActiveStepTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1483DBB4220125BE004C26B6 /* ORKActiveStepTests.swift */; }; 148E58BD227B36DB00EEF915 /* ORKCompletionStepViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 148E58BC227B36DB00EEF915 /* ORKCompletionStepViewControllerTests.swift */; }; 148E58C2227B753F00EEF915 /* ORKContinueButtonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 148E58C1227B753F00EEF915 /* ORKContinueButtonTests.swift */; }; @@ -72,26 +72,26 @@ 14F7AC8B2269035200D52F41 /* ORKStepViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14F7AC8A2269035200D52F41 /* ORKStepViewControllerTests.swift */; }; 22ED1847285290250052406B /* ORKAudiometryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 22ED1845285290250052406B /* ORKAudiometryTests.m */; }; 22ED1848285290250052406B /* ORKAudiometryTestData.plist in Resources */ = {isa = PBXBuildFile; fileRef = 22ED1846285290250052406B /* ORKAudiometryTestData.plist */; }; - 2429D5721BBB5397003A512F /* ORKRegistrationStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 2429D5701BBB5397003A512F /* ORKRegistrationStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - 2429D5731BBB5397003A512F /* ORKRegistrationStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 2429D5711BBB5397003A512F /* ORKRegistrationStep.m */; platformFilter = ios; }; - 242C9E051BBDFDAC0088B7F4 /* ORKVerificationStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 242C9E031BBDFDAC0088B7F4 /* ORKVerificationStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - 242C9E061BBDFDAC0088B7F4 /* ORKVerificationStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 242C9E041BBDFDAC0088B7F4 /* ORKVerificationStep.m */; platformFilter = ios; }; - 2433C9E31B9A506F0052D375 /* ORKKeychainWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 2433C9E11B9A506F0052D375 /* ORKKeychainWrapper.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + 2429D5721BBB5397003A512F /* ORKRegistrationStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 2429D5701BBB5397003A512F /* ORKRegistrationStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2429D5731BBB5397003A512F /* ORKRegistrationStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 2429D5711BBB5397003A512F /* ORKRegistrationStep.m */; }; + 242C9E051BBDFDAC0088B7F4 /* ORKVerificationStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 242C9E031BBDFDAC0088B7F4 /* ORKVerificationStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 242C9E061BBDFDAC0088B7F4 /* ORKVerificationStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 242C9E041BBDFDAC0088B7F4 /* ORKVerificationStep.m */; }; + 2433C9E31B9A506F0052D375 /* ORKKeychainWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 2433C9E11B9A506F0052D375 /* ORKKeychainWrapper.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2433C9E41B9A506F0052D375 /* ORKKeychainWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 2433C9E21B9A506F0052D375 /* ORKKeychainWrapper.m */; }; 244EFAD21BCEFD83001850D9 /* ORKAnswerFormat_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 244EFAD11BCEFD83001850D9 /* ORKAnswerFormat_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; 248604061B4C98760010C8A0 /* ORKAnswerFormatTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 248604051B4C98760010C8A0 /* ORKAnswerFormatTests.m */; }; - 2489F7B11D65214D008DEF20 /* ORKVideoCaptureStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 2489F7A91D65214D008DEF20 /* ORKVideoCaptureStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - 2489F7B21D65214D008DEF20 /* ORKVideoCaptureStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 2489F7AA1D65214D008DEF20 /* ORKVideoCaptureStep.m */; platformFilter = ios; }; - 24A4DA141B8D1115009C797A /* ORKPasscodeStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 24A4DA121B8D1115009C797A /* ORKPasscodeStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - 24A4DA151B8D1115009C797A /* ORKPasscodeStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 24A4DA131B8D1115009C797A /* ORKPasscodeStep.m */; platformFilter = ios; }; - 24BC5CEE1BC345D900846B43 /* ORKLoginStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 24BC5CEC1BC345D900846B43 /* ORKLoginStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - 24BC5CEF1BC345D900846B43 /* ORKLoginStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 24BC5CED1BC345D900846B43 /* ORKLoginStep.m */; platformFilter = ios; }; - 24C296751BD052F800B42EF1 /* ORKVerificationStep_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 24C296741BD052F800B42EF1 /* ORKVerificationStep_Internal.h */; platformFilter = ios; }; - 24C296771BD055B800B42EF1 /* ORKLoginStep_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 24C296761BD055B800B42EF1 /* ORKLoginStep_Internal.h */; platformFilter = ios; }; + 2489F7B11D65214D008DEF20 /* ORKVideoCaptureStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 2489F7A91D65214D008DEF20 /* ORKVideoCaptureStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2489F7B21D65214D008DEF20 /* ORKVideoCaptureStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 2489F7AA1D65214D008DEF20 /* ORKVideoCaptureStep.m */; }; + 24A4DA141B8D1115009C797A /* ORKPasscodeStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 24A4DA121B8D1115009C797A /* ORKPasscodeStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 24A4DA151B8D1115009C797A /* ORKPasscodeStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 24A4DA131B8D1115009C797A /* ORKPasscodeStep.m */; }; + 24BC5CEE1BC345D900846B43 /* ORKLoginStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 24BC5CEC1BC345D900846B43 /* ORKLoginStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 24BC5CEF1BC345D900846B43 /* ORKLoginStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 24BC5CED1BC345D900846B43 /* ORKLoginStep.m */; }; + 24C296751BD052F800B42EF1 /* ORKVerificationStep_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 24C296741BD052F800B42EF1 /* ORKVerificationStep_Internal.h */; }; + 24C296771BD055B800B42EF1 /* ORKLoginStep_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 24C296761BD055B800B42EF1 /* ORKLoginStep_Internal.h */; }; 2EBFE11D1AE1B32D00CB8254 /* ORKUIViewAccessibilityTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2EBFE11C1AE1B32D00CB8254 /* ORKUIViewAccessibilityTests.m */; }; 2EBFE1201AE1B74100CB8254 /* ORKVoiceEngineTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2EBFE11F1AE1B74100CB8254 /* ORKVoiceEngineTests.m */; }; - 511987C3246330CA004FC2C7 /* ORKRequestPermissionsStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 511987C1246330CA004FC2C7 /* ORKRequestPermissionsStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - 511987C4246330CA004FC2C7 /* ORKRequestPermissionsStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 511987C2246330CA004FC2C7 /* ORKRequestPermissionsStep.m */; platformFilter = ios; }; + 511987C3246330CA004FC2C7 /* ORKRequestPermissionsStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 511987C1246330CA004FC2C7 /* ORKRequestPermissionsStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 511987C4246330CA004FC2C7 /* ORKRequestPermissionsStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 511987C2246330CA004FC2C7 /* ORKRequestPermissionsStep.m */; }; 511BB024298DCCC200936EC0 /* ORKSpeechRecognitionStepViewController_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 511BB022298DCCC200936EC0 /* ORKSpeechRecognitionStepViewController_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; 511E8D622995C20E00A384A5 /* ORKEnvironmentSPLMeterStepViewController_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 511E8D602995C20E00A384A5 /* ORKEnvironmentSPLMeterStepViewController_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; 512741272B1557220045A449 /* ResearchKit.docc in Sources */ = {isa = PBXBuildFile; fileRef = 512741262B1557220045A449 /* ResearchKit.docc */; }; @@ -179,20 +179,20 @@ 5192BEED2AE043D3006E43FB /* ORKTimedWalkStepViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BEE92AE043D3006E43FB /* ORKTimedWalkStepViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5192BEEE2AE043D3006E43FB /* ORKTimedWalkContentView.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BEEA2AE043D3006E43FB /* ORKTimedWalkContentView.h */; }; 5192BEEF2AE043D3006E43FB /* ORKTimedWalkStepViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5192BEEB2AE043D3006E43FB /* ORKTimedWalkStepViewController.m */; }; - 5192BF502AE09673006E43FB /* ORKPredicateFormItemVisibilityRule.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BF4D2AE09672006E43FB /* ORKPredicateFormItemVisibilityRule.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - 5192BF512AE09673006E43FB /* ORKPredicateFormItemVisibilityRule.m in Sources */ = {isa = PBXBuildFile; fileRef = 5192BF4E2AE09672006E43FB /* ORKPredicateFormItemVisibilityRule.m */; platformFilter = ios; }; - 5192BF522AE09673006E43FB /* ORKPredicateFormItemVisibilityRule_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BF4F2AE09672006E43FB /* ORKPredicateFormItemVisibilityRule_Private.h */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; }; + 5192BF502AE09673006E43FB /* ORKPredicateFormItemVisibilityRule.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BF4D2AE09672006E43FB /* ORKPredicateFormItemVisibilityRule.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5192BF512AE09673006E43FB /* ORKPredicateFormItemVisibilityRule.m in Sources */ = {isa = PBXBuildFile; fileRef = 5192BF4E2AE09672006E43FB /* ORKPredicateFormItemVisibilityRule.m */; }; + 5192BF522AE09673006E43FB /* ORKPredicateFormItemVisibilityRule_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BF4F2AE09672006E43FB /* ORKPredicateFormItemVisibilityRule_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; 5192BF592AE09794006E43FB /* ORKFormItemVisibilityRule.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BF572AE09793006E43FB /* ORKFormItemVisibilityRule.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5192BF5A2AE09794006E43FB /* ORKFormItemVisibilityRule.m in Sources */ = {isa = PBXBuildFile; fileRef = 5192BF582AE09794006E43FB /* ORKFormItemVisibilityRule.m */; }; 5192BF5D2AE19036006E43FB /* frequency_dBSPL_AIRPODSPROV2.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5192BF5C2AE19036006E43FB /* frequency_dBSPL_AIRPODSPROV2.plist */; }; 5192BF7D2AE1A87D006E43FB /* retspl_AIRPODSPROV2.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5192BF7C2AE1A87D006E43FB /* retspl_AIRPODSPROV2.plist */; }; 5192BF7F2AE1A9BA006E43FB /* volume_curve_AIRPODSPROV2.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5192BF7E2AE1A9BA006E43FB /* volume_curve_AIRPODSPROV2.plist */; }; - 5192BF842AE1BA47006E43FB /* ORKFrontFacingCameraStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BF802AE1BA47006E43FB /* ORKFrontFacingCameraStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - 5192BF852AE1BA47006E43FB /* ORKFrontFacingCameraStepResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 5192BF812AE1BA47006E43FB /* ORKFrontFacingCameraStepResult.m */; platformFilter = ios; }; - 5192BF862AE1BA47006E43FB /* ORKFrontFacingCameraStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 5192BF822AE1BA47006E43FB /* ORKFrontFacingCameraStep.m */; platformFilter = ios; }; - 5192BF872AE1BA47006E43FB /* ORKFrontFacingCameraStepResult.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BF832AE1BA47006E43FB /* ORKFrontFacingCameraStepResult.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - 5192BF8A2AE1BBB0006E43FB /* ORKSecondaryTaskStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 5192BF882AE1BBB0006E43FB /* ORKSecondaryTaskStep.m */; platformFilter = ios; }; - 5192BF8B2AE1BBB0006E43FB /* ORKSecondaryTaskStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BF892AE1BBB0006E43FB /* ORKSecondaryTaskStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + 5192BF842AE1BA47006E43FB /* ORKFrontFacingCameraStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BF802AE1BA47006E43FB /* ORKFrontFacingCameraStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5192BF852AE1BA47006E43FB /* ORKFrontFacingCameraStepResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 5192BF812AE1BA47006E43FB /* ORKFrontFacingCameraStepResult.m */; }; + 5192BF862AE1BA47006E43FB /* ORKFrontFacingCameraStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 5192BF822AE1BA47006E43FB /* ORKFrontFacingCameraStep.m */; }; + 5192BF872AE1BA47006E43FB /* ORKFrontFacingCameraStepResult.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BF832AE1BA47006E43FB /* ORKFrontFacingCameraStepResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 5192BF8A2AE1BBB0006E43FB /* ORKSecondaryTaskStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 5192BF882AE1BBB0006E43FB /* ORKSecondaryTaskStep.m */; }; + 5192BF8B2AE1BBB0006E43FB /* ORKSecondaryTaskStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BF892AE1BBB0006E43FB /* ORKSecondaryTaskStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5192BF8F2AE1C051006E43FB /* ORKAgePicker.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BF8D2AE1C051006E43FB /* ORKAgePicker.h */; }; 5192BF902AE1C051006E43FB /* ORKAgePicker.m in Sources */ = {isa = PBXBuildFile; fileRef = 5192BF8E2AE1C051006E43FB /* ORKAgePicker.m */; }; 5192BF932AE1C2F8006E43FB /* ORKChoiceViewCell+ORKColorChoice.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BF912AE1C2F7006E43FB /* ORKChoiceViewCell+ORKColorChoice.h */; }; @@ -203,19 +203,19 @@ 5192BF9A2AE1D8A4006E43FB /* ORKChoiceViewCell+ORKTextChoice.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B8444642A79C25000292DEA /* ORKChoiceViewCell+ORKTextChoice.m */; }; 5192BF9D2AE1DE62006E43FB /* ORKColorChoiceCellGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 5192BF9B2AE1DE62006E43FB /* ORKColorChoiceCellGroup.m */; }; 5192BF9E2AE1DE62006E43FB /* ORKColorChoiceCellGroup.h in Headers */ = {isa = PBXBuildFile; fileRef = 5192BF9C2AE1DE62006E43FB /* ORKColorChoiceCellGroup.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 5192BFA12AEAD7B5006E43FB /* Artwork.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 861610BF1A8D8EDD00245F7A /* Artwork.xcassets */; platformFilter = ios; }; - 519CE8222C6582BE003BB584 /* ORKHealthCondition.m in Sources */ = {isa = PBXBuildFile; fileRef = 519CE8162C6582BD003BB584 /* ORKHealthCondition.m */; platformFilter = ios; }; - 519CE8232C6582BE003BB584 /* ORKFamilyHistoryStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 519CE8172C6582BD003BB584 /* ORKFamilyHistoryStep.m */; platformFilter = ios; }; - 519CE8242C6582BE003BB584 /* ORKHealthCondition.h in Headers */ = {isa = PBXBuildFile; fileRef = 519CE8182C6582BD003BB584 /* ORKHealthCondition.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - 519CE8252C6582BE003BB584 /* ORKFamilyHistoryResult.h in Headers */ = {isa = PBXBuildFile; fileRef = 519CE8192C6582BD003BB584 /* ORKFamilyHistoryResult.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - 519CE8262C6582BE003BB584 /* ORKRelatedPerson.m in Sources */ = {isa = PBXBuildFile; fileRef = 519CE81A2C6582BD003BB584 /* ORKRelatedPerson.m */; platformFilter = ios; }; - 519CE8272C6582BE003BB584 /* ORKRelativeGroup.h in Headers */ = {isa = PBXBuildFile; fileRef = 519CE81B2C6582BD003BB584 /* ORKRelativeGroup.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - 519CE8282C6582BE003BB584 /* ORKRelativeGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 519CE81C2C6582BD003BB584 /* ORKRelativeGroup.m */; platformFilter = ios; }; - 519CE8292C6582BE003BB584 /* ORKConditionStepConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = 519CE81D2C6582BE003BB584 /* ORKConditionStepConfiguration.m */; platformFilter = ios; }; - 519CE82A2C6582BE003BB584 /* ORKConditionStepConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = 519CE81E2C6582BE003BB584 /* ORKConditionStepConfiguration.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - 519CE82B2C6582BE003BB584 /* ORKFamilyHistoryStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 519CE81F2C6582BE003BB584 /* ORKFamilyHistoryStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - 519CE82C2C6582BE003BB584 /* ORKRelatedPerson.h in Headers */ = {isa = PBXBuildFile; fileRef = 519CE8202C6582BE003BB584 /* ORKRelatedPerson.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - 519CE82D2C6582BE003BB584 /* ORKFamilyHistoryResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 519CE8212C6582BE003BB584 /* ORKFamilyHistoryResult.m */; platformFilter = ios; }; + 5192BFA12AEAD7B5006E43FB /* Artwork.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 861610BF1A8D8EDD00245F7A /* Artwork.xcassets */; }; + 519CE8222C6582BE003BB584 /* ORKHealthCondition.m in Sources */ = {isa = PBXBuildFile; fileRef = 519CE8162C6582BD003BB584 /* ORKHealthCondition.m */; }; + 519CE8232C6582BE003BB584 /* ORKFamilyHistoryStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 519CE8172C6582BD003BB584 /* ORKFamilyHistoryStep.m */; }; + 519CE8242C6582BE003BB584 /* ORKHealthCondition.h in Headers */ = {isa = PBXBuildFile; fileRef = 519CE8182C6582BD003BB584 /* ORKHealthCondition.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 519CE8252C6582BE003BB584 /* ORKFamilyHistoryResult.h in Headers */ = {isa = PBXBuildFile; fileRef = 519CE8192C6582BD003BB584 /* ORKFamilyHistoryResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 519CE8262C6582BE003BB584 /* ORKRelatedPerson.m in Sources */ = {isa = PBXBuildFile; fileRef = 519CE81A2C6582BD003BB584 /* ORKRelatedPerson.m */; }; + 519CE8272C6582BE003BB584 /* ORKRelativeGroup.h in Headers */ = {isa = PBXBuildFile; fileRef = 519CE81B2C6582BD003BB584 /* ORKRelativeGroup.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 519CE8282C6582BE003BB584 /* ORKRelativeGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 519CE81C2C6582BD003BB584 /* ORKRelativeGroup.m */; }; + 519CE8292C6582BE003BB584 /* ORKConditionStepConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = 519CE81D2C6582BE003BB584 /* ORKConditionStepConfiguration.m */; }; + 519CE82A2C6582BE003BB584 /* ORKConditionStepConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = 519CE81E2C6582BE003BB584 /* ORKConditionStepConfiguration.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 519CE82B2C6582BE003BB584 /* ORKFamilyHistoryStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 519CE81F2C6582BE003BB584 /* ORKFamilyHistoryStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 519CE82C2C6582BE003BB584 /* ORKRelatedPerson.h in Headers */ = {isa = PBXBuildFile; fileRef = 519CE8202C6582BE003BB584 /* ORKRelatedPerson.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 519CE82D2C6582BE003BB584 /* ORKFamilyHistoryResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 519CE8212C6582BE003BB584 /* ORKFamilyHistoryResult.m */; }; 519CE8352C658617003BB584 /* ORKFamilyHistoryStepViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 519CE8302C658617003BB584 /* ORKFamilyHistoryStepViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 519CE8382C658617003BB584 /* ORKFamilyHistoryStepViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 519CE8332C658617003BB584 /* ORKFamilyHistoryStepViewController.m */; }; 519CE8412C658654003BB584 /* ORKFamilyHistoryRelatedPersonCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 519CE83B2C658653003BB584 /* ORKFamilyHistoryRelatedPersonCell.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -226,12 +226,13 @@ 519CE8462C658654003BB584 /* ORKFamilyHistoryTableHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 519CE8402C658654003BB584 /* ORKFamilyHistoryTableHeaderView.m */; }; 519CE85C2C6AD858003BB584 /* ORKFamilyHistoryStepViewController_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 519CE85B2C6AD858003BB584 /* ORKFamilyHistoryStepViewController_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; 519CE85F2C6BC1DB003BB584 /* ORKFamilyHistoryResultTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519CE85E2C6BC1DB003BB584 /* ORKFamilyHistoryResultTests.swift */; }; - 51A11F172BD08D5E0060C07E /* HKSample+ORKJSONDictionary.h in Headers */ = {isa = PBXBuildFile; fileRef = 51A11F132BD08D5D0060C07E /* HKSample+ORKJSONDictionary.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - 51A11F182BD08D5E0060C07E /* CMMotionActivity+ORKJSONDictionary.m in Sources */ = {isa = PBXBuildFile; fileRef = 51A11F142BD08D5D0060C07E /* CMMotionActivity+ORKJSONDictionary.m */; platformFilter = ios; }; - 51A11F192BD08D5E0060C07E /* HKSample+ORKJSONDictionary.m in Sources */ = {isa = PBXBuildFile; fileRef = 51A11F152BD08D5D0060C07E /* HKSample+ORKJSONDictionary.m */; platformFilter = ios; }; - 51A11F1A2BD08D5E0060C07E /* CMMotionActivity+ORKJSONDictionary.h in Headers */ = {isa = PBXBuildFile; fileRef = 51A11F162BD08D5E0060C07E /* CMMotionActivity+ORKJSONDictionary.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + 51A11F172BD08D5E0060C07E /* HKSample+ORKJSONDictionary.h in Headers */ = {isa = PBXBuildFile; fileRef = 51A11F132BD08D5D0060C07E /* HKSample+ORKJSONDictionary.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 51A11F182BD08D5E0060C07E /* CMMotionActivity+ORKJSONDictionary.m in Sources */ = {isa = PBXBuildFile; fileRef = 51A11F142BD08D5D0060C07E /* CMMotionActivity+ORKJSONDictionary.m */; }; + 51A11F192BD08D5E0060C07E /* HKSample+ORKJSONDictionary.m in Sources */ = {isa = PBXBuildFile; fileRef = 51A11F152BD08D5D0060C07E /* HKSample+ORKJSONDictionary.m */; }; + 51A11F1A2BD08D5E0060C07E /* CMMotionActivity+ORKJSONDictionary.h in Headers */ = {isa = PBXBuildFile; fileRef = 51A11F162BD08D5E0060C07E /* CMMotionActivity+ORKJSONDictionary.h */; settings = {ATTRIBUTES = (Public, ); }; }; 51A11F222BD152660060C07E /* ORKActiveStepCustomView.h in Headers */ = {isa = PBXBuildFile; fileRef = 51A11F202BD152660060C07E /* ORKActiveStepCustomView.h */; settings = {ATTRIBUTES = (Public, ); }; }; 51A11F232BD152660060C07E /* ORKActiveStepCustomView.m in Sources */ = {isa = PBXBuildFile; fileRef = 51A11F212BD152660060C07E /* ORKActiveStepCustomView.m */; }; + 51A11F242BD1548C0060C07E /* UIImageView+ResearchKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B9CC5662A68C02C00080E29 /* UIImageView+ResearchKit.m */; }; 51AEAAB22B744AC200F4D107 /* ORKQuestionStepViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 51AEAAA82B743FA500F4D107 /* ORKQuestionStepViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 51AEAAB32B744ACC00F4D107 /* ORKQuestionStepViewController_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 51AEAAA72B743FA500F4D107 /* ORKQuestionStepViewController_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; 51AEAAB42B744B5700F4D107 /* ORKQuestionStepViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 51AEAAA62B743FA500F4D107 /* ORKQuestionStepViewController.m */; }; @@ -240,15 +241,15 @@ 51AF19582B583BBA00D3B399 /* ORKDataCollectionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 51AF19532B583BB900D3B399 /* ORKDataCollectionTests.m */; }; 51AF19592B583BBA00D3B399 /* samples.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 51AF19542B583BB900D3B399 /* samples.bundle */; }; 51AF19662B583D0F00D3B399 /* ORKESerialization.m in Sources */ = {isa = PBXBuildFile; fileRef = 51AF19552B583BB900D3B399 /* ORKESerialization.m */; }; - 51AF1B152B67F30500D3B399 /* ORKSignatureFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = 51AF1B132B67F30500D3B399 /* ORKSignatureFormatter.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - 51AF1B162B67F30500D3B399 /* ORKSignatureFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 51AF1B142B67F30500D3B399 /* ORKSignatureFormatter.m */; platformFilter = ios; }; - 51AF1B202B683C3400D3B399 /* ORKWebViewStepResult_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 51AF1B1F2B683C3400D3B399 /* ORKWebViewStepResult_Private.h */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; }; + 51AF1B152B67F30500D3B399 /* ORKSignatureFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = 51AF1B132B67F30500D3B399 /* ORKSignatureFormatter.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 51AF1B162B67F30500D3B399 /* ORKSignatureFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 51AF1B142B67F30500D3B399 /* ORKSignatureFormatter.m */; }; + 51AF1B202B683C3400D3B399 /* ORKWebViewStepResult_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 51AF1B1F2B683C3400D3B399 /* ORKWebViewStepResult_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; 51AF1B232B69803A00D3B399 /* Window.wav in Resources */ = {isa = PBXBuildFile; fileRef = 51AF1B212B69803A00D3B399 /* Window.wav */; }; 51AF1B242B69803A00D3B399 /* Noise.wav in Resources */ = {isa = PBXBuildFile; fileRef = 51AF1B222B69803A00D3B399 /* Noise.wav */; }; 51B76DFB2CB5B5A30061698A /* ResearchKitActiveTask.docc in Sources */ = {isa = PBXBuildFile; fileRef = 51B76DFA2CB5B5A30061698A /* ResearchKitActiveTask.docc */; }; 51B76DFD2CB5C73C0061698A /* ResearchKitUI.docc in Sources */ = {isa = PBXBuildFile; fileRef = 51B76DFC2CB5C73C0061698A /* ResearchKitUI.docc */; }; - 51B94DBD2B311E810039B0E7 /* CLLocationManager+ResearchKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 51B94DBC2B311E810039B0E7 /* CLLocationManager+ResearchKit.h */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; }; - 51B94DC72B3254FE0039B0E7 /* CLLocationManager+ResearchKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 51B94DC62B3254FD0039B0E7 /* CLLocationManager+ResearchKit.m */; platformFilter = ios; }; + 51B94DBD2B311E810039B0E7 /* CLLocationManager+ResearchKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 51B94DBC2B311E810039B0E7 /* CLLocationManager+ResearchKit.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 51B94DC72B3254FE0039B0E7 /* CLLocationManager+ResearchKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 51B94DC62B3254FD0039B0E7 /* CLLocationManager+ResearchKit.m */; }; 51BD8FC929D60FB60001D54E /* frequency_dBSPL_AIRPODSMAX.plist in Resources */ = {isa = PBXBuildFile; fileRef = E293655D25757CC700092A7C /* frequency_dBSPL_AIRPODSMAX.plist */; }; 51BD8FCA29D60FB60001D54E /* frequency_dBSPL_EARPODS.plist in Resources */ = {isa = PBXBuildFile; fileRef = 713D4B1B20FE5464002BE28D /* frequency_dBSPL_EARPODS.plist */; }; 51BD8FCB29D60FB60001D54E /* frequency_dBSPL_AIRPODS.plist in Resources */ = {isa = PBXBuildFile; fileRef = 71B7B4D820AA91D400C5768A /* frequency_dBSPL_AIRPODS.plist */; }; @@ -267,12 +268,12 @@ 51CB80D62AFEBE7E00A1F410 /* ORKFormStepViewControllerConditionalFormItemsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51CB80D52AFEBE7E00A1F410 /* ORKFormStepViewControllerConditionalFormItemsTests.swift */; }; 51CB80D82AFEBEC600A1F410 /* ORKPredicateFormItemVisibilityRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51CB80D72AFEBEC600A1F410 /* ORKPredicateFormItemVisibilityRuleTests.swift */; }; 51CB80DA2AFEBF3800A1F410 /* ORKFormItemVisibilityRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51CB80D92AFEBF3800A1F410 /* ORKFormItemVisibilityRuleTests.swift */; }; - 51E03D6324919711008F8406 /* ORKPermissionType.h in Headers */ = {isa = PBXBuildFile; fileRef = 51E03D6124919711008F8406 /* ORKPermissionType.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - 51E03D6424919711008F8406 /* ORKPermissionType.m in Sources */ = {isa = PBXBuildFile; fileRef = 51E03D6224919711008F8406 /* ORKPermissionType.m */; platformFilter = ios; }; - 51E03D682491A601008F8406 /* ORKHealthKitPermissionType.h in Headers */ = {isa = PBXBuildFile; fileRef = 51E03D662491A601008F8406 /* ORKHealthKitPermissionType.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - 51E03D692491A601008F8406 /* ORKHealthKitPermissionType.m in Sources */ = {isa = PBXBuildFile; fileRef = 51E03D672491A601008F8406 /* ORKHealthKitPermissionType.m */; platformFilter = ios; }; - 51EB9A532B8408D50064A515 /* ORKInstructionStepHTMLFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = 51EB9A512B8408D50064A515 /* ORKInstructionStepHTMLFormatter.h */; platformFilter = ios; }; - 51EB9A542B8408D50064A515 /* ORKInstructionStepHTMLFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 51EB9A522B8408D50064A515 /* ORKInstructionStepHTMLFormatter.m */; platformFilter = ios; }; + 51E03D6324919711008F8406 /* ORKPermissionType.h in Headers */ = {isa = PBXBuildFile; fileRef = 51E03D6124919711008F8406 /* ORKPermissionType.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 51E03D6424919711008F8406 /* ORKPermissionType.m in Sources */ = {isa = PBXBuildFile; fileRef = 51E03D6224919711008F8406 /* ORKPermissionType.m */; }; + 51E03D682491A601008F8406 /* ORKHealthKitPermissionType.h in Headers */ = {isa = PBXBuildFile; fileRef = 51E03D662491A601008F8406 /* ORKHealthKitPermissionType.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 51E03D692491A601008F8406 /* ORKHealthKitPermissionType.m in Sources */ = {isa = PBXBuildFile; fileRef = 51E03D672491A601008F8406 /* ORKHealthKitPermissionType.m */; }; + 51EB9A532B8408D50064A515 /* ORKInstructionStepHTMLFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = 51EB9A512B8408D50064A515 /* ORKInstructionStepHTMLFormatter.h */; }; + 51EB9A542B8408D50064A515 /* ORKInstructionStepHTMLFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = 51EB9A522B8408D50064A515 /* ORKInstructionStepHTMLFormatter.m */; }; 51EB9A5E2B8D3BA70064A515 /* ORKInstructionStepHTMLFormatterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 51EB9A5D2B8D3BA70064A515 /* ORKInstructionStepHTMLFormatterTests.m */; }; 51F716C1297E288A00D8ACF7 /* ORKNormalizedReactionTimeStimulusView.m in Sources */ = {isa = PBXBuildFile; fileRef = 51F716B7297E288900D8ACF7 /* ORKNormalizedReactionTimeStimulusView.m */; }; 51F716C2297E288A00D8ACF7 /* ORKNormalizedReactionTimeStimulusView.h in Headers */ = {isa = PBXBuildFile; fileRef = 51F716B8297E288900D8ACF7 /* ORKNormalizedReactionTimeStimulusView.h */; }; @@ -284,7 +285,7 @@ 51F716C8297E288A00D8ACF7 /* ORKNormalizedReactionTimeStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 51F716BE297E288900D8ACF7 /* ORKNormalizedReactionTimeStep.m */; }; 51F716C9297E288A00D8ACF7 /* ORKNormalizedReactionTimeViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 51F716BF297E288A00D8ACF7 /* ORKNormalizedReactionTimeViewController.h */; settings = {ATTRIBUTES = (Private, ); }; }; 51F716CA297E288A00D8ACF7 /* ORKNormalizedReactionTimeContentView.h in Headers */ = {isa = PBXBuildFile; fileRef = 51F716C0297E288A00D8ACF7 /* ORKNormalizedReactionTimeContentView.h */; }; - 51F716CB297E2A1100D8ACF7 /* ORKConsentDocument+ORKInstructionStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 0BE9D5242947EA4900DA0625 /* ORKConsentDocument+ORKInstructionStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + 51F716CB297E2A1100D8ACF7 /* ORKConsentDocument+ORKInstructionStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 0BE9D5242947EA4900DA0625 /* ORKConsentDocument+ORKInstructionStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; 51F716D1297E2CC400D8ACF7 /* ORKConsentLearnMoreViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 51F716CD297E2CB600D8ACF7 /* ORKConsentLearnMoreViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 51F716D3297E2FB600D8ACF7 /* ORKConsentLearnMoreViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 51F716CE297E2CB600D8ACF7 /* ORKConsentLearnMoreViewController.m */; }; 51F716EB2981B49000D8ACF7 /* ORKSpeechInNoiseStepViewController_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 51F716E82981AF1200D8ACF7 /* ORKSpeechInNoiseStepViewController_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -293,7 +294,7 @@ 5D04884C25EF4CC30006C68B /* ORKQuestionStep_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D04884B25EF4CC30006C68B /* ORKQuestionStep_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; 5D04885725F19A7A0006C68B /* ORKDevice.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D04885525F19A7A0006C68B /* ORKDevice.h */; settings = {ATTRIBUTES = (Private, ); }; }; 5D04885825F19A7A0006C68B /* ORKDevice.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D04885625F19A7A0006C68B /* ORKDevice.m */; }; - 5D04885C25F1B3AB0006C68B /* ORKDevice_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D04885B25F1B3AB0006C68B /* ORKDevice_Private.h */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; }; + 5D04885C25F1B3AB0006C68B /* ORKDevice_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D04885B25F1B3AB0006C68B /* ORKDevice_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; 5D43C5D424255675006F4084 /* ORKBodyItem_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D43C5D324255675006F4084 /* ORKBodyItem_Internal.h */; settings = {ATTRIBUTES = (Private, ); }; }; 5DABE5AE24DA173F00570C57 /* ResearchKit_Prefix.pch in Headers */ = {isa = PBXBuildFile; fileRef = 5DABE5AD24DA16E600570C57 /* ResearchKit_Prefix.pch */; }; 5E6AB7DF2BC86900009ED0D5 /* ORKTaskViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E6AB7DE2BC86900009ED0D5 /* ORKTaskViewControllerTests.swift */; }; @@ -302,26 +303,26 @@ 5EB91CC82BCE2EFD00BBF23E /* splMeter_sensitivity_offset.plist in Resources */ = {isa = PBXBuildFile; fileRef = 71F3B27F21001DEC00FB1C41 /* splMeter_sensitivity_offset.plist */; }; 5EB91CC92BCE2F1D00BBF23E /* SentencesList.txt in Resources */ = {isa = PBXBuildFile; fileRef = BA22F76C20C4F884006E6E11 /* SentencesList.txt */; }; 622774402C5CF7DC00F2E741 /* retspl_dBFS_AIRPODSPROV2.plist in Resources */ = {isa = PBXBuildFile; fileRef = 6227743F2C5CF7DC00F2E741 /* retspl_dBFS_AIRPODSPROV2.plist */; }; - 714080DB235FD14700281E04 /* ResearchKit.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 714080D9235FD14700281E04 /* ResearchKit.stringsdict */; platformFilter = ios; }; + 714080DB235FD14700281E04 /* ResearchKit.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 714080D9235FD14700281E04 /* ResearchKit.stringsdict */; }; 714151D0225C4A23002CA33B /* ORKPasscodeViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14A92C6922444F93007547F2 /* ORKPasscodeViewControllerTests.swift */; }; 7141EA2222EFBC0C00650145 /* ORKLoggingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7141EA2122EFBC0C00650145 /* ORKLoggingTests.m */; }; - 7167D028231B1EAA00AAB4DD /* ORKFormStep_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 7167D027231B1EAA00AAB4DD /* ORKFormStep_Internal.h */; platformFilter = ios; }; - 8419D66E1FB73CC80088D7E5 /* ORKWebViewStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 8419D66C1FB73CC80088D7E5 /* ORKWebViewStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - 8419D66F1FB73CC80088D7E5 /* ORKWebViewStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 8419D66D1FB73CC80088D7E5 /* ORKWebViewStep.m */; platformFilter = ios; }; + 7167D028231B1EAA00AAB4DD /* ORKFormStep_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 7167D027231B1EAA00AAB4DD /* ORKFormStep_Internal.h */; }; + 8419D66E1FB73CC80088D7E5 /* ORKWebViewStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 8419D66C1FB73CC80088D7E5 /* ORKWebViewStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8419D66F1FB73CC80088D7E5 /* ORKWebViewStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 8419D66D1FB73CC80088D7E5 /* ORKWebViewStep.m */; }; 861D11AD1AA7951F003C98A7 /* ORKChoiceAnswerFormatHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = 861D11AB1AA7951F003C98A7 /* ORKChoiceAnswerFormatHelper.h */; settings = {ATTRIBUTES = (Private, ); }; }; 861D11AE1AA7951F003C98A7 /* ORKChoiceAnswerFormatHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 861D11AC1AA7951F003C98A7 /* ORKChoiceAnswerFormatHelper.m */; }; - 866DA51F1D63D04700C9AF3F /* ORKCollector_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 866DA5131D63D04700C9AF3F /* ORKCollector_Internal.h */; platformFilter = ios; }; - 866DA5201D63D04700C9AF3F /* ORKCollector.h in Headers */ = {isa = PBXBuildFile; fileRef = 866DA5141D63D04700C9AF3F /* ORKCollector.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - 866DA5211D63D04700C9AF3F /* ORKCollector.m in Sources */ = {isa = PBXBuildFile; fileRef = 866DA5151D63D04700C9AF3F /* ORKCollector.m */; platformFilter = ios; }; - 866DA5221D63D04700C9AF3F /* ORKDataCollectionManager_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 866DA5161D63D04700C9AF3F /* ORKDataCollectionManager_Internal.h */; platformFilter = ios; }; - 866DA5231D63D04700C9AF3F /* ORKDataCollectionManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 866DA5171D63D04700C9AF3F /* ORKDataCollectionManager.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - 866DA5241D63D04700C9AF3F /* ORKDataCollectionManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 866DA5181D63D04700C9AF3F /* ORKDataCollectionManager.m */; platformFilter = ios; }; - 866DA5251D63D04700C9AF3F /* ORKHealthSampleQueryOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 866DA5191D63D04700C9AF3F /* ORKHealthSampleQueryOperation.h */; platformFilter = ios; }; - 866DA5261D63D04700C9AF3F /* ORKHealthSampleQueryOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 866DA51A1D63D04700C9AF3F /* ORKHealthSampleQueryOperation.m */; platformFilter = ios; }; - 866DA5271D63D04700C9AF3F /* ORKMotionActivityQueryOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 866DA51B1D63D04700C9AF3F /* ORKMotionActivityQueryOperation.h */; platformFilter = ios; }; - 866DA5281D63D04700C9AF3F /* ORKMotionActivityQueryOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 866DA51C1D63D04700C9AF3F /* ORKMotionActivityQueryOperation.m */; platformFilter = ios; }; - 866DA5291D63D04700C9AF3F /* ORKOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 866DA51D1D63D04700C9AF3F /* ORKOperation.h */; platformFilter = ios; }; - 866DA52A1D63D04700C9AF3F /* ORKOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 866DA51E1D63D04700C9AF3F /* ORKOperation.m */; platformFilter = ios; }; + 866DA51F1D63D04700C9AF3F /* ORKCollector_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 866DA5131D63D04700C9AF3F /* ORKCollector_Internal.h */; }; + 866DA5201D63D04700C9AF3F /* ORKCollector.h in Headers */ = {isa = PBXBuildFile; fileRef = 866DA5141D63D04700C9AF3F /* ORKCollector.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 866DA5211D63D04700C9AF3F /* ORKCollector.m in Sources */ = {isa = PBXBuildFile; fileRef = 866DA5151D63D04700C9AF3F /* ORKCollector.m */; }; + 866DA5221D63D04700C9AF3F /* ORKDataCollectionManager_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 866DA5161D63D04700C9AF3F /* ORKDataCollectionManager_Internal.h */; }; + 866DA5231D63D04700C9AF3F /* ORKDataCollectionManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 866DA5171D63D04700C9AF3F /* ORKDataCollectionManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 866DA5241D63D04700C9AF3F /* ORKDataCollectionManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 866DA5181D63D04700C9AF3F /* ORKDataCollectionManager.m */; }; + 866DA5251D63D04700C9AF3F /* ORKHealthSampleQueryOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 866DA5191D63D04700C9AF3F /* ORKHealthSampleQueryOperation.h */; }; + 866DA5261D63D04700C9AF3F /* ORKHealthSampleQueryOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 866DA51A1D63D04700C9AF3F /* ORKHealthSampleQueryOperation.m */; }; + 866DA5271D63D04700C9AF3F /* ORKMotionActivityQueryOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 866DA51B1D63D04700C9AF3F /* ORKMotionActivityQueryOperation.h */; }; + 866DA5281D63D04700C9AF3F /* ORKMotionActivityQueryOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 866DA51C1D63D04700C9AF3F /* ORKMotionActivityQueryOperation.m */; }; + 866DA5291D63D04700C9AF3F /* ORKOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 866DA51D1D63D04700C9AF3F /* ORKOperation.h */; }; + 866DA52A1D63D04700C9AF3F /* ORKOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 866DA51E1D63D04700C9AF3F /* ORKOperation.m */; }; 86C40CC01A8D7C5C00081FAC /* ORKCompletionStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B521A8D7C5B00081FAC /* ORKCompletionStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; 86C40CC21A8D7C5C00081FAC /* ORKCompletionStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B531A8D7C5B00081FAC /* ORKCompletionStep.m */; }; 86C40CE41A8D7C5C00081FAC /* ORKAnswerFormat.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B641A8D7C5B00081FAC /* ORKAnswerFormat.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -331,11 +332,11 @@ 86C40D141A8D7C5C00081FAC /* ORKHelpers_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B7C1A8D7C5C00081FAC /* ORKHelpers_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; 86C40D161A8D7C5C00081FAC /* ORKErrors.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B7D1A8D7C5C00081FAC /* ORKErrors.h */; settings = {ATTRIBUTES = (Private, ); }; }; 86C40D181A8D7C5C00081FAC /* ORKErrors.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B7E1A8D7C5C00081FAC /* ORKErrors.m */; }; - 86C40D1A1A8D7C5C00081FAC /* ORKFormItem_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B7F1A8D7C5C00081FAC /* ORKFormItem_Internal.h */; platformFilter = ios; }; + 86C40D1A1A8D7C5C00081FAC /* ORKFormItem_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B7F1A8D7C5C00081FAC /* ORKFormItem_Internal.h */; }; 86C40D201A8D7C5C00081FAC /* ORKFormStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B821A8D7C5C00081FAC /* ORKFormStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; 86C40D221A8D7C5C00081FAC /* ORKFormStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B831A8D7C5C00081FAC /* ORKFormStep.m */; }; - 86C40D301A8D7C5C00081FAC /* ORKHealthAnswerFormat.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B8A1A8D7C5C00081FAC /* ORKHealthAnswerFormat.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - 86C40D321A8D7C5C00081FAC /* ORKHealthAnswerFormat.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B8B1A8D7C5C00081FAC /* ORKHealthAnswerFormat.m */; platformFilter = ios; }; + 86C40D301A8D7C5C00081FAC /* ORKHealthAnswerFormat.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B8A1A8D7C5C00081FAC /* ORKHealthAnswerFormat.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 86C40D321A8D7C5C00081FAC /* ORKHealthAnswerFormat.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B8B1A8D7C5C00081FAC /* ORKHealthAnswerFormat.m */; }; 86C40D361A8D7C5C00081FAC /* ORKHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B8D1A8D7C5C00081FAC /* ORKHelpers.m */; }; 86C40D401A8D7C5C00081FAC /* ORKInstructionStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B921A8D7C5C00081FAC /* ORKInstructionStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; 86C40D421A8D7C5C00081FAC /* ORKInstructionStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B931A8D7C5C00081FAC /* ORKInstructionStep.m */; }; @@ -343,7 +344,7 @@ 86C40D581A8D7C5C00081FAC /* ORKOrderedTask.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B9E1A8D7C5C00081FAC /* ORKOrderedTask.m */; }; 86C40D5E1A8D7C5C00081FAC /* ORKQuestionStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BA11A8D7C5C00081FAC /* ORKQuestionStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; 86C40D601A8D7C5C00081FAC /* ORKQuestionStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40BA21A8D7C5C00081FAC /* ORKQuestionStep.m */; }; - 86C40D621A8D7C5C00081FAC /* ORKQuestionStep_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BA31A8D7C5C00081FAC /* ORKQuestionStep_Internal.h */; platformFilter = ios; }; + 86C40D621A8D7C5C00081FAC /* ORKQuestionStep_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BA31A8D7C5C00081FAC /* ORKQuestionStep_Internal.h */; }; 86C40D6A1A8D7C5C00081FAC /* ORKResult.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BA71A8D7C5C00081FAC /* ORKResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; 86C40D6C1A8D7C5C00081FAC /* ORKResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40BA81A8D7C5C00081FAC /* ORKResult.m */; }; 86C40D6E1A8D7C5C00081FAC /* ORKResult_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BA91A8D7C5C00081FAC /* ORKResult_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -351,16 +352,16 @@ 86C40D901A8D7C5C00081FAC /* ORKStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40BBA1A8D7C5C00081FAC /* ORKStep.m */; }; 86C40D921A8D7C5C00081FAC /* ORKStep_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BBB1A8D7C5C00081FAC /* ORKStep_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; 86C40DC61A8D7C5C00081FAC /* ORKTask.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BD51A8D7C5C00081FAC /* ORKTask.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 86C40DFE1A8D7C5C00081FAC /* ORKConsentDocument.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BF21A8D7C5C00081FAC /* ORKConsentDocument.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - 86C40E001A8D7C5C00081FAC /* ORKConsentDocument.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40BF31A8D7C5C00081FAC /* ORKConsentDocument.m */; platformFilter = ios; }; - 86C40E021A8D7C5C00081FAC /* ORKConsentDocument_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BF41A8D7C5C00081FAC /* ORKConsentDocument_Internal.h */; platformFilter = ios; }; - 86C40E081A8D7C5C00081FAC /* ORKConsentReviewStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BF71A8D7C5C00081FAC /* ORKConsentReviewStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - 86C40E0A1A8D7C5C00081FAC /* ORKConsentReviewStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40BF81A8D7C5C00081FAC /* ORKConsentReviewStep.m */; platformFilter = ios; }; - 86C40E181A8D7C5C00081FAC /* ORKConsentSection.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BFF1A8D7C5C00081FAC /* ORKConsentSection.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - 86C40E1A1A8D7C5C00081FAC /* ORKConsentSection.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40C001A8D7C5C00081FAC /* ORKConsentSection.m */; platformFilter = ios; }; - 86C40E1C1A8D7C5C00081FAC /* ORKConsentSection_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40C011A8D7C5C00081FAC /* ORKConsentSection_Private.h */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; }; - 86C40E1E1A8D7C5C00081FAC /* ORKConsentSignature.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40C021A8D7C5C00081FAC /* ORKConsentSignature.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - 86C40E201A8D7C5C00081FAC /* ORKConsentSignature.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40C031A8D7C5C00081FAC /* ORKConsentSignature.m */; platformFilter = ios; }; + 86C40DFE1A8D7C5C00081FAC /* ORKConsentDocument.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BF21A8D7C5C00081FAC /* ORKConsentDocument.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 86C40E001A8D7C5C00081FAC /* ORKConsentDocument.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40BF31A8D7C5C00081FAC /* ORKConsentDocument.m */; }; + 86C40E021A8D7C5C00081FAC /* ORKConsentDocument_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BF41A8D7C5C00081FAC /* ORKConsentDocument_Internal.h */; }; + 86C40E081A8D7C5C00081FAC /* ORKConsentReviewStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BF71A8D7C5C00081FAC /* ORKConsentReviewStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 86C40E0A1A8D7C5C00081FAC /* ORKConsentReviewStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40BF81A8D7C5C00081FAC /* ORKConsentReviewStep.m */; }; + 86C40E181A8D7C5C00081FAC /* ORKConsentSection.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BFF1A8D7C5C00081FAC /* ORKConsentSection.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 86C40E1A1A8D7C5C00081FAC /* ORKConsentSection.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40C001A8D7C5C00081FAC /* ORKConsentSection.m */; }; + 86C40E1C1A8D7C5C00081FAC /* ORKConsentSection_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40C011A8D7C5C00081FAC /* ORKConsentSection_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 86C40E1E1A8D7C5C00081FAC /* ORKConsentSignature.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40C021A8D7C5C00081FAC /* ORKConsentSignature.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 86C40E201A8D7C5C00081FAC /* ORKConsentSignature.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40C031A8D7C5C00081FAC /* ORKConsentSignature.m */; }; 86CC8EB31AC09383001CCD89 /* ORKAccessibilityTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 86CC8EA81AC09383001CCD89 /* ORKAccessibilityTests.m */; }; 86CC8EB41AC09383001CCD89 /* ORKChoiceAnswerFormatHelperTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 86CC8EA91AC09383001CCD89 /* ORKChoiceAnswerFormatHelperTests.m */; }; 86CC8EB51AC09383001CCD89 /* ORKConsentTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 86CC8EAA1AC09383001CCD89 /* ORKConsentTests.m */; }; @@ -370,21 +371,21 @@ 86D348021AC161B0006DB02B /* ORKRecorderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 86D348001AC16175006DB02B /* ORKRecorderTests.m */; }; AE75433A24E32CCC00E4C7CF /* ORKEarlyTerminationConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = AE75433824E32CCC00E4C7CF /* ORKEarlyTerminationConfiguration.h */; settings = {ATTRIBUTES = (Public, ); }; }; AE75433B24E32CCC00E4C7CF /* ORKEarlyTerminationConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = AE75433924E32CCC00E4C7CF /* ORKEarlyTerminationConfiguration.m */; }; - B11C54991A9EEF8800265E61 /* ORKConsentSharingStep.h in Headers */ = {isa = PBXBuildFile; fileRef = B11C54961A9EEF8800265E61 /* ORKConsentSharingStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - B11C549B1A9EEF8800265E61 /* ORKConsentSharingStep.m in Sources */ = {isa = PBXBuildFile; fileRef = B11C54971A9EEF8800265E61 /* ORKConsentSharingStep.m */; platformFilter = ios; }; + B11C54991A9EEF8800265E61 /* ORKConsentSharingStep.h in Headers */ = {isa = PBXBuildFile; fileRef = B11C54961A9EEF8800265E61 /* ORKConsentSharingStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B11C549B1A9EEF8800265E61 /* ORKConsentSharingStep.m in Sources */ = {isa = PBXBuildFile; fileRef = B11C54971A9EEF8800265E61 /* ORKConsentSharingStep.m */; }; B183A4A21A8535D100C76870 /* ResearchKit_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = B1B894391A00345200C5CF2D /* ResearchKit_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; B183A4DD1A8535D100C76870 /* ResearchKit.h in Headers */ = {isa = PBXBuildFile; fileRef = B1C1DE4F196F541F00F75544 /* ResearchKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; - B1C0F4E41A9BA65F0022C153 /* ResearchKit.strings in Resources */ = {isa = PBXBuildFile; fileRef = B1C0F4E11A9BA65F0022C153 /* ResearchKit.strings */; platformFilter = ios; }; - B1C7955E1A9FBF04007279BA /* HealthKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B1C7955D1A9FBF04007279BA /* HealthKit.framework */; platformFilter = ios; settings = {ATTRIBUTES = (Required, ); }; }; + B1C0F4E41A9BA65F0022C153 /* ResearchKit.strings in Resources */ = {isa = PBXBuildFile; fileRef = B1C0F4E11A9BA65F0022C153 /* ResearchKit.strings */; }; + B1C7955E1A9FBF04007279BA /* HealthKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B1C7955D1A9FBF04007279BA /* HealthKit.framework */; settings = {ATTRIBUTES = (Required, ); }; }; BA473FE8224DB38900A362E3 /* ORKBodyItem.h in Headers */ = {isa = PBXBuildFile; fileRef = BA473FE6224DB38900A362E3 /* ORKBodyItem.h */; settings = {ATTRIBUTES = (Public, ); }; }; BA473FE9224DB38900A362E3 /* ORKBodyItem.m in Sources */ = {isa = PBXBuildFile; fileRef = BA473FE7224DB38900A362E3 /* ORKBodyItem.m */; }; BA8C5021226FFB04001896D0 /* ORKLearnMoreItem.h in Headers */ = {isa = PBXBuildFile; fileRef = BA8C501F226FFB04001896D0 /* ORKLearnMoreItem.h */; settings = {ATTRIBUTES = (Public, ); }; }; BAB96755226E5D67006AAC56 /* ORKStepContainerViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BAB96754226E5D67006AAC56 /* ORKStepContainerViewTests.m */; }; - BABBB1AE2097D97200CB29E5 /* ORKPDFViewerStep.h in Headers */ = {isa = PBXBuildFile; fileRef = BABBB1AC2097D97200CB29E5 /* ORKPDFViewerStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - BABBB1AF2097D97200CB29E5 /* ORKPDFViewerStep.m in Sources */ = {isa = PBXBuildFile; fileRef = BABBB1AD2097D97200CB29E5 /* ORKPDFViewerStep.m */; platformFilter = ios; }; + BABBB1AE2097D97200CB29E5 /* ORKPDFViewerStep.h in Headers */ = {isa = PBXBuildFile; fileRef = BABBB1AC2097D97200CB29E5 /* ORKPDFViewerStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; + BABBB1AF2097D97200CB29E5 /* ORKPDFViewerStep.m in Sources */ = {isa = PBXBuildFile; fileRef = BABBB1AD2097D97200CB29E5 /* ORKPDFViewerStep.m */; }; BAD9E9122255E9750014FA29 /* ORKLearnMoreInstructionStep.h in Headers */ = {isa = PBXBuildFile; fileRef = BAD9E9102255E9750014FA29 /* ORKLearnMoreInstructionStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; BAD9E9132255E9750014FA29 /* ORKLearnMoreInstructionStep.m in Sources */ = {isa = PBXBuildFile; fileRef = BAD9E9112255E9750014FA29 /* ORKLearnMoreInstructionStep.m */; }; - BC081DE224CBC4DE00AD92AA /* ORKTypes_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = BC081DE124CBC4DE00AD92AA /* ORKTypes_Private.h */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; }; + BC081DE224CBC4DE00AD92AA /* ORKTypes_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = BC081DE124CBC4DE00AD92AA /* ORKTypes_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; BC13CE391B0660220044153C /* ORKNavigableOrderedTask.h in Headers */ = {isa = PBXBuildFile; fileRef = BC13CE371B0660220044153C /* ORKNavigableOrderedTask.h */; settings = {ATTRIBUTES = (Public, ); }; }; BC13CE3A1B0660220044153C /* ORKNavigableOrderedTask.m in Sources */ = {isa = PBXBuildFile; fileRef = BC13CE381B0660220044153C /* ORKNavigableOrderedTask.m */; }; BC13CE3C1B0662990044153C /* ORKStepNavigationRule_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = BC13CE3B1B0662990044153C /* ORKStepNavigationRule_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -393,28 +394,28 @@ BC2908BC1FBD628F0030AB89 /* ORKTypes.m in Sources */ = {isa = PBXBuildFile; fileRef = BC2908BB1FBD628F0030AB89 /* ORKTypes.m */; }; BC40696121104DDF00A4100C /* ResearchKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B183A5951A8535D100C76870 /* ResearchKit.framework */; }; BC4D521F27B326EA0099DC18 /* ORKSecureCodingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC4D521E27B326EA0099DC18 /* ORKSecureCodingTests.swift */; }; - BC94EF311E962F7400143081 /* ORKDeprecated.h in Headers */ = {isa = PBXBuildFile; fileRef = BC94EF2F1E962F7400143081 /* ORKDeprecated.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - BC94EF361E96394C00143081 /* ORKRegistrationStep_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = BC94EF351E96394C00143081 /* ORKRegistrationStep_Internal.h */; platformFilter = ios; }; + BC94EF311E962F7400143081 /* ORKDeprecated.h in Headers */ = {isa = PBXBuildFile; fileRef = BC94EF2F1E962F7400143081 /* ORKDeprecated.h */; settings = {ATTRIBUTES = (Public, ); }; }; + BC94EF361E96394C00143081 /* ORKRegistrationStep_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = BC94EF351E96394C00143081 /* ORKRegistrationStep_Internal.h */; }; BCA5C0351AEC05F20092AC8D /* ORKStepNavigationRule.h in Headers */ = {isa = PBXBuildFile; fileRef = BCA5C0331AEC05F20092AC8D /* ORKStepNavigationRule.h */; settings = {ATTRIBUTES = (Public, ); }; }; BCA5C0361AEC05F20092AC8D /* ORKStepNavigationRule.m in Sources */ = {isa = PBXBuildFile; fileRef = BCA5C0341AEC05F20092AC8D /* ORKStepNavigationRule.m */; }; BCAD50E81B0201EE0034806A /* ORKTaskTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BCAD50E71B0201EE0034806A /* ORKTaskTests.m */; }; BCB080A11B83EFB900A3F400 /* ORKStepNavigationRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCB080A01B83EFB900A3F400 /* ORKStepNavigationRule.swift */; }; BCB8133C1C98367A00346561 /* ORKTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = BCB8133B1C98367A00346561 /* ORKTypes.h */; settings = {ATTRIBUTES = (Public, ); }; }; BCB96C131B19C0EC002A0B96 /* ORKStepTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BCB96C121B19C0EC002A0B96 /* ORKStepTests.m */; }; - BCCE9EC121104B2200B809F8 /* ORKConsentDocument_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = BCCE9EC021104B2200B809F8 /* ORKConsentDocument_Private.h */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; }; + BCCE9EC121104B2200B809F8 /* ORKConsentDocument_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = BCCE9EC021104B2200B809F8 /* ORKConsentDocument_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; BCFF24BD1B0798D10044EC35 /* ORKResultPredicate.m in Sources */ = {isa = PBXBuildFile; fileRef = BCFF24BC1B0798D10044EC35 /* ORKResultPredicate.m */; }; - BF1D43851D4904C6007EE90B /* ORKVideoInstructionStep.h in Headers */ = {isa = PBXBuildFile; fileRef = BF1D43831D4904C6007EE90B /* ORKVideoInstructionStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - BF1D43861D4904C6007EE90B /* ORKVideoInstructionStep.m in Sources */ = {isa = PBXBuildFile; fileRef = BF1D43841D4904C6007EE90B /* ORKVideoInstructionStep.m */; platformFilter = ios; }; - BF5161501BE9C53D00174DDD /* ORKWaitStep.h in Headers */ = {isa = PBXBuildFile; fileRef = BF9155A11BDE8DA9007FA459 /* ORKWaitStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - BF91559B1BDE8D7D007FA459 /* ORKReviewStep_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = BF9155951BDE8D7D007FA459 /* ORKReviewStep_Internal.h */; platformFilter = ios; }; - BF91559C1BDE8D7D007FA459 /* ORKReviewStep.h in Headers */ = {isa = PBXBuildFile; fileRef = BF9155961BDE8D7D007FA459 /* ORKReviewStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - BF91559D1BDE8D7D007FA459 /* ORKReviewStep.m in Sources */ = {isa = PBXBuildFile; fileRef = BF9155971BDE8D7D007FA459 /* ORKReviewStep.m */; platformFilter = ios; }; - BF9155A81BDE8DA9007FA459 /* ORKWaitStep.m in Sources */ = {isa = PBXBuildFile; fileRef = BF9155A21BDE8DA9007FA459 /* ORKWaitStep.m */; platformFilter = ios; }; + BF1D43851D4904C6007EE90B /* ORKVideoInstructionStep.h in Headers */ = {isa = PBXBuildFile; fileRef = BF1D43831D4904C6007EE90B /* ORKVideoInstructionStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; + BF1D43861D4904C6007EE90B /* ORKVideoInstructionStep.m in Sources */ = {isa = PBXBuildFile; fileRef = BF1D43841D4904C6007EE90B /* ORKVideoInstructionStep.m */; }; + BF5161501BE9C53D00174DDD /* ORKWaitStep.h in Headers */ = {isa = PBXBuildFile; fileRef = BF9155A11BDE8DA9007FA459 /* ORKWaitStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; + BF91559B1BDE8D7D007FA459 /* ORKReviewStep_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = BF9155951BDE8D7D007FA459 /* ORKReviewStep_Internal.h */; }; + BF91559C1BDE8D7D007FA459 /* ORKReviewStep.h in Headers */ = {isa = PBXBuildFile; fileRef = BF9155961BDE8D7D007FA459 /* ORKReviewStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; + BF91559D1BDE8D7D007FA459 /* ORKReviewStep.m in Sources */ = {isa = PBXBuildFile; fileRef = BF9155971BDE8D7D007FA459 /* ORKReviewStep.m */; }; + BF9155A81BDE8DA9007FA459 /* ORKWaitStep.m in Sources */ = {isa = PBXBuildFile; fileRef = BF9155A21BDE8DA9007FA459 /* ORKWaitStep.m */; }; CA08054028AD7CC8001695EF /* ORKViewControllerProviding.h in Headers */ = {isa = PBXBuildFile; fileRef = CA08053F28AD7CC8001695EF /* ORKViewControllerProviding.h */; settings = {ATTRIBUTES = (Private, ); }; }; CA0AC56928BD4FAC00E80040 /* ORKStepViewControllerHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA0AC56828BD4FAB00E80040 /* ORKStepViewControllerHelpers.swift */; }; CA1C7A44288B0C68004DAB3A /* ResearchKitUI.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D06632F24FEF272005D9B40 /* ResearchKitUI.h */; settings = {ATTRIBUTES = (Public, ); }; }; CA1C7A5C288B0CA2004DAB3A /* ResearchKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B183A5951A8535D100C76870 /* ResearchKit.framework */; }; - CA23A31D2899941700EC6E91 /* ORKSkin_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = CA23A31C2899941700EC6E91 /* ORKSkin_Private.h */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; }; + CA23A31D2899941700EC6E91 /* ORKSkin_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = CA23A31C2899941700EC6E91 /* ORKSkin_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; CA2616D32894649A008B1425 /* ORKRequestPermissionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 51E03D6C2491B6DB008F8406 /* ORKRequestPermissionView.m */; }; CA2616D42894649A008B1425 /* ORKRequestPermissionButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 5170009C254A197300CACE12 /* ORKRequestPermissionButton.m */; }; CA2B8F6128A16B050025B773 /* ORKSpeechInNoiseStepViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7118AC6E20BF6A7700D7A6BB /* ORKSpeechInNoiseStepViewController.m */; }; @@ -536,20 +537,20 @@ CA2B8FF928A177C30025B773 /* ORKTrailmakingStepViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 781D540E1DF886AB00223305 /* ORKTrailmakingStepViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; CA2B8FFA28A177DE0025B773 /* ORKWalkingTaskStepViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B211A8D7C5B00081FAC /* ORKWalkingTaskStepViewController.m */; }; CA2B8FFB28A177E40025B773 /* ORKWalkingTaskStepViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B201A8D7C5B00081FAC /* ORKWalkingTaskStepViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; - CA2B900928A17AD90025B773 /* ORKActiveStep_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B321A8D7C5B00081FAC /* ORKActiveStep_Internal.h */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; }; - CA2B900A28A17ADE0025B773 /* ORKActiveStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B311A8D7C5B00081FAC /* ORKActiveStep.m */; platformFilter = ios; }; - CA2B900B28A17AE10025B773 /* ORKActiveStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B301A8D7C5B00081FAC /* ORKActiveStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; + CA2B900928A17AD90025B773 /* ORKActiveStep_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B321A8D7C5B00081FAC /* ORKActiveStep_Internal.h */; settings = {ATTRIBUTES = (Private, ); }; }; + CA2B900A28A17ADE0025B773 /* ORKActiveStep.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B311A8D7C5B00081FAC /* ORKActiveStep.m */; }; + CA2B900B28A17AE10025B773 /* ORKActiveStep.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B301A8D7C5B00081FAC /* ORKActiveStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; CA2B901028A17BEE0025B773 /* ORKActiveTaskResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A261E81A87B005C2A1E /* ORKActiveTaskResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; - CA2B901128A17D120025B773 /* ORKFileResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A391E81AF1D005C2A1E /* ORKFileResult.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - CA2B901228A17D170025B773 /* ORKFileResult.m in Sources */ = {isa = PBXBuildFile; fileRef = FF919A3A1E81AF1D005C2A1E /* ORKFileResult.m */; platformFilter = ios; }; + CA2B901128A17D120025B773 /* ORKFileResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A391E81AF1D005C2A1E /* ORKFileResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CA2B901228A17D170025B773 /* ORKFileResult.m in Sources */ = {isa = PBXBuildFile; fileRef = FF919A3A1E81AF1D005C2A1E /* ORKFileResult.m */; }; CA2B901628A1802C0025B773 /* ORKOrderedTask+ORKPredefinedActiveTask.m in Sources */ = {isa = PBXBuildFile; fileRef = FF154FB31E82EF5E004ED908 /* ORKOrderedTask+ORKPredefinedActiveTask.m */; }; CA2B901728A180480025B773 /* ORKOrderedTask+ORKPredefinedActiveTask.h in Headers */ = {isa = PBXBuildFile; fileRef = FF154FB21E82EF5E004ED908 /* ORKOrderedTask+ORKPredefinedActiveTask.h */; settings = {ATTRIBUTES = (Public, ); }; }; - CA2B901F28A1864A0025B773 /* ORKRecorder.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B471A8D7C5B00081FAC /* ORKRecorder.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - CA2B902028A186500025B773 /* ORKRecorder_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B491A8D7C5B00081FAC /* ORKRecorder_Internal.h */; platformFilter = ios; }; - CA2B902128A186550025B773 /* ORKRecorder_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B4A1A8D7C5B00081FAC /* ORKRecorder_Private.h */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; }; - CA2B902228A1867E0025B773 /* ORKRecorder.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B481A8D7C5B00081FAC /* ORKRecorder.m */; platformFilter = ios; }; - CA2B902328A186A80025B773 /* ORKDataLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B3C1A8D7C5B00081FAC /* ORKDataLogger.h */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; }; - CA2B902428A186AF0025B773 /* ORKDataLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B3D1A8D7C5B00081FAC /* ORKDataLogger.m */; platformFilter = ios; }; + CA2B901F28A1864A0025B773 /* ORKRecorder.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B471A8D7C5B00081FAC /* ORKRecorder.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CA2B902028A186500025B773 /* ORKRecorder_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B491A8D7C5B00081FAC /* ORKRecorder_Internal.h */; }; + CA2B902128A186550025B773 /* ORKRecorder_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B4A1A8D7C5B00081FAC /* ORKRecorder_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; + CA2B902228A1867E0025B773 /* ORKRecorder.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B481A8D7C5B00081FAC /* ORKRecorder.m */; }; + CA2B902328A186A80025B773 /* ORKDataLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B3C1A8D7C5B00081FAC /* ORKDataLogger.h */; settings = {ATTRIBUTES = (Private, ); }; }; + CA2B902428A186AF0025B773 /* ORKDataLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B3D1A8D7C5B00081FAC /* ORKDataLogger.m */; }; CA2B902628A187390025B773 /* ORKTask_Util.m in Sources */ = {isa = PBXBuildFile; fileRef = CA2B902528A187390025B773 /* ORKTask_Util.m */; }; CA2B902728A18EA60025B773 /* ORKActiveStepViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B381A8D7C5B00081FAC /* ORKActiveStepViewController.m */; }; CA2B902828A18EAD0025B773 /* ORKActiveStepViewController_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B391A8D7C5B00081FAC /* ORKActiveStepViewController_Internal.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -565,15 +566,15 @@ CA6A0D7F288B51D30048C1EF /* ORKSkin.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40BB81A8D7C5C00081FAC /* ORKSkin.m */; }; CA6A0D81288B54650048C1EF /* ORKConsentReviewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40BEC1A8D7C5C00081FAC /* ORKConsentReviewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; CA6A0D82288B54650048C1EF /* ORKConsentReviewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40BED1A8D7C5C00081FAC /* ORKConsentReviewController.m */; }; - CA6A0D83288B5B370048C1EF /* ORKHTMLPDFWriter.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B8E1A8D7C5C00081FAC /* ORKHTMLPDFWriter.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - CA6A0D84288B5B370048C1EF /* ORKHTMLPDFPageRenderer.h in Headers */ = {isa = PBXBuildFile; fileRef = 8DE27B3E1D5BC072009A26E3 /* ORKHTMLPDFPageRenderer.h */; platformFilter = ios; }; - CA6A0D85288B5B370048C1EF /* ORKHTMLPDFPageRenderer.m in Sources */ = {isa = PBXBuildFile; fileRef = 8DE27B3F1D5BC0B9009A26E3 /* ORKHTMLPDFPageRenderer.m */; platformFilter = ios; }; - CA6A0D86288B5B370048C1EF /* ORKHTMLPDFWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B8F1A8D7C5C00081FAC /* ORKHTMLPDFWriter.m */; platformFilter = ios; }; + CA6A0D83288B5B370048C1EF /* ORKHTMLPDFWriter.h in Headers */ = {isa = PBXBuildFile; fileRef = 86C40B8E1A8D7C5C00081FAC /* ORKHTMLPDFWriter.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CA6A0D84288B5B370048C1EF /* ORKHTMLPDFPageRenderer.h in Headers */ = {isa = PBXBuildFile; fileRef = 8DE27B3E1D5BC072009A26E3 /* ORKHTMLPDFPageRenderer.h */; }; + CA6A0D85288B5B370048C1EF /* ORKHTMLPDFPageRenderer.m in Sources */ = {isa = PBXBuildFile; fileRef = 8DE27B3F1D5BC0B9009A26E3 /* ORKHTMLPDFPageRenderer.m */; }; + CA6A0D86288B5B370048C1EF /* ORKHTMLPDFWriter.m in Sources */ = {isa = PBXBuildFile; fileRef = 86C40B8F1A8D7C5C00081FAC /* ORKHTMLPDFWriter.m */; }; CA6A0D9E288F1CA20048C1EF /* ResearchKitUI_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = CA6A0D9D288F1BDC0048C1EF /* ResearchKitUI_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; CA6A0DA0288F2C1C0048C1EF /* ResearchKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CA1C7A5A288B0C68004DAB3A /* ResearchKitUI.framework */; }; CA954B6E28AD8A8C0020A35C /* ORKStep+ResearchKitActiveTask.m in Sources */ = {isa = PBXBuildFile; fileRef = CA954B6D28AD8A8C0020A35C /* ORKStep+ResearchKitActiveTask.m */; }; CA954B6F28AD8DA50020A35C /* ORKStep+ResearchKitUI.m in Sources */ = {isa = PBXBuildFile; fileRef = CA08054328AD7EBA001695EF /* ORKStep+ResearchKitUI.m */; }; - CA994D5628AB08410019DEA4 /* ORKDeprecated.m in Sources */ = {isa = PBXBuildFile; fileRef = BC94EF301E962F7400143081 /* ORKDeprecated.m */; platformFilter = ios; }; + CA994D5628AB08410019DEA4 /* ORKDeprecated.m in Sources */ = {isa = PBXBuildFile; fileRef = BC94EF301E962F7400143081 /* ORKDeprecated.m */; }; CAA20D4E288B3D5700EDC764 /* ORKRecordButton.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D43C5CF24217393006F4084 /* ORKRecordButton.h */; settings = {ATTRIBUTES = (Public, ); }; }; CAA20D4F288B3D5700EDC764 /* ORKRecordButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D43C5D024217393006F4084 /* ORKRecordButton.m */; }; CAA20D50288B3D6400EDC764 /* ORKCheckmarkView.h in Headers */ = {isa = PBXBuildFile; fileRef = BAD6FAAF2332A2FD006647E7 /* ORKCheckmarkView.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -987,42 +988,41 @@ CAFAA6C228A198BD0010BBDE /* ResearchKitActiveTask_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = CAFAA6C128A198BD0010BBDE /* ResearchKitActiveTask_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; CAFAA6C428A19E200010BBDE /* ResearchKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B183A5951A8535D100C76870 /* ResearchKit.framework */; }; CAFAA6C528A19E260010BBDE /* ResearchKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CA1C7A5A288B0C68004DAB3A /* ResearchKitUI.framework */; }; - D44239791AF17F5100559D96 /* ORKImageCaptureStep.h in Headers */ = {isa = PBXBuildFile; fileRef = D44239771AF17F5100559D96 /* ORKImageCaptureStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - D442397A1AF17F5100559D96 /* ORKImageCaptureStep.m in Sources */ = {isa = PBXBuildFile; fileRef = D44239781AF17F5100559D96 /* ORKImageCaptureStep.m */; platformFilter = ios; }; - E454438D2CC1C78E0004E385 /* ResearchKit_Prefix.pch in Headers */ = {isa = PBXBuildFile; fileRef = 5DABE5AD24DA16E600570C57 /* ResearchKit_Prefix.pch */; }; + D44239791AF17F5100559D96 /* ORKImageCaptureStep.h in Headers */ = {isa = PBXBuildFile; fileRef = D44239771AF17F5100559D96 /* ORKImageCaptureStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D442397A1AF17F5100559D96 /* ORKImageCaptureStep.m in Sources */ = {isa = PBXBuildFile; fileRef = D44239781AF17F5100559D96 /* ORKImageCaptureStep.m */; }; FA7A9D2B1B082688005A2BEA /* ORKConsentDocumentTests.m in Sources */ = {isa = PBXBuildFile; fileRef = FA7A9D2A1B082688005A2BEA /* ORKConsentDocumentTests.m */; }; - FA7A9D2F1B083DD3005A2BEA /* ORKConsentSectionFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = FA7A9D2D1B083DD3005A2BEA /* ORKConsentSectionFormatter.h */; platformFilter = ios; }; - FA7A9D301B083DD3005A2BEA /* ORKConsentSectionFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = FA7A9D2E1B083DD3005A2BEA /* ORKConsentSectionFormatter.m */; platformFilter = ios; }; - FA7A9D331B0843A9005A2BEA /* ORKConsentSignatureFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = FA7A9D311B0843A9005A2BEA /* ORKConsentSignatureFormatter.h */; platformFilter = ios; }; - FA7A9D341B0843A9005A2BEA /* ORKConsentSignatureFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = FA7A9D321B0843A9005A2BEA /* ORKConsentSignatureFormatter.m */; platformFilter = ios; }; + FA7A9D2F1B083DD3005A2BEA /* ORKConsentSectionFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = FA7A9D2D1B083DD3005A2BEA /* ORKConsentSectionFormatter.h */; }; + FA7A9D301B083DD3005A2BEA /* ORKConsentSectionFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = FA7A9D2E1B083DD3005A2BEA /* ORKConsentSectionFormatter.m */; }; + FA7A9D331B0843A9005A2BEA /* ORKConsentSignatureFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = FA7A9D311B0843A9005A2BEA /* ORKConsentSignatureFormatter.h */; }; + FA7A9D341B0843A9005A2BEA /* ORKConsentSignatureFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = FA7A9D321B0843A9005A2BEA /* ORKConsentSignatureFormatter.m */; }; FA7A9D371B09365F005A2BEA /* ORKConsentSectionFormatterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = FA7A9D361B09365F005A2BEA /* ORKConsentSectionFormatterTests.m */; }; FA7A9D391B0969A7005A2BEA /* ORKConsentSignatureFormatterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = FA7A9D381B0969A7005A2BEA /* ORKConsentSignatureFormatterTests.m */; }; - FF0CB38A1FD5C4C3002D838C /* ORKWebViewStepResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF0CB3881FD5C4C3002D838C /* ORKWebViewStepResult.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - FF0CB38B1FD5C4C3002D838C /* ORKWebViewStepResult.m in Sources */ = {isa = PBXBuildFile; fileRef = FF0CB3891FD5C4C3002D838C /* ORKWebViewStepResult.m */; platformFilter = ios; }; - FF5051ED1D668FF80065E677 /* ORKPageStep_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = FF5051EC1D668FF80065E677 /* ORKPageStep_Private.h */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; }; - FF5051F01D66908C0065E677 /* ORKNavigablePageStep.h in Headers */ = {isa = PBXBuildFile; fileRef = FF5051EE1D66908C0065E677 /* ORKNavigablePageStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - FF5051F11D66908C0065E677 /* ORKNavigablePageStep.m in Sources */ = {isa = PBXBuildFile; fileRef = FF5051EF1D66908C0065E677 /* ORKNavigablePageStep.m */; platformFilter = ios; }; - FF5CA6121D2C2670001660A3 /* ORKTableStep.h in Headers */ = {isa = PBXBuildFile; fileRef = FF5CA6101D2C2670001660A3 /* ORKTableStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - FF5CA6131D2C2670001660A3 /* ORKTableStep.m in Sources */ = {isa = PBXBuildFile; fileRef = FF5CA6111D2C2670001660A3 /* ORKTableStep.m */; platformFilter = ios; }; - FF5CA61B1D2C6453001660A3 /* ORKSignatureStep.h in Headers */ = {isa = PBXBuildFile; fileRef = FF5CA6191D2C6453001660A3 /* ORKSignatureStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - FF5CA61C1D2C6453001660A3 /* ORKSignatureStep.m in Sources */ = {isa = PBXBuildFile; fileRef = FF5CA61A1D2C6453001660A3 /* ORKSignatureStep.m */; platformFilter = ios; }; + FF0CB38A1FD5C4C3002D838C /* ORKWebViewStepResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF0CB3881FD5C4C3002D838C /* ORKWebViewStepResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; + FF0CB38B1FD5C4C3002D838C /* ORKWebViewStepResult.m in Sources */ = {isa = PBXBuildFile; fileRef = FF0CB3891FD5C4C3002D838C /* ORKWebViewStepResult.m */; }; + FF5051ED1D668FF80065E677 /* ORKPageStep_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = FF5051EC1D668FF80065E677 /* ORKPageStep_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; + FF5051F01D66908C0065E677 /* ORKNavigablePageStep.h in Headers */ = {isa = PBXBuildFile; fileRef = FF5051EE1D66908C0065E677 /* ORKNavigablePageStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; + FF5051F11D66908C0065E677 /* ORKNavigablePageStep.m in Sources */ = {isa = PBXBuildFile; fileRef = FF5051EF1D66908C0065E677 /* ORKNavigablePageStep.m */; }; + FF5CA6121D2C2670001660A3 /* ORKTableStep.h in Headers */ = {isa = PBXBuildFile; fileRef = FF5CA6101D2C2670001660A3 /* ORKTableStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; + FF5CA6131D2C2670001660A3 /* ORKTableStep.m in Sources */ = {isa = PBXBuildFile; fileRef = FF5CA6111D2C2670001660A3 /* ORKTableStep.m */; }; + FF5CA61B1D2C6453001660A3 /* ORKSignatureStep.h in Headers */ = {isa = PBXBuildFile; fileRef = FF5CA6191D2C6453001660A3 /* ORKSignatureStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; + FF5CA61C1D2C6453001660A3 /* ORKSignatureStep.m in Sources */ = {isa = PBXBuildFile; fileRef = FF5CA61A1D2C6453001660A3 /* ORKSignatureStep.m */; }; FF919A531E81BEB5005C2A1E /* ORKCollectionResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A511E81BEB5005C2A1E /* ORKCollectionResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; FF919A541E81BEB5005C2A1E /* ORKCollectionResult.m in Sources */ = {isa = PBXBuildFile; fileRef = FF919A521E81BEB5005C2A1E /* ORKCollectionResult.m */; }; FF919A561E81BEE0005C2A1E /* ORKCollectionResult_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A551E81BEDD005C2A1E /* ORKCollectionResult_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; FF919A591E81C628005C2A1E /* ORKQuestionResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A571E81C628005C2A1E /* ORKQuestionResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; FF919A5A1E81C628005C2A1E /* ORKQuestionResult.m in Sources */ = {isa = PBXBuildFile; fileRef = FF919A581E81C628005C2A1E /* ORKQuestionResult.m */; }; FF919A5C1E81C641005C2A1E /* ORKQuestionResult_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A5B1E81C63B005C2A1E /* ORKQuestionResult_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; - FF919A5F1E81CF07005C2A1E /* ORKVideoInstructionStepResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A5D1E81CF07005C2A1E /* ORKVideoInstructionStepResult.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - FF919A601E81CF07005C2A1E /* ORKVideoInstructionStepResult.m in Sources */ = {isa = PBXBuildFile; fileRef = FF919A5E1E81CF07005C2A1E /* ORKVideoInstructionStepResult.m */; platformFilter = ios; }; - FF919A631E81D04D005C2A1E /* ORKSignatureResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A611E81D04D005C2A1E /* ORKSignatureResult.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - FF919A641E81D04D005C2A1E /* ORKSignatureResult.m in Sources */ = {isa = PBXBuildFile; fileRef = FF919A621E81D04D005C2A1E /* ORKSignatureResult.m */; platformFilter = ios; }; - FF919A661E81D168005C2A1E /* ORKSignatureResult_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A651E81D164005C2A1E /* ORKSignatureResult_Private.h */; platformFilter = ios; settings = {ATTRIBUTES = (Private, ); }; }; - FF919A691E81D255005C2A1E /* ORKConsentSignatureResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A671E81D255005C2A1E /* ORKConsentSignatureResult.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - FF919A6A1E81D255005C2A1E /* ORKConsentSignatureResult.m in Sources */ = {isa = PBXBuildFile; fileRef = FF919A681E81D255005C2A1E /* ORKConsentSignatureResult.m */; platformFilter = ios; }; - FF919A6D1E81D3B0005C2A1E /* ORKPasscodeResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A6B1E81D3B0005C2A1E /* ORKPasscodeResult.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - FF919A6E1E81D3B0005C2A1E /* ORKPasscodeResult.m in Sources */ = {isa = PBXBuildFile; fileRef = FF919A6C1E81D3B0005C2A1E /* ORKPasscodeResult.m */; platformFilter = ios; }; - FFDDD8491D3555EA00446806 /* ORKPageStep.h in Headers */ = {isa = PBXBuildFile; fileRef = FFDDD8471D3555EA00446806 /* ORKPageStep.h */; platformFilter = ios; settings = {ATTRIBUTES = (Public, ); }; }; - FFDDD84A1D3555EA00446806 /* ORKPageStep.m in Sources */ = {isa = PBXBuildFile; fileRef = FFDDD8481D3555EA00446806 /* ORKPageStep.m */; platformFilter = ios; }; + FF919A5F1E81CF07005C2A1E /* ORKVideoInstructionStepResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A5D1E81CF07005C2A1E /* ORKVideoInstructionStepResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; + FF919A601E81CF07005C2A1E /* ORKVideoInstructionStepResult.m in Sources */ = {isa = PBXBuildFile; fileRef = FF919A5E1E81CF07005C2A1E /* ORKVideoInstructionStepResult.m */; }; + FF919A631E81D04D005C2A1E /* ORKSignatureResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A611E81D04D005C2A1E /* ORKSignatureResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; + FF919A641E81D04D005C2A1E /* ORKSignatureResult.m in Sources */ = {isa = PBXBuildFile; fileRef = FF919A621E81D04D005C2A1E /* ORKSignatureResult.m */; }; + FF919A661E81D168005C2A1E /* ORKSignatureResult_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A651E81D164005C2A1E /* ORKSignatureResult_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; + FF919A691E81D255005C2A1E /* ORKConsentSignatureResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A671E81D255005C2A1E /* ORKConsentSignatureResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; + FF919A6A1E81D255005C2A1E /* ORKConsentSignatureResult.m in Sources */ = {isa = PBXBuildFile; fileRef = FF919A681E81D255005C2A1E /* ORKConsentSignatureResult.m */; }; + FF919A6D1E81D3B0005C2A1E /* ORKPasscodeResult.h in Headers */ = {isa = PBXBuildFile; fileRef = FF919A6B1E81D3B0005C2A1E /* ORKPasscodeResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; + FF919A6E1E81D3B0005C2A1E /* ORKPasscodeResult.m in Sources */ = {isa = PBXBuildFile; fileRef = FF919A6C1E81D3B0005C2A1E /* ORKPasscodeResult.m */; }; + FFDDD8491D3555EA00446806 /* ORKPageStep.h in Headers */ = {isa = PBXBuildFile; fileRef = FFDDD8471D3555EA00446806 /* ORKPageStep.h */; settings = {ATTRIBUTES = (Public, ); }; }; + FFDDD84A1D3555EA00446806 /* ORKPageStep.m in Sources */ = {isa = PBXBuildFile; fileRef = FFDDD8481D3555EA00446806 /* ORKPageStep.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -4668,20 +4668,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 */, @@ -4706,16 +4706,6 @@ 519CE82A2C6582BE003BB584 /* ORKConditionStepConfiguration.h in Headers */, 51AF1B202B683C3400D3B399 /* ORKWebViewStepResult_Private.h in Headers */, 5D04884C25EF4CC30006C68B /* ORKQuestionStep_Private.h in Headers */, - 8419D66E1FB73CC80088D7E5 /* ORKWebViewStep.h in Headers */, - FFDDD8491D3555EA00446806 /* ORKPageStep.h in Headers */, - D44239791AF17F5100559D96 /* ORKImageCaptureStep.h in Headers */, - FF919A6D1E81D3B0005C2A1E /* ORKPasscodeResult.h in Headers */, - 5192BF502AE09673006E43FB /* ORKPredicateFormItemVisibilityRule.h in Headers */, - FF0CB38A1FD5C4C3002D838C /* ORKWebViewStepResult.h in Headers */, - 86C40D301A8D7C5C00081FAC /* ORKHealthAnswerFormat.h in Headers */, - FF5CA6121D2C2670001660A3 /* ORKTableStep.h in Headers */, - 51A11F172BD08D5E0060C07E /* HKSample+ORKJSONDictionary.h in Headers */, - BC13CE401B0666FD0044153C /* ORKResultPredicate.h in Headers */, 86C40D621A8D7C5C00081FAC /* ORKQuestionStep_Internal.h in Headers */, B183A4DD1A8535D100C76870 /* ResearchKit.h in Headers */, BAD9E9122255E9750014FA29 /* ORKLearnMoreInstructionStep.h in Headers */, @@ -4727,6 +4717,7 @@ FF919A631E81D04D005C2A1E /* ORKSignatureResult.h in Headers */, 03EDD58024CA6B1D006245E9 /* ORKNotificationPermissionType.h in Headers */, 5192BF8B2AE1BBB0006E43FB /* ORKSecondaryTaskStep.h in Headers */, + 5192BF8C2AE1BF16006E43FB /* (null) in Headers */, 24A4DA141B8D1115009C797A /* ORKPasscodeStep.h in Headers */, FA7A9D331B0843A9005A2BEA /* ORKConsentSignatureFormatter.h in Headers */, 5DABE5AE24DA173F00570C57 /* ResearchKit_Prefix.pch in Headers */, @@ -4737,16 +4728,25 @@ 86C40D5E1A8D7C5C00081FAC /* ORKQuestionStep.h in Headers */, 519CE82C2C6582BE003BB584 /* ORKRelatedPerson.h in Headers */, FF919A591E81C628005C2A1E /* ORKQuestionResult.h in Headers */, + 8419D66E1FB73CC80088D7E5 /* ORKWebViewStep.h in Headers */, + FFDDD8491D3555EA00446806 /* ORKPageStep.h in Headers */, + D44239791AF17F5100559D96 /* ORKImageCaptureStep.h in Headers */, + FF919A6D1E81D3B0005C2A1E /* ORKPasscodeResult.h in Headers */, 24C296771BD055B800B42EF1 /* ORKLoginStep_Internal.h in Headers */, 86C40CC01A8D7C5C00081FAC /* ORKCompletionStep.h in Headers */, 5192BF502AE09673006E43FB /* ORKPredicateFormItemVisibilityRule.h in Headers */, 519CE8272C6582BE003BB584 /* ORKRelativeGroup.h in Headers */, 14A92C4822440195007547F2 /* ORKHelpers_Internal.h in Headers */, + FF0CB38A1FD5C4C3002D838C /* ORKWebViewStepResult.h in Headers */, 5D43C5D424255675006F4084 /* ORKBodyItem_Internal.h in Headers */, + 86C40D301A8D7C5C00081FAC /* ORKHealthAnswerFormat.h in Headers */, 7167D028231B1EAA00AAB4DD /* ORKFormStep_Internal.h in Headers */, + FF5CA6121D2C2670001660A3 /* ORKTableStep.h in Headers */, FA7A9D2F1B083DD3005A2BEA /* ORKConsentSectionFormatter.h in Headers */, + 51A11F172BD08D5E0060C07E /* HKSample+ORKJSONDictionary.h in Headers */, 86C40D561A8D7C5C00081FAC /* ORKOrderedTask.h in Headers */, FF5051F01D66908C0065E677 /* ORKNavigablePageStep.h in Headers */, + BC13CE401B0666FD0044153C /* ORKResultPredicate.h in Headers */, 51EB9A532B8408D50064A515 /* ORKInstructionStepHTMLFormatter.h in Headers */, 86C40E081A8D7C5C00081FAC /* ORKConsentReviewStep.h in Headers */, 519CE82B2C6582BE003BB584 /* ORKFamilyHistoryStep.h in Headers */, @@ -4889,7 +4889,6 @@ CAA20D7C288B3D9100EDC764 /* ORKSurveyAnswerCellForSES.h in Headers */, CAA20F3B288B3F6D00EDC764 /* UIView+ORKAccessibility.h in Headers */, CAA20D61288B3D9100EDC764 /* ORKScaleSlider.h in Headers */, - E454438D2CC1C78E0004E385 /* ResearchKit_Prefix.pch in Headers */, CAA20DEF288B3DB400EDC764 /* ORKCaption1Label.h in Headers */, CAA20F31288B3F5B00EDC764 /* ORKConsentReviewStepViewController.h in Headers */, CAA20DC8288B3DB400EDC764 /* ORKRoundTappingButton.h in Headers */, @@ -5237,7 +5236,7 @@ attributes = { BuildIndependentTargetsInParallel = YES; CLASSPREFIX = ORK; - LastSwiftUpdateCheck = 1600; + LastSwiftUpdateCheck = 1530; LastUpgradeCheck = 1500; ORGANIZATIONNAME = researchkit.org; TargetAttributes = { @@ -5259,7 +5258,7 @@ }; }; buildConfigurationList = 3FFF18381829DB1D00167070 /* Build configuration list for PBXProject "ResearchKit" */; - compatibilityVersion = "Xcode 15.0"; + compatibilityVersion = "Xcode 3.2"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( @@ -5530,18 +5529,18 @@ 519CE8292C6582BE003BB584 /* ORKConditionStepConfiguration.m in Sources */, 86C40D6C1A8D7C5C00081FAC /* ORKResult.m in Sources */, 86C40D181A8D7C5C00081FAC /* ORKErrors.m in Sources */, - CA2B902428A186AF0025B773 /* ORKDataLogger.m in Sources */, 5192BF852AE1BA47006E43FB /* ORKFrontFacingCameraStepResult.m in Sources */, 031A0FC224CF4ECD000E4455 /* ORKSensorPermissionType.m in Sources */, - FF919A6A1E81D255005C2A1E /* ORKConsentSignatureResult.m in Sources */, CA6A0D85288B5B370048C1EF /* ORKHTMLPDFPageRenderer.m in Sources */, 519CE8232C6582BE003BB584 /* ORKFamilyHistoryStep.m in Sources */, 51A11F182BD08D5E0060C07E /* CMMotionActivity+ORKJSONDictionary.m in Sources */, 24A4DA151B8D1115009C797A /* ORKPasscodeStep.m in Sources */, BCA5C0361AEC05F20092AC8D /* ORKStepNavigationRule.m in Sources */, + FF919A6A1E81D255005C2A1E /* ORKConsentSignatureResult.m in Sources */, 5192BF5A2AE09794006E43FB /* ORKFormItemVisibilityRule.m in Sources */, 86C40CC21A8D7C5C00081FAC /* ORKCompletionStep.m in Sources */, BF9155A81BDE8DA9007FA459 /* ORKWaitStep.m in Sources */, + BCB080A11B83EFB900A3F400 /* ORKStepNavigationRule.swift in Sources */, AE75433B24E32CCC00E4C7CF /* ORKEarlyTerminationConfiguration.m in Sources */, 519CE8282C6582BE003BB584 /* ORKRelativeGroup.m in Sources */, 86C40D361A8D7C5C00081FAC /* ORKHelpers.m in Sources */, @@ -5561,7 +5560,6 @@ 51A11F192BD08D5E0060C07E /* HKSample+ORKJSONDictionary.m in Sources */, 86C40D581A8D7C5C00081FAC /* ORKOrderedTask.m in Sources */, 86C40D601A8D7C5C00081FAC /* ORKQuestionStep.m in Sources */, - BCB080A11B83EFB900A3F400 /* ORKStepNavigationRule.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -5693,6 +5691,7 @@ CAA20F3A288B3F6D00EDC764 /* ORKAccessibilityFunctions.m in Sources */, 5192BF982AE1C5A1006E43FB /* ORKAnswerFormat+FormStepViewControllerAdditions.m in Sources */, CAA20E01288B3E8100EDC764 /* ORKRequestPermissionsStepContainerView.m in Sources */, + 51A11F242BD1548C0060C07E /* UIImageView+ResearchKit.m in Sources */, CAA20F38288B3F6700EDC764 /* ORKVerificationStepView.m in Sources */, CAA20D6C288B3D9100EDC764 /* ORKSurveyAnswerCellForScale.m in Sources */, CAA20E2A288B3E8200EDC764 /* ORKStepView.m in Sources */, @@ -6040,7 +6039,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 5D000ED12620F27100E5442A /* Project-Debug.xcconfig */; buildSettings = { - ENABLE_MODULE_VERIFIER = NO; + ENABLE_MODULE_VERIFIER = YES; ENABLE_TESTING_SEARCH_PATHS = YES; IPHONEOS_DEPLOYMENT_TARGET = 13.0; }; @@ -6094,11 +6093,9 @@ IPHONEOS_DEPLOYMENT_TARGET = 13.0; MODULEMAP_PRIVATE_FILE = ResearchKit/ResearchKit_Private.modulemap; "OTHER_SWIFT_FLAGS[arch=*]" = "$(inherited) -runtime-compatibility-version none"; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; - TARGETED_DEVICE_FAMILY = "1,2,7"; - WATCHOS_DEPLOYMENT_TARGET = 10.0; - XROS_DEPLOYMENT_TARGET = 1.0; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; @@ -6108,11 +6105,9 @@ buildSettings = { IPHONEOS_DEPLOYMENT_TARGET = 13.0; MODULEMAP_PRIVATE_FILE = ResearchKit/ResearchKit_Private.modulemap; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; - TARGETED_DEVICE_FAMILY = "1,2,7"; - WATCHOS_DEPLOYMENT_TARGET = 10.0; - XROS_DEPLOYMENT_TARGET = 1.0; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; @@ -6124,7 +6119,6 @@ CURRENT_PROJECT_VERSION = ""; ENABLE_MODULE_VERIFIER = YES; ENABLE_TESTING_SEARCH_PATHS = YES; - GCC_PREFIX_HEADER = ResearchKit/ResearchKit_Prefix.pch; MODULEMAP_PRIVATE_FILE = ResearchKitUI/ResearchKitUI_Private.modulemap; PRODUCT_NAME = ResearchKitUI; SUPPORTS_MACCATALYST = NO; @@ -6141,7 +6135,6 @@ CURRENT_PROJECT_VERSION = ""; ENABLE_MODULE_VERIFIER = YES; ENABLE_TESTING_SEARCH_PATHS = YES; - GCC_PREFIX_HEADER = ResearchKit/ResearchKit_Prefix.pch; MODULEMAP_PRIVATE_FILE = ResearchKitUI/ResearchKitUI_Private.modulemap; PRODUCT_NAME = ResearchKitUI; SUPPORTS_MACCATALYST = NO; diff --git a/ResearchKit.xcodeproj/xcshareddata/xcschemes/ResearchKitTests.xcscheme b/ResearchKit.xcodeproj/xcshareddata/xcschemes/ResearchKitTests.xcscheme index f2cf40cb7e..f267b0c018 100644 --- a/ResearchKit.xcodeproj/xcshareddata/xcschemes/ResearchKitTests.xcscheme +++ b/ResearchKit.xcodeproj/xcshareddata/xcschemes/ResearchKitTests.xcscheme @@ -54,36 +54,6 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/ResearchKit/Common/ORKAnswerFormat.h b/ResearchKit/Common/ORKAnswerFormat.h index ed785f47c4..ec0b928dcb 100644 --- a/ResearchKit/Common/ORKAnswerFormat.h +++ b/ResearchKit/Common/ORKAnswerFormat.h @@ -32,7 +32,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + #import + + +#if TARGET_OS_IOS #import @class ORKScaleAnswerFormat; @@ -52,6 +56,7 @@ @class ORKLocationAnswerFormat; @class ORKSESAnswerFormat; @class ORKImageChoice; +#endif NS_ASSUME_NONNULL_BEGIN @@ -434,6 +439,10 @@ ORK_CLASS_AVAILABLE @end + +#pragma mark - iOS + +#if TARGET_OS_IOS @interface ORKAnswerFormat() /// @name Factory methods @@ -523,7 +532,7 @@ ORK_CLASS_AVAILABLE minimumValue:(double)minimumValue maximumValue:(double)maximumValue defaultValue:(double)defaultValue; -#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && TARGET_OS_IOS +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION + (ORKLocationAnswerFormat *)locationAnswerFormat; #endif @@ -1781,7 +1790,6 @@ This By default, the value of this property is `NO`. */ @property (copy, nullable) NSString *placeholder; -#if !TARGET_OS_WATCH /** The autocapitalization type that applies to the user's input. @@ -1824,7 +1832,6 @@ This By default, the value of this property is `NO`. If specified, overrides the default password generation rules for fields with secureTextEntry. */ @property (nonatomic, copy, nullable) UITextInputPasswordRules *passwordRules API_AVAILABLE(ios(12)); -#endif @end @@ -2207,7 +2214,7 @@ ORK_CLASS_AVAILABLE An `ORKLocationAnswerFormat` object produces an `ORKLocationQuestionResult` object. */ -#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && TARGET_OS_IOS +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION ORK_CLASS_AVAILABLE @interface ORKLocationAnswerFormat : ORKAnswerFormat @@ -2243,4 +2250,6 @@ ORK_CLASS_AVAILABLE @end +#endif + NS_ASSUME_NONNULL_END diff --git a/ResearchKit/Common/ORKAnswerFormat.m b/ResearchKit/Common/ORKAnswerFormat.m index 227addf26d..982a6f5649 100644 --- a/ResearchKit/Common/ORKAnswerFormat.m +++ b/ResearchKit/Common/ORKAnswerFormat.m @@ -90,11 +90,11 @@ BOOL ORKIsAnswerEmpty(id answer) { } +#if TARGET_OS_IOS static NSNumberFormatterStyle ORKNumberFormattingStyleConvert(ORKNumberFormattingStyle style) { return style == ORKNumberFormattingStylePercent ? NSNumberFormatterPercentStyle : NSNumberFormatterDecimalStyle; } -#if TARGET_OS_IOS @implementation ORKAnswerDefaultSource { NSMutableDictionary *_unitsTable; } @@ -289,6 +289,8 @@ - (void)updateHealthKitUnitForAnswerFormat:(ORKAnswerFormat *)answerFormat force @implementation ORKAnswerFormat +#if TARGET_OS_IOS + + (ORKScaleAnswerFormat *)scaleAnswerFormatWithMaximumValue:(NSInteger)scaleMaximum minimumValue:(NSInteger)scaleMinimum defaultValue:(NSInteger)defaultValue @@ -422,7 +424,7 @@ + (ORKDateAnswerFormat *)dateAnswerFormatWithStyle:(ORKDateAnswerStyle)style calendar:calendar]; [answerFormat setDaysBeforeCurrentDateToSetMinimumDate:daysBefore]; [answerFormat setDaysAfterCurrentDateToSetMinimumDate:daysAfter]; - + return answerFormat; } @@ -487,7 +489,7 @@ + (ORKWeightAnswerFormat *)weightAnswerFormatWithMeasurementSystem:(ORKMeasureme defaultValue:defaultValue]; } -#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && TARGET_OS_IOS +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION + (ORKLocationAnswerFormat *)locationAnswerFormat { return [ORKLocationAnswerFormat new]; } @@ -499,6 +501,8 @@ + (ORKSESAnswerFormat *)socioEconomicAnswerFormatWithTopRungText:(NSString *)top bottomRungText:bottomRungText]; } +#endif + + (ORKBooleanAnswerFormat *)booleanAnswerFormat { return [ORKBooleanAnswerFormat new]; } @@ -684,6 +688,7 @@ static void ork_validateChoices(NSArray *choices) { return choices; } +#if TARGET_OS_IOS @implementation ORKValuePickerAnswerFormat { ORKChoiceAnswerFormatHelper *_helper; ORKTextChoice *_nullTextChoice; @@ -836,7 +841,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)); @@ -885,7 +890,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) { @@ -896,11 +901,11 @@ - (NSString *)stringForAnswer:(id)answer { *stop = YES; } }]; - + if (answerTexts.count != self.valuePickers.count) { return nil; } - + return [answerTexts componentsJoinedByString:self.separator]; } @@ -1022,6 +1027,8 @@ - (NSArray *)choices { } @end +#endif + #pragma mark - ORKTextChoiceAnswerFormat @@ -1481,6 +1488,7 @@ - (BOOL)shouldShowDontKnowButton { #pragma mark - ORKTextChoiceOther +#if TARGET_OS_IOS @implementation ORKTextChoiceOther + (instancetype)new { @@ -1704,6 +1712,7 @@ - (BOOL)shouldShowDontKnowButton { } @end +#endif #pragma mark - ORKBooleanAnswerFormat @@ -1791,7 +1800,7 @@ - (BOOL)shouldShowDontKnowButton { #pragma mark - ORKTimeOfDayAnswerFormat - +#if TARGET_OS_IOS @implementation ORKTimeOfDayAnswerFormat - (instancetype)init { @@ -2818,7 +2827,7 @@ - (BOOL)shouldHideValueMarkers { #pragma mark - ORKTextScaleAnswerFormat - +#if TARGET_OS_IOS @interface ORKTextScaleAnswerFormat () { ORKChoiceAnswerFormatHelper *_helper; @@ -3029,15 +3038,19 @@ - (NSArray *)choices { @end +#endif + #pragma mark - ORKTextAnswerFormat @interface ORKTextAnswerFormat() + @end @implementation ORKTextAnswerFormat + - (Class)questionResultClass { return [ORKTextQuestionResult class]; } @@ -3056,6 +3069,7 @@ - (void)commonInit { } + - (instancetype)initWithMaximumLength:(NSInteger)maximumLength { self = [super init]; if (self) { @@ -3307,6 +3321,7 @@ - (NSString *)stringForAnswer:(id)answer { return answerString; } + - (ORKQuestionResult *)resultWithIdentifier:(NSString *)identifier answer:(id)answer { ORKQuestionResult *questionResult = nil; questionResult = (ORKQuestionResult *)[super resultWithIdentifier:identifier answer:answer]; @@ -4004,7 +4019,7 @@ + (BOOL)supportsSecureCoding { @end -#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && TARGET_OS_IOS +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION #pragma mark - ORKLocationAnswerFormat @implementation ORKLocationAnswerFormat @@ -4138,3 +4153,4 @@ - (NSString *)stringForAnswer:(id)answer { } @end +#endif diff --git a/ResearchKit/Common/ORKAnswerFormat_Internal.h b/ResearchKit/Common/ORKAnswerFormat_Internal.h index ee2ad4c073..cd096508e4 100644 --- a/ResearchKit/Common/ORKAnswerFormat_Internal.h +++ b/ResearchKit/Common/ORKAnswerFormat_Internal.h @@ -33,9 +33,10 @@ #import #endif +#if TARGET_OS_IOS #import #import - +#endif @class ORKChoiceAnswerFormatHelper; NS_ASSUME_NONNULL_BEGIN @@ -57,7 +58,7 @@ NSString *ORKQuestionTypeString(ORKQuestionType questionType); @end ORK_DESIGNATE_CODING_AND_SERIALIZATION_INITIALIZERS(ORKAnswerFormat) -#if TARGET_OS_IOS || TARGET_OS_VISION +#if TARGET_OS_IOS ORK_DESIGNATE_CODING_AND_SERIALIZATION_INITIALIZERS(ORKImageChoiceAnswerFormat) ORK_DESIGNATE_CODING_AND_SERIALIZATION_INITIALIZERS(ORKValuePickerAnswerFormat) ORK_DESIGNATE_CODING_AND_SERIALIZATION_INITIALIZERS(ORKMultipleValuePickerAnswerFormat) @@ -134,11 +135,6 @@ ORK_DESIGNATE_CODING_AND_SERIALIZATION_INITIALIZERS(ORKTextChoice) @end -@interface ORKDateAnswerFormat () { - NSDate *_currentDateOverride; -} -@end - #if TARGET_OS_IOS @protocol ORKScaleAnswerFormatProvider @@ -202,7 +198,6 @@ NSArray *ORKAllowableValueClasses(void); @end #if TARGET_OS_IOS - @interface ORKValuePickerAnswerFormat () - (instancetype)initWithTextChoices:(NSArray *)textChoices nullChoice:(ORKTextChoice *)nullChoice NS_DESIGNATED_INITIALIZER; @@ -211,6 +206,7 @@ NSArray *ORKAllowableValueClasses(void); @end + @interface ORKImageChoice () @end @@ -222,7 +218,11 @@ NSArray *ORKAllowableValueClasses(void); @end -@interface ORKDateAnswerFormat () + +@interface ORKDateAnswerFormat () { + NSDate *_currentDateOverride; +} + - (NSDate *)pickerDefaultDate; - (nullable NSDate *)pickerMinimumDate; - (nullable NSDate *)pickerMaximumDate; diff --git a/ResearchKit/Common/ORKAnswerFormat_Private.h b/ResearchKit/Common/ORKAnswerFormat_Private.h index 4cde856db0..351cfc0c24 100644 --- a/ResearchKit/Common/ORKAnswerFormat_Private.h +++ b/ResearchKit/Common/ORKAnswerFormat_Private.h @@ -28,7 +28,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + + +#if TARGET_OS_IOS #import +#endif + NS_ASSUME_NONNULL_BEGIN @@ -55,6 +60,7 @@ ORK_EXTERN id ORKNullAnswerValue(void) ORK_AVAILABLE_DECL; An `ORKConfirmTextAnswerFormat` object produces an `ORKBooleanQuestionResult` object. */ +#if TARGET_OS_IOS ORK_CLASS_AVAILABLE @interface ORKConfirmTextAnswerFormat : ORKTextAnswerFormat @@ -89,6 +95,7 @@ ORK_CLASS_AVAILABLE @property (nonatomic, copy, readonly) NSString *errorMessage; @end +#endif @protocol ORKAnswerFormatPlatterPresentable diff --git a/ResearchKit/Common/ORKBodyItem.m b/ResearchKit/Common/ORKBodyItem.m index 4ae57909bf..14244d010b 100644 --- a/ResearchKit/Common/ORKBodyItem.m +++ b/ResearchKit/Common/ORKBodyItem.m @@ -30,20 +30,16 @@ 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]; @@ -54,7 +50,6 @@ - (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 6102d6ee31..a1dd41cc3c 100644 --- a/ResearchKit/Common/ORKBodyItem_Internal.h +++ b/ResearchKit/Common/ORKBodyItem_Internal.h @@ -34,11 +34,9 @@ NS_ASSUME_NONNULL_BEGIN @interface ORKBodyItem () -#if !TARGET_OS_WATCH @property (nonatomic, copy, nullable) void (^customButtonConfigurationHandler)(UIButton *button); - (instancetype)initWithCustomButtonConfigurationHandler:(void(^)(UIButton *button))configurationHandler; -#endif - (BOOL)isCustomButtonItemType; diff --git a/ResearchKit/Common/ORKCollectionResult.h b/ResearchKit/Common/ORKCollectionResult.h index 606695682a..2cdb6aba5e 100644 --- a/ResearchKit/Common/ORKCollectionResult.h +++ b/ResearchKit/Common/ORKCollectionResult.h @@ -28,7 +28,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#if TARGET_OS_IOS #import +#endif + NS_ASSUME_NONNULL_BEGIN diff --git a/ResearchKit/Common/ORKCollectionResult_Private.h b/ResearchKit/Common/ORKCollectionResult_Private.h index 2f5148cd2b..b35d1c2bc5 100644 --- a/ResearchKit/Common/ORKCollectionResult_Private.h +++ b/ResearchKit/Common/ORKCollectionResult_Private.h @@ -29,7 +29,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + + +#if TARGET_OS_IOS #import +#endif NS_ASSUME_NONNULL_BEGIN diff --git a/ResearchKit/Common/ORKCompletionStep.h b/ResearchKit/Common/ORKCompletionStep.h index b2602e2c9f..48b7094a6b 100644 --- a/ResearchKit/Common/ORKCompletionStep.h +++ b/ResearchKit/Common/ORKCompletionStep.h @@ -30,9 +30,10 @@ #import -#import + #if TARGET_OS_IOS +#import #import #endif diff --git a/ResearchKit/Common/ORKDataCollectionManager.h b/ResearchKit/Common/ORKDataCollectionManager.h index 3bbd488e11..f3bf5456ae 100644 --- a/ResearchKit/Common/ORKDataCollectionManager.h +++ b/ResearchKit/Common/ORKDataCollectionManager.h @@ -28,7 +28,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#if !TARGET_OS_VISION #import #import @@ -201,5 +200,3 @@ ORK_CLASS_AVAILABLE @end NS_ASSUME_NONNULL_END - -#endif diff --git a/ResearchKit/Common/ORKDevice.h b/ResearchKit/Common/ORKDevice.h index 70198e55ba..a54484461d 100644 --- a/ResearchKit/Common/ORKDevice.h +++ b/ResearchKit/Common/ORKDevice.h @@ -29,7 +29,10 @@ */ #import +#if TARGET_OS_IOS #import +#endif + NS_ASSUME_NONNULL_BEGIN diff --git a/ResearchKit/Common/ORKDevice_Private.h b/ResearchKit/Common/ORKDevice_Private.h index 6c5f87e347..b7a7134c17 100644 --- a/ResearchKit/Common/ORKDevice_Private.h +++ b/ResearchKit/Common/ORKDevice_Private.h @@ -28,7 +28,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#if TARGET_OS_IOS #import +#endif + NS_ASSUME_NONNULL_BEGIN diff --git a/ResearchKit/Common/ORKEarlyTerminationConfiguration.h b/ResearchKit/Common/ORKEarlyTerminationConfiguration.h index 41e935e226..a55748bb79 100644 --- a/ResearchKit/Common/ORKEarlyTerminationConfiguration.h +++ b/ResearchKit/Common/ORKEarlyTerminationConfiguration.h @@ -29,7 +29,11 @@ */ #import + +#if TARGET_OS_IOS #import +#endif + NS_ASSUME_NONNULL_BEGIN diff --git a/ResearchKit/Common/ORKErrors.h b/ResearchKit/Common/ORKErrors.h index 512e8003fa..3244068f00 100644 --- a/ResearchKit/Common/ORKErrors.h +++ b/ResearchKit/Common/ORKErrors.h @@ -30,7 +30,12 @@ #import + +#if TARGET_OS_IOS #import +#endif + + NS_ASSUME_NONNULL_BEGIN diff --git a/ResearchKit/Common/ORKFormStep.h b/ResearchKit/Common/ORKFormStep.h index 24005dfd8d..f564b25ad8 100644 --- a/ResearchKit/Common/ORKFormStep.h +++ b/ResearchKit/Common/ORKFormStep.h @@ -30,8 +30,12 @@ #import -#import + + +#if TARGET_OS_IOS #import +#import +#endif /** Values that determine the style diff --git a/ResearchKit/Common/ORKFormStep.m b/ResearchKit/Common/ORKFormStep.m index 943503c166..1a0a0bf4e4 100644 --- a/ResearchKit/Common/ORKFormStep.m +++ b/ResearchKit/Common/ORKFormStep.m @@ -39,10 +39,7 @@ 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 @@ -185,7 +182,7 @@ - (void)setFormItems:(NSArray *)formItems { } } -#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION && TARGET_OS_IOS +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION - (NSSet *)requestedHealthKitTypesForReading { NSMutableSet *healthTypes = [NSMutableSet set]; @@ -265,7 +262,6 @@ - (instancetype)initWithSectionTitle:(nullable NSString *)sectionTitle detailTex return self; } -#if TARGET_OS_IOS - (ORKFormItem *)confirmationAnswerFormItemWithIdentifier:(NSString *)identifier text:(nullable NSString *)text errorMessage:(NSString *)errorMessage { @@ -285,7 +281,6 @@ - (ORKFormItem *)confirmationAnswerFormItemWithIdentifier:(NSString *)identifier optional:self.optional]; return item; } -#endif + (BOOL)supportsSecureCoding { return YES; @@ -299,9 +294,7 @@ - (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; } @@ -318,9 +311,7 @@ - (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; } @@ -359,12 +350,8 @@ - (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 56d022c07c..3b5cdee54f 100644 --- a/ResearchKit/Common/ORKHealthAnswerFormat.h +++ b/ResearchKit/Common/ORKHealthAnswerFormat.h @@ -31,15 +31,12 @@ #import -#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION && TARGET_OS_IOS +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION #import #endif NS_ASSUME_NONNULL_BEGIN -@class ORKHealthKitCharacteristicTypeAnswerFormat; -@class ORKHealthKitQuantityTypeAnswerFormat; - /** An enumeration of biological sex options. */ @@ -77,7 +74,7 @@ ORK_EXTERN ORKBloodTypeIdentifier const ORKBloodTypeIdentifierONegative; You can use the HealthKit characteristic answer format to let users autofill information, such as their blood type or date of birth. */ -#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION && TARGET_OS_IOS +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION ORK_CLASS_AVAILABLE @interface ORKHealthKitCharacteristicTypeAnswerFormat : ORKAnswerFormat @@ -229,7 +226,6 @@ included in the question result generated by form items or question steps - (NSString *)localizedUnitString; @end - #endif - NS_ASSUME_NONNULL_END + diff --git a/ResearchKit/Common/ORKHelpers_Internal.h b/ResearchKit/Common/ORKHelpers_Internal.h index 65c521d6e2..40ce9fed07 100644 --- a/ResearchKit/Common/ORKHelpers_Internal.h +++ b/ResearchKit/Common/ORKHelpers_Internal.h @@ -29,13 +29,19 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#import -#import + #import -#import -#import +#if TARGET_OS_IOS #import +#import +#import +#endif + + +#import +#import + NS_ASSUME_NONNULL_BEGIN diff --git a/ResearchKit/Common/ORKHelpers_Private.h b/ResearchKit/Common/ORKHelpers_Private.h index d94b57f666..c021bfd702 100644 --- a/ResearchKit/Common/ORKHelpers_Private.h +++ b/ResearchKit/Common/ORKHelpers_Private.h @@ -30,7 +30,11 @@ #import +#if TARGET_OS_IOS #import +#endif + + NS_ASSUME_NONNULL_BEGIN diff --git a/ResearchKit/Common/ORKInstructionStep.h b/ResearchKit/Common/ORKInstructionStep.h index 773a4279cb..056a26bbef 100644 --- a/ResearchKit/Common/ORKInstructionStep.h +++ b/ResearchKit/Common/ORKInstructionStep.h @@ -28,8 +28,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + #import + + + +#if TARGET_OS_IOS #import +#endif NS_ASSUME_NONNULL_BEGIN diff --git a/ResearchKit/Common/ORKMotionActivityPermissionType.m b/ResearchKit/Common/ORKMotionActivityPermissionType.m index af3c8e9bf6..dbada52090 100644 --- a/ResearchKit/Common/ORKMotionActivityPermissionType.m +++ b/ResearchKit/Common/ORKMotionActivityPermissionType.m @@ -43,20 +43,25 @@ @interface ORKMotionActivityPermissionType() @property (nonatomic, readonly, assign) BOOL canContinue; @end -@implementation ORKMotionActivityPermissionType +@implementation ORKMotionActivityPermissionType { + __weak NSTimer *_checkStatusTimer; +} + (instancetype)new { return [[ORKMotionActivityPermissionType alloc] init]; } -- (instancetype)init -{ +- (instancetype)init { self = [super init]; if (self) { } return self; } +- (void)cleanUp { + [self _invalidateCheckStatusTimer]; +} + - (CMMotionActivityManager *)activityManager { if (!_activityManager) { _activityManager = [[CMMotionActivityManager alloc] init]; @@ -105,9 +110,33 @@ - (void)requestPermission { [self.activityManager startActivityUpdatesToQueue:[NSOperationQueue mainQueue] withHandler:^(CMMotionActivity * _Nullable activity) {}]; [self.activityManager stopActivityUpdates]; + if (self.permissionsStatusUpdateCallback != nil) { self.permissionsStatusUpdateCallback(); } + + if (!_checkStatusTimer) { + __weak typeof(self) weakSelf = self; + _checkStatusTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 + target:weakSelf + selector:@selector(_checkPermissionStatus) + userInfo:nil + repeats:YES]; + } +} + +- (void)_checkPermissionStatus { + if ([self permissionState] == ORKRequestPermissionsStateConnected && self.permissionsStatusUpdateCallback != nil) { + self.permissionsStatusUpdateCallback(); + [self _invalidateCheckStatusTimer]; + } +} + +- (void)_invalidateCheckStatusTimer { + if (_checkStatusTimer) { + [_checkStatusTimer invalidate]; + _checkStatusTimer = nil; + } } - (BOOL)isEqual:(id)object { diff --git a/ResearchKit/Common/ORKOrderedTask.h b/ResearchKit/Common/ORKOrderedTask.h index f655acca10..016f7bc8a6 100644 --- a/ResearchKit/Common/ORKOrderedTask.h +++ b/ResearchKit/Common/ORKOrderedTask.h @@ -30,7 +30,12 @@ #import + + +#if TARGET_OS_IOS #import +#endif + NS_ASSUME_NONNULL_BEGIN diff --git a/ResearchKit/Common/ORKOrderedTask.m b/ResearchKit/Common/ORKOrderedTask.m index 6f98005335..727346bed6 100644 --- a/ResearchKit/Common/ORKOrderedTask.m +++ b/ResearchKit/Common/ORKOrderedTask.m @@ -30,19 +30,16 @@ 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 "ORKFormStep.h" -#import "ORKFormItem_Internal.h" +#import "ORKStep_Private.h" #import "ORKHelpers_Internal.h" -#import "ORKInstructionStep.h" -#import "ORKQuestionStep.h" #import "ORKSkin.h" -#import "ORKStep_Private.h" - - #if TARGET_OS_IOS +#import "ORKQuestionStep.h" +#import "ORKFormStep.h" +#import "ORKFormItem_Internal.h" #import "ORKActiveStep_Internal.h" #import "ORKEarlyTerminationConfiguration.h" #endif @@ -266,6 +263,7 @@ - (ORKTaskTotalProgress)totalProgressOfCurrentStep:(ORKStep *)currentStep { int currentStepStartingProgressNumber = 0; for (ORKStep *step in self.steps) { +#if TARGET_OS_IOS if ([step isKindOfClass:[ORKFormStep class]]) { ORKFormStep *formStep = (ORKFormStep *)step; if (formStep.identifier == currentStep.identifier) { @@ -279,6 +277,14 @@ - (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; @@ -287,6 +293,7 @@ - (ORKTaskTotalProgress)totalProgressOfCurrentStep:(ORKStep *)currentStep { return totalProgress; } +#if TARGET_OS_IOS - (NSMutableArray *)calculateSectionsForFormItems:(NSArray *)formItems { NSMutableArray *_sections = [NSMutableArray new]; NSMutableArray *section = nil; @@ -367,7 +374,6 @@ - (BOOL)doesItemRequireSingleSection:(ORKFormItem *)item { return NO; } -#if TARGET_OS_IOS - (BOOL)providesBackgroundAudioPrompts { BOOL providesAudioPrompts = NO; for (ORKStep *step in self.steps) { diff --git a/ResearchKit/Common/ORKPageStep.h b/ResearchKit/Common/ORKPageStep.h index 5dc1402d66..f8ff19d4af 100644 --- a/ResearchKit/Common/ORKPageStep.h +++ b/ResearchKit/Common/ORKPageStep.h @@ -28,8 +28,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + +#if TARGET_OS_IOS #import #import +#endif + NS_ASSUME_NONNULL_BEGIN diff --git a/ResearchKit/Common/ORKPermissionType.h b/ResearchKit/Common/ORKPermissionType.h index 35a9ee5114..aa0c146014 100644 --- a/ResearchKit/Common/ORKPermissionType.h +++ b/ResearchKit/Common/ORKPermissionType.h @@ -32,7 +32,6 @@ #import #import -#import NS_ASSUME_NONNULL_BEGIN @@ -50,6 +49,7 @@ typedef NS_ENUM(NSInteger, ORKRequestPermissionsState) { ORKRequestPermissionsStateError, }; +typedef NS_OPTIONS(NSUInteger, UNAuthorizationOptions); typedef NSString * SRSensor NS_TYPED_ENUM API_AVAILABLE(ios(14.0)); /** @@ -69,6 +69,7 @@ ORK_CLASS_AVAILABLE @property (nonatomic, assign, readonly) BOOL canContinue; - (void)requestPermission; +- (void)cleanUp; + (ORKHealthKitPermissionType *)healthKitPermissionTypeWithSampleTypesToWrite:(nullable NSSet *)sampleTypesToWrite objectTypesToRead:(nullable NSSet *)objectTypesToRead; diff --git a/ResearchKit/Common/ORKPermissionType.m b/ResearchKit/Common/ORKPermissionType.m index 2283e980ab..5d99e4eeed 100644 --- a/ResearchKit/Common/ORKPermissionType.m +++ b/ResearchKit/Common/ORKPermissionType.m @@ -66,6 +66,10 @@ - (void)requestPermission { ORKThrowMethodUnavailableException(); } +- (void)cleanUp { + // left empty for optional subclass override +} + + (ORKHealthKitPermissionType *)healthKitPermissionTypeWithSampleTypesToWrite:(NSSet *)sampleTypesToWrite objectTypesToRead:(NSSet *)objectTypesToRead { return [[ORKHealthKitPermissionType alloc] initWithSampleTypesToWrite:sampleTypesToWrite objectTypesToRead:objectTypesToRead]; diff --git a/ResearchKit/Common/ORKQuestionResult.h b/ResearchKit/Common/ORKQuestionResult.h index c7c5a231dd..b9c84c21e4 100644 --- a/ResearchKit/Common/ORKQuestionResult.h +++ b/ResearchKit/Common/ORKQuestionResult.h @@ -28,13 +28,17 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#import +#if TARGET_OS_IOS #import +#endif -#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && TARGET_OS_IOS + +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION #import #endif +#import + NS_ASSUME_NONNULL_BEGIN @class CLCircularRegion; @@ -149,7 +153,7 @@ ORK_CLASS_AVAILABLE @end -#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && TARGET_OS_IOS +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION /** The `ORKLocation` class represents the location addess obtained from a location question. */ @@ -180,7 +184,7 @@ ORK_CLASS_AVAILABLE @property (nonatomic, copy, readonly, nullable) CNPostalAddress *postalAddress; @end -#endif +#endif /** A result object from a location answer format. @@ -192,8 +196,7 @@ ORK_CLASS_AVAILABLE completes, it may be appropriate to serialize it for transmission to a server, or to immediately perform analysis on it. */ - -#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && TARGET_OS_IOS +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION ORK_CLASS_AVAILABLE @interface ORKLocationQuestionResult : ORKQuestionResult @@ -203,7 +206,7 @@ ORK_CLASS_AVAILABLE @property (nonatomic, copy, nullable) ORKLocation *locationAnswer; @end -#endif +#endif /** A result object from a multiple-component picker-style choice-based answer format. diff --git a/ResearchKit/Common/ORKQuestionResult.m b/ResearchKit/Common/ORKQuestionResult.m index 37203a76dd..fffdc7b06e 100644 --- a/ResearchKit/Common/ORKQuestionResult.m +++ b/ResearchKit/Common/ORKQuestionResult.m @@ -28,18 +28,15 @@ 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 - -#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && TARGET_OS_IOS -#import -#import + +#import "ORKQuestionResult_Private.h" +#import "ORKResult_Private.h" + + +#if TARGET_OS_IOS +#import "ORKQuestionStep.h" +#import "ORKHelpers_Internal.h" +#import "ORKAnswerFormat_Internal.h" #endif @implementation ORKQuestionResult { @@ -329,7 +326,7 @@ - (NSDate *)dateAnswer { @end -#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && TARGET_OS_IOS +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION #pragma mark - ORKLocationQuestionResult @implementation ORKLocation @@ -471,6 +468,7 @@ - (ORKLocation *)locationAnswer { } @end + #endif #pragma mark - ORKSESQuestionResult diff --git a/ResearchKit/Common/ORKQuestionResult_Private.h b/ResearchKit/Common/ORKQuestionResult_Private.h index a8bbb2b696..c5bfbc78a2 100644 --- a/ResearchKit/Common/ORKQuestionResult_Private.h +++ b/ResearchKit/Common/ORKQuestionResult_Private.h @@ -28,10 +28,15 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + + +#if TARGET_OS_IOS #import +#endif #import + NS_ASSUME_NONNULL_BEGIN @interface ORKQuestionResult () @@ -44,10 +49,9 @@ NS_ASSUME_NONNULL_BEGIN @end -#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && TARGET_OS_IOS +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION @interface ORKLocation () - - (instancetype)initWithCoordinate:(CLLocationCoordinate2D)coordinate region:(nullable CLCircularRegion *)region userInput:(nullable NSString *)userInput @@ -56,6 +60,5 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithPlacemark:(CLPlacemark *)placemark userInput:(NSString *)userInput; @end -#endif - +#endif NS_ASSUME_NONNULL_END diff --git a/ResearchKit/Common/ORKResult.h b/ResearchKit/Common/ORKResult.h index 549dd5fd57..da7ffc0c10 100644 --- a/ResearchKit/Common/ORKResult.h +++ b/ResearchKit/Common/ORKResult.h @@ -29,7 +29,11 @@ */ #import + +#if TARGET_OS_IOS #import +#endif + NS_ASSUME_NONNULL_BEGIN diff --git a/ResearchKit/Common/ORKResult_Private.h b/ResearchKit/Common/ORKResult_Private.h index 1c62fade2f..9f41be217d 100644 --- a/ResearchKit/Common/ORKResult_Private.h +++ b/ResearchKit/Common/ORKResult_Private.h @@ -28,7 +28,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#if TARGET_OS_IOS #import +#endif + NS_ASSUME_NONNULL_BEGIN diff --git a/ResearchKit/Common/ORKSkin.h b/ResearchKit/Common/ORKSkin.h index 74d38ba5d5..b16c46fbee 100644 --- a/ResearchKit/Common/ORKSkin.h +++ b/ResearchKit/Common/ORKSkin.h @@ -28,8 +28,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + #import +#if TARGET_OS_IOS #import +#endif + NS_ASSUME_NONNULL_BEGIN diff --git a/ResearchKit/Common/ORKSkin_Private.h b/ResearchKit/Common/ORKSkin_Private.h index 1466bda858..9717032d36 100644 --- a/ResearchKit/Common/ORKSkin_Private.h +++ b/ResearchKit/Common/ORKSkin_Private.h @@ -30,7 +30,11 @@ */ #import + +#if TARGET_OS_IOS #import +#endif + #if TARGET_OS_IOS diff --git a/ResearchKit/Common/ORKStep.h b/ResearchKit/Common/ORKStep.h index c732ca098c..a6b53b65b0 100644 --- a/ResearchKit/Common/ORKStep.h +++ b/ResearchKit/Common/ORKStep.h @@ -28,9 +28,13 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + +#if TARGET_OS_IOS +#import +#endif + #import #import -#import @class HKObjectType; @class ORKResult; @@ -176,14 +180,14 @@ ORK_CLASS_AVAILABLE API_AVAILABLE(ios(11.0), watchos(6.0)) /** A property that gates automatic tint color image changes based on appearance changes. - The default value for this property is NO. + The default value for this property is NO. */ @property (nonatomic) BOOL shouldAutomaticallyAdjustImageTintColor; /** A property that determines whether to show progress for this step when presented. - The default is YES. + The default is YES. */ @property (nonatomic, assign) BOOL showsProgress; @@ -248,7 +252,7 @@ ORK_CLASS_AVAILABLE API_AVAILABLE(ios(11.0), watchos(6.0)) #pragma mark - iOS -#if TARGET_OS_IOS || TARGET_OS_VISION +#if TARGET_OS_IOS @class ORKBodyItem; @@ -309,7 +313,4 @@ API_AVAILABLE(ios(11)) #endif -#pragma mark - watchOS / VisionOS - - NS_ASSUME_NONNULL_END diff --git a/ResearchKit/Common/ORKStep_Private.h b/ResearchKit/Common/ORKStep_Private.h index 6ba5bfdaaf..998a7815c5 100644 --- a/ResearchKit/Common/ORKStep_Private.h +++ b/ResearchKit/Common/ORKStep_Private.h @@ -28,7 +28,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + +#if TARGET_OS_IOS #import +#endif NS_ASSUME_NONNULL_BEGIN diff --git a/ResearchKit/Common/ORKTask.h b/ResearchKit/Common/ORKTask.h index 02c3969291..ffe062b19c 100644 --- a/ResearchKit/Common/ORKTask.h +++ b/ResearchKit/Common/ORKTask.h @@ -35,7 +35,11 @@ #import #endif + +#if TARGET_OS_IOS #import +#endif + NS_ASSUME_NONNULL_BEGIN diff --git a/ResearchKit/Common/ORKTypes.h b/ResearchKit/Common/ORKTypes.h index e9a2eea8fc..9ee0041fd9 100644 --- a/ResearchKit/Common/ORKTypes.h +++ b/ResearchKit/Common/ORKTypes.h @@ -30,7 +30,11 @@ #import #import + + +#if TARGET_OS_IOS #import +#endif NS_ASSUME_NONNULL_BEGIN diff --git a/ResearchKit/Common/ORKVideoCaptureStep.h b/ResearchKit/Common/ORKVideoCaptureStep.h index 67dad5449d..c3d2bf39c0 100644 --- a/ResearchKit/Common/ORKVideoCaptureStep.h +++ b/ResearchKit/Common/ORKVideoCaptureStep.h @@ -87,14 +87,12 @@ ORK_CLASS_AVAILABLE */ @property (nonatomic, getter=isAudioMute) BOOL audioMute; -#if !TARGET_OS_VISION /** Constants indicating the desired torch mode to use The default value is `AVCaptureTorchModeAuto` (see `AVCaptureTorchMode`). */ @property (nonatomic) AVCaptureTorchMode torchMode; -#endif /** Constants indicating the physical position of an AVCaptureDevice's hardware on the system. diff --git a/ResearchKit/Common/ORKWebViewStep.h b/ResearchKit/Common/ORKWebViewStep.h index 387a1f7dfa..a588b682ef 100644 --- a/ResearchKit/Common/ORKWebViewStep.h +++ b/ResearchKit/Common/ORKWebViewStep.h @@ -63,10 +63,8 @@ NS_ASSUME_NONNULL_BEGIN @optional - (UIView * _Nullable)customHeaderViewForSignatureContent; -#if !TARGET_OS_VISION @optional - (UIScrollViewKeyboardDismissMode)keyboardDismissModeForCustomView; -#endif @end diff --git a/ResearchKit/Configuration/ResearchKit/ResearchKit-Shared.xcconfig b/ResearchKit/Configuration/ResearchKit/ResearchKit-Shared.xcconfig index fe80b618db..3b29f32f3e 100644 --- a/ResearchKit/Configuration/ResearchKit/ResearchKit-Shared.xcconfig +++ b/ResearchKit/Configuration/ResearchKit/ResearchKit-Shared.xcconfig @@ -23,7 +23,7 @@ PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*] = SWIFT_VERSION = 5.0 CLANG_STATIC_ANALYZER_MODE = deep -ORK_FRAMEWORK_VERSION_NUMBER = 3.1 +ORK_FRAMEWORK_VERSION_NUMBER = 3.1.1 ORK_FRAMEWORK_BUILD_NUMBER = $(ORK_FRAMEWORK_BUILD_NUMBER_CI_$(CI)) // ORK_FRAMEWORK_BUILD_NUMBER_CI_TRUE or ORK_FRAMEWORK_BUILD_NUMBER_CI_ ORK_FRAMEWORK_BUILD_NUMBER_CI_TRUE = $(CI_BUILD_NUMBER) diff --git a/ResearchKit/Onboarding/ORKRegistrationStep.m b/ResearchKit/Onboarding/ORKRegistrationStep.m index 40423eec95..69b2444446 100644 --- a/ResearchKit/Onboarding/ORKRegistrationStep.m +++ b/ResearchKit/Onboarding/ORKRegistrationStep.m @@ -73,13 +73,8 @@ static _Nullable id ORKFindInArrayByFormItemId(NSArray *array, NSString *formIte answerFormat.autocapitalizationType = UITextAutocapitalizationTypeNone; answerFormat.autocorrectionType = UITextAutocorrectionTypeNo; answerFormat.spellCheckingType = UITextSpellCheckingTypeNo; - - if (@available(iOS 12.0, *)) { - answerFormat.textContentType = UITextContentTypeNewPassword; - } else { - answerFormat.textContentType = UITextContentTypePassword; - } - + answerFormat.textContentType = UITextContentTypeOneTimeCode; + ORKFormItem *item = [[ORKFormItem alloc] initWithIdentifier:ORKRegistrationFormItemIdentifierPassword text:ORKLocalizedString(@"PASSWORD_FORM_ITEM_TITLE", nil) answerFormat:answerFormat diff --git a/ResearchKit/ResearchKit.docc/API Collections/AnswerFormats.md b/ResearchKit/ResearchKit.docc/API Collections/AnswerFormats.md index 3ee34b98c9..710cd2a8b7 100644 --- a/ResearchKit/ResearchKit.docc/API Collections/AnswerFormats.md +++ b/ResearchKit/ResearchKit.docc/API Collections/AnswerFormats.md @@ -18,7 +18,7 @@ Present a specific question and collect an answer. - ``ORKAnswerFormat`` - ``ORKNumericPrecision`` -### Height, and Weight +### Height and Weight - ``ORKHeightAnswerFormat`` - ``ORKWeightAnswerFormat`` diff --git a/ResearchKit/ResearchKit.h b/ResearchKit/ResearchKit.h index 3d921f55d9..d72f2bd3a6 100644 --- a/ResearchKit/ResearchKit.h +++ b/ResearchKit/ResearchKit.h @@ -29,42 +29,26 @@ */ -#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 #import @@ -77,15 +61,24 @@ #import #import #import +#import #import #import +#import +#import +#import +#import +#import #import +#import +#import #import #import #import +#import #import #import #import @@ -99,6 +92,10 @@ #import #import + +#import +#import + #import #import @@ -110,7 +107,11 @@ #import #import + +#import #import +#import + #import #import @@ -121,5 +122,3 @@ #import #import #import - -#endif diff --git a/ResearchKit/ResearchKit_Private.h b/ResearchKit/ResearchKit_Private.h index b1b99dae6f..b8a94726c4 100644 --- a/ResearchKit/ResearchKit_Private.h +++ b/ResearchKit/ResearchKit_Private.h @@ -28,35 +28,30 @@ 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 796b3b6e80..5f9ff9415f 100644 --- a/ResearchKit/Stale/ORKQuestionStep.h +++ b/ResearchKit/Stale/ORKQuestionStep.h @@ -30,7 +30,11 @@ #import + + +#if TARGET_OS_IOS #import +#endif NS_ASSUME_NONNULL_BEGIN diff --git a/ResearchKit/Stale/ORKQuestionStep.m b/ResearchKit/Stale/ORKQuestionStep.m index 34f352bd37..4de9b2d381 100644 --- a/ResearchKit/Stale/ORKQuestionStep.m +++ b/ResearchKit/Stale/ORKQuestionStep.m @@ -28,15 +28,15 @@ 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 +#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 diff --git a/ResearchKit/Stale/ORKQuestionStep_Private.h b/ResearchKit/Stale/ORKQuestionStep_Private.h index eb3e082866..37ae14b731 100644 --- a/ResearchKit/Stale/ORKQuestionStep_Private.h +++ b/ResearchKit/Stale/ORKQuestionStep_Private.h @@ -28,8 +28,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#import + + +#if TARGET_OS_IOS #import +#import +#endif NS_ASSUME_NONNULL_BEGIN diff --git a/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/ORKdBHLToneAudiometryStepViewController.m b/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/ORKdBHLToneAudiometryStepViewController.m index f13e0fa56a..47e9088533 100644 --- a/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/ORKdBHLToneAudiometryStepViewController.m +++ b/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/ORKdBHLToneAudiometryStepViewController.m @@ -122,18 +122,20 @@ - (ORKdBHLToneAudiometryAudioGenerator *)createAudioGeneratorFromHeadphoneType:( - (void)configureStep { ORKdBHLToneAudiometryStep *dBHLTAStep = [self dBHLToneAudiometryStep]; - self.dBHLToneAudiometryContentView = [[ORKdBHLToneAudiometryContentView alloc] init]; - self.activeStepView.activeCustomView = self.dBHLToneAudiometryContentView; - self.activeStepView.customContentFillsAvailableSpace = YES; - [self.activeStepView.navigationFooterView setHidden:YES]; + if (!self.dBHLToneAudiometryContentView) { + self.dBHLToneAudiometryContentView = [[ORKdBHLToneAudiometryContentView alloc] init]; + self.activeStepView.activeCustomView = self.dBHLToneAudiometryContentView; + self.activeStepView.customContentFillsAvailableSpace = YES; + [self.activeStepView.navigationFooterView setHidden:YES]; - [self.dBHLToneAudiometryContentView.tapButton addTarget:self action:@selector(tapButtonPressed) forControlEvents:UIControlEventTouchDown]; - - _audioChannel = dBHLTAStep.earPreference; - _audioGenerator = [self createAudioGeneratorFromHeadphoneType:dBHLTAStep.headphoneType]; - _audioGenerator.delegate = self; + [self.dBHLToneAudiometryContentView.tapButton addTarget:self action:@selector(tapButtonPressed) forControlEvents:UIControlEventTouchDown]; + + _audioChannel = dBHLTAStep.earPreference; + _audioGenerator = [self createAudioGeneratorFromHeadphoneType:dBHLTAStep.headphoneType]; + _audioGenerator.delegate = self; - _hapticFeedback = [[UIImpactFeedbackGenerator alloc] initWithStyle: UIImpactFeedbackStyleHeavy]; + _hapticFeedback = [[UIImpactFeedbackGenerator alloc] initWithStyle: UIImpactFeedbackStyleHeavy]; + } } - (void)addObservers { diff --git a/ResearchKitTests/ORKAnswerFormatTests.m b/ResearchKitTests/ORKAnswerFormatTests.m index 732e9779ba..859d44245c 100644 --- a/ResearchKitTests/ORKAnswerFormatTests.m +++ b/ResearchKitTests/ORKAnswerFormatTests.m @@ -1448,13 +1448,11 @@ - (void)testTextAnswerFormat { XCTAssertEqual([regexAnswerFormat isAnswerValidWithString:incorrectPhoneNumber], NO, @"Should return NO since it is not in the correct format"); } -#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION - (void)testLocationAnswerFormat { ORKLocationAnswerFormat *answerFormat = [ORKAnswerFormat locationAnswerFormat]; [answerFormat setUseCurrentLocation:YES]; XCTAssertEqual(answerFormat.useCurrentLocation, YES); } -#endif - (void)testWeightAnswerFormat { ORKWeightAnswerFormat *answerFormat = [ORKAnswerFormat weightAnswerFormatWithMeasurementSystem:ORKMeasurementSystemMetric diff --git a/ResearchKitTests/ORKJSONSerializationTests.m b/ResearchKitTests/ORKJSONSerializationTests.m index a70fb02aad..703f08e10b 100644 --- a/ResearchKitTests/ORKJSONSerializationTests.m +++ b/ResearchKitTests/ORKJSONSerializationTests.m @@ -55,7 +55,7 @@ @implementation TestCompilerFlagHelper + (NSArray *)_fetchExclusionList { NSArray *classesToExclude = @[]; - + #if !ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION NSArray *locationClasses = @[ @"ORKLocation", @@ -485,6 +485,7 @@ - (id)init { [ORKTouchAbilityPinchStep class], [ORKTouchAbilitySwipeStep class], [ORKTouchAbilityTapResult class], + [ORKTouchAbilityTouchTracker class], [ORKTouchAbilityRotationStep class], [ORKTouchAbilityLongPressStep class], [ORKTouchAbilityScrollStep class], @@ -619,6 +620,7 @@ - (id)init { @"ORKTimeIntervalAnswerFormat.defaultInterval", @"ORKTimeIntervalAnswerFormat.maximumInterval", @"ORKTimeIntervalAnswerFormat.step", + @"ORKTouchAbilityTouchTracker.delegate", @"ORKVerificationStep.verificationViewControllerClass", @"ORKVideoCaptureStep.templateImage", @"ORKWeightAnswerFormat.useMetricSystem", diff --git a/ResearchKitUI/Common/Answer Format/Choice Format Helpers/ORKColorChoiceCellGroup.h b/ResearchKitUI/Common/Answer Format/Choice Format Helpers/ORKColorChoiceCellGroup.h index 22893c8294..7ffa7f25dd 100644 --- a/ResearchKitUI/Common/Answer Format/Choice Format Helpers/ORKColorChoiceCellGroup.h +++ b/ResearchKitUI/Common/Answer Format/Choice Format Helpers/ORKColorChoiceCellGroup.h @@ -40,9 +40,6 @@ NS_ASSUME_NONNULL_BEGIN @class ORKColorChoiceAnswerFormat; @class ORKChoiceViewCell; @class ORKTextChoiceAnswerFormat; -@class ORKScaleAnswerFormat; -@class ORKContinuousScaleAnswerFormat; -@class ORKTextScaleAnswerFormat; @interface ORKColorChoiceCellGroup : NSObject diff --git a/ResearchKitUI/Common/Answer Format/Control Views/ORKLocationSelectionView.m b/ResearchKitUI/Common/Answer Format/Control Views/ORKLocationSelectionView.m index 8633c9b175..1c5a9d595e 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 ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION && TARGET_OS_IOS +#if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION #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/SwiftUI Views/SwiftUIViewFactory.swift b/ResearchKitUI/Common/Answer Format/SwiftUI Views/SwiftUIViewFactory.swift index 4b12392b00..cfbeedc625 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 ResearchKit import SwiftUI +import ResearchKit @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 35a4df3ece..4d3dae55b6 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 ResearchKit import SwiftUI +import ResearchKit struct TextChoiceView: View { @ObservedObject var textChoiceHelper: SwiftUITextChoiceHelper diff --git a/ResearchKitUI/Common/Step/Request Permissions Step/ORKRequestPermissionsStepViewController.m b/ResearchKitUI/Common/Step/Request Permissions Step/ORKRequestPermissionsStepViewController.m index 7e8a3d9db9..ee28c005dd 100644 --- a/ResearchKitUI/Common/Step/Request Permissions Step/ORKRequestPermissionsStepViewController.m +++ b/ResearchKitUI/Common/Step/Request Permissions Step/ORKRequestPermissionsStepViewController.m @@ -91,9 +91,18 @@ - (void)viewWillAppear:(BOOL)animated { } - (void)dealloc { + [self _cleanupPermissionTypes]; [[NSNotificationCenter defaultCenter] removeObserver:self name:ORKRequestPermissionsNotificationCardViewStatusChanged object:nil]; } +- (void)_cleanupPermissionTypes { + ORKRequestPermissionsStep *requestPermissionStep = [self requestPermissionsStep]; + + for (ORKPermissionType *permissionType in requestPermissionStep.permissionTypes) { + [permissionType cleanUp]; + } +} + - (ORKStepResult *)result { ORKStepResult *parentResult = [super result]; @@ -185,9 +194,13 @@ - (ORKRequestPermissionsStep *)requestPermissionsStep { NSMutableArray *cardViews = [NSMutableArray new]; for (ORKPermissionType *permissionType in requestPermissionStep.permissionTypes) { - ORKRequestPermissionView *cardView = [[ORKRequestPermissionView alloc] initWithIconImage:permissionType.image title:permissionType.localizedTitle detailText:permissionType.localizedDetailText]; + ORKRequestPermissionView *cardView = [[ORKRequestPermissionView alloc] initWithIconImage:permissionType.image + title:permissionType.localizedTitle + detailText:permissionType.localizedDetailText]; [cardView updateIconTintColor:permissionType.iconTintColor]; - [cardView.requestPermissionButton addTarget:permissionType action:@selector(requestPermission) forControlEvents:UIControlEventTouchUpInside]; + [cardView.requestPermissionButton addTarget:permissionType + action:@selector(requestPermission) + forControlEvents:UIControlEventTouchUpInside]; [self permissionStatusUpdatedForPermissionType:permissionType cardView:cardView]; // create the update callback From e9003d7d0a0bc16d2f5df8a1177bc851ab3c72c6 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Thu, 27 Mar 2025 21:38:07 -0700 Subject: [PATCH 09/12] fix: Make TextChoice Codable --- .../Questions/Response Options/TextChoice.swift | 2 +- .../Questions/Types/ImageChoiceQuestion.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ResearchKitSwiftUI/ResearchKitSwiftUI/Questions/Response Options/TextChoice.swift b/ResearchKitSwiftUI/ResearchKitSwiftUI/Questions/Response Options/TextChoice.swift index 33e1f0398a..b7e9b7d88a 100644 --- a/ResearchKitSwiftUI/ResearchKitSwiftUI/Questions/Response Options/TextChoice.swift +++ b/ResearchKitSwiftUI/ResearchKitSwiftUI/Questions/Response Options/TextChoice.swift @@ -31,7 +31,7 @@ import Foundation /// Represents a text choice. -public struct TextChoice: Identifiable { +public struct TextChoice: Identifiable, Codable { /// The id for this multiple choice option. public let id: String diff --git a/ResearchKitSwiftUI/ResearchKitSwiftUI/Questions/Types/ImageChoiceQuestion.swift b/ResearchKitSwiftUI/ResearchKitSwiftUI/Questions/Types/ImageChoiceQuestion.swift index 95124d45d1..54d0e3b216 100644 --- a/ResearchKitSwiftUI/ResearchKitSwiftUI/Questions/Types/ImageChoiceQuestion.swift +++ b/ResearchKitSwiftUI/ResearchKitSwiftUI/Questions/Types/ImageChoiceQuestion.swift @@ -126,7 +126,7 @@ public struct ImageChoice: Identifiable, Equatable { } /// Represents the number of of choices that can be selected. -public enum ChoiceSelectionLimit { +public enum ChoiceSelectionLimit: String, Codable { /// Allows for the selection of only one choice. case single From 0bb781c6a634a1277a2d6281deea1eafaa1817c2 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Mon, 31 Mar 2025 10:09:51 -0700 Subject: [PATCH 10/12] Make measurement codable --- .../ResearchKitSwiftUI/Questions/Types/HeightQuestion.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ResearchKitSwiftUI/ResearchKitSwiftUI/Questions/Types/HeightQuestion.swift b/ResearchKitSwiftUI/ResearchKitSwiftUI/Questions/Types/HeightQuestion.swift index 019cc95ab2..de59d8a29f 100644 --- a/ResearchKitSwiftUI/ResearchKitSwiftUI/Questions/Types/HeightQuestion.swift +++ b/ResearchKitSwiftUI/ResearchKitSwiftUI/Questions/Types/HeightQuestion.swift @@ -31,7 +31,7 @@ import SwiftUI /// Represents the different measurement systems that can be used. -public enum MeasurementSystem { +public enum MeasurementSystem: Codable { /// The US Customary measurement system. case USC From 53df187ba8ac4d5284fddd794023e5262d08c7d2 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Mon, 31 Mar 2025 17:01:30 -0700 Subject: [PATCH 11/12] Show ResearchKit cards properly --- .../Questions/Response Options/TextChoice.swift | 2 +- .../ResearchKitSwiftUI/Questions/Types/HeightQuestion.swift | 2 +- .../Questions/Types/ImageChoiceQuestion.swift | 2 +- ResearchKitSwiftUI/ResearchKitSwiftUI/Results/ResultValue.swift | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ResearchKitSwiftUI/ResearchKitSwiftUI/Questions/Response Options/TextChoice.swift b/ResearchKitSwiftUI/ResearchKitSwiftUI/Questions/Response Options/TextChoice.swift index b7e9b7d88a..c36ec3fc42 100644 --- a/ResearchKitSwiftUI/ResearchKitSwiftUI/Questions/Response Options/TextChoice.swift +++ b/ResearchKitSwiftUI/ResearchKitSwiftUI/Questions/Response Options/TextChoice.swift @@ -31,7 +31,7 @@ import Foundation /// Represents a text choice. -public struct TextChoice: Identifiable, Codable { +public struct TextChoice: Identifiable, Codable, Hashable { /// The id for this multiple choice option. public let id: String diff --git a/ResearchKitSwiftUI/ResearchKitSwiftUI/Questions/Types/HeightQuestion.swift b/ResearchKitSwiftUI/ResearchKitSwiftUI/Questions/Types/HeightQuestion.swift index de59d8a29f..d6675e3b68 100644 --- a/ResearchKitSwiftUI/ResearchKitSwiftUI/Questions/Types/HeightQuestion.swift +++ b/ResearchKitSwiftUI/ResearchKitSwiftUI/Questions/Types/HeightQuestion.swift @@ -31,7 +31,7 @@ import SwiftUI /// Represents the different measurement systems that can be used. -public enum MeasurementSystem: Codable { +public enum MeasurementSystem: Codable, Hashable { /// The US Customary measurement system. case USC diff --git a/ResearchKitSwiftUI/ResearchKitSwiftUI/Questions/Types/ImageChoiceQuestion.swift b/ResearchKitSwiftUI/ResearchKitSwiftUI/Questions/Types/ImageChoiceQuestion.swift index 54d0e3b216..2ff2ee2060 100644 --- a/ResearchKitSwiftUI/ResearchKitSwiftUI/Questions/Types/ImageChoiceQuestion.swift +++ b/ResearchKitSwiftUI/ResearchKitSwiftUI/Questions/Types/ImageChoiceQuestion.swift @@ -126,7 +126,7 @@ public struct ImageChoice: Identifiable, Equatable { } /// Represents the number of of choices that can be selected. -public enum ChoiceSelectionLimit: String, Codable { +public enum ChoiceSelectionLimit: String, Codable, Hashable { /// Allows for the selection of only one choice. case single diff --git a/ResearchKitSwiftUI/ResearchKitSwiftUI/Results/ResultValue.swift b/ResearchKitSwiftUI/ResearchKitSwiftUI/Results/ResultValue.swift index afbc40b22f..2581838a4f 100644 --- a/ResearchKitSwiftUI/ResearchKitSwiftUI/Results/ResultValue.swift +++ b/ResearchKitSwiftUI/ResearchKitSwiftUI/Results/ResultValue.swift @@ -31,7 +31,7 @@ import Foundation /// `ResultValue` is limited to a few question types that have different representations for their selected values. -public enum ResultValue: Codable { +public enum ResultValue: Codable, Hashable { case int(Int) case string(String) case date(Date) From aaec708366be9ee33af5097f1e074e0c2e130b17 Mon Sep 17 00:00:00 2001 From: Corey Baker Date: Tue, 12 Aug 2025 17:37:22 -0700 Subject: [PATCH 12/12] Update to ResearchKit 3.2.0 --- ResearchKit.xcodeproj/project.pbxproj | 44 +- ResearchKit.xctestplan | 6 +- ResearchKit/Common/ORKDataLogger.h | 6 +- ResearchKit/Common/ORKDataLogger.m | 77 +- ResearchKit/Common/ORKFormStep.m | 1 + ResearchKit/Common/ORKHelpers.m | 21 + ResearchKit/Common/ORKHelpers_Internal.h | 15 + .../ORKPredicateFormItemVisibilityRule.m | 9 +- ...KPredicateFormItemVisibilityRule_Private.h | 9 +- ResearchKit/Common/ORKRecorder.h | 254 +- ResearchKit/Common/ORKRecorder.m | 84 +- ResearchKit/Common/ORKRecorder.swift | 66 + ResearchKit/Common/ORKRecorder_Internal.h | 4 +- ResearchKit/Common/ORKRecorder_Private.h | 74 +- ResearchKit/Common/ORKStep.h | 12 +- ResearchKit/Common/ORKStepNavigationRule.m | 47 +- .../Common/ORKStepNavigationRule_Private.h | 36 + .../ResearchKit/ResearchKit-Shared.xcconfig | 2 +- .../Localized/en.lproj/ResearchKit.strings | 18 +- .../AmslerGrid/ORKAmslerGridResult.h | 23 +- .../AmslerGrid/ORKAmslerGridResult.m | 30 +- .../ORKAmslerGridStepViewController.m | 99 +- .../Active Step/ORKActiveStepViewController.m | 15 +- .../ORKOrderedTask+ORKPredefinedActiveTask.h | 2080 ++++++++++++----- .../ORKOrderedTask+ORKPredefinedActiveTask.m | 561 ++++- .../CMAccelerometerData+ORKJSONDictionary.m | 6 +- .../Accelerometer/ORKAccelerometerRecorder.h | 17 + .../Accelerometer/ORKAccelerometerRecorder.m | 47 +- .../Common/Recorders/Audio/ORKAudioRecorder.h | 19 +- .../Common/Recorders/Audio/ORKAudioRecorder.m | 55 +- .../Common/Recorders/Audio/ORKAudioStreamer.m | 25 +- .../Audio/ORKStreamingAudioRecorder.h | 17 +- .../Audio/ORKStreamingAudioRecorder.m | 34 +- .../CMLogItem+TimestampSince1970.swift | 78 + .../CMDeviceMotion+ORKJSONDictionary.m | 6 +- .../Device Motion/ORKDeviceMotionRecorder.h | 20 +- .../Device Motion/ORKDeviceMotionRecorder.m | 49 +- .../Health/ORKHealthClinicalTypeRecorder.h | 46 +- .../Health/ORKHealthClinicalTypeRecorder.m | 47 +- .../Health/ORKHealthQuantityTypeRecorder.h | 79 +- .../Health/ORKHealthQuantityTypeRecorder.m | 66 +- .../Recorders/Location/ORKLocationRecorder.h | 18 +- .../Recorders/Location/ORKLocationRecorder.m | 44 +- .../Pedometer/ORKPedometerRecorder.h | 15 +- .../Pedometer/ORKPedometerRecorder.m | 37 +- .../Common/Recorders/Touch/ORKTouchRecorder.h | 43 +- .../Common/Recorders/Touch/ORKTouchRecorder.m | 35 +- .../Reaction Time/ORKReactionTimeResult.h | 2 +- .../Reaction Time/ORKReactionTimeResult.m | 12 +- .../ORKReactionTimeViewController.m | 13 +- .../ORKdBHLToneAudiometryAudioGenerator.m | 7 +- .../frequency_dBSPL_AIRPODSV2.plist | 28 + .../ORKAudiometry/retspl_AIRPODSMAX.plist | 22 +- .../ORKAudiometry/retspl_AIRPODSPRO.plist | 22 +- .../ORKAudiometry/retspl_AIRPODSPROV2.plist | 22 +- .../ORKAudiometry/retspl_AIRPODSV2.plist | 28 + .../ORKAudiometry/retspl_AIRPODSV3.plist | 22 +- .../ORKAudiometry/retspl_EARPODS.plist | 22 +- .../volume_curve_AIRPODSV2.plist | 38 + .../ORKEnvironmentSPLMeterStep.h | 2 + .../ORKEnvironmentSPLMeterStep.m | 10 +- .../CMLogItem+timestampSince1970.swift | 101 + .../ORKActiveTaskResultTests.swift | 15 +- ResearchKitTests/ORKDataLoggerTests.m | 19 + ResearchKitTests/ORKESerialization.m | 74 +- ResearchKitTests/ORKJSONSerializationTests.m | 55 +- ...RKAccelerometerRecorderConfiguration.swift | 88 + .../ORKAudioRecorderConfiguration.swift | 88 + ...RKAudioStreamerRecorderConfiguration.swift | 84 + ...ORKDeviceMotionRecorderConfiguration.swift | 88 + ...lthClinicalTypeRecorderConfiguration.swift | 96 + ...althQuantityTypeRecordeConfiguration.swift | 97 + .../ORKLocationRecorderConfiguration.swift | 84 + .../ORKPedometerRecorderConfiguration.swift | 84 + .../ORKRecorderConfiguration.swift | 44 + ResearchKitTests/ORKRecorderTests.m | 94 +- .../ORKStepViewControllerTests.swift | 2 +- ...ORKAccelerometerRecorderConfiguration.json | 2 +- .../samples.bundle/ORKAmslerGridResult.json | 27 +- .../ORKAudioRecorderConfiguration.json | 2 +- .../ORKAudioStreamerConfiguration.json | 2 +- .../ORKDeviceMotionRecorderConfiguration.json | 2 +- ...althClinicalTypeRecorderConfiguration.json | 2 +- ...althQuantityTypeRecorderConfiguration.json | 2 +- .../ORKKeyValueStepModifier.json | 1 + .../ORKLocationRecorderConfiguration.json | 2 +- .../ORKPedometerRecorderConfiguration.json | 2 +- .../ORKPredicateStepNavigationRule.json | 2 +- .../samples.bundle/ORKReactionTimeResult.json | 2 +- .../ORKRecorderConfiguration.json | 2 +- .../ORKSpatialSpanMemoryGameRecord.json | 13 +- ...RKStreamingAudioRecorderConfiguration.json | 2 +- .../ORKTouchRecorderConfiguration.json | 2 +- .../Control Views/ORKScaleSliderView.m | 13 +- .../Form Step Views/ORKFormItemCell.m | 26 + .../Common/CheckmarkView/ORKCheckmarkView.m | 10 +- .../Container Views/ORKStepContentView.m | 2 - .../ORKFamilyHistoryRelatedPersonCell.m | 11 +- .../Form Step/ORKFormStepViewController.m | 97 +- .../Common/Task/ORKTaskViewController.h | 13 +- .../Common/Task/ORKTaskViewController.m | 6 + 101 files changed, 4891 insertions(+), 1113 deletions(-) create mode 100644 ResearchKit/Common/ORKRecorder.swift create mode 100644 ResearchKitActiveTask/Common/Recorders/CMLogItem+TimestampSince1970.swift create mode 100644 ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/frequency_dBSPL_AIRPODSV2.plist create mode 100644 ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/retspl_AIRPODSV2.plist create mode 100644 ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/volume_curve_AIRPODSV2.plist create mode 100644 ResearchKitTests/CMLogItem+timestampSince1970.swift create mode 100644 ResearchKitTests/ORKRecorderConfiguration/ORKAccelerometerRecorderConfiguration.swift create mode 100644 ResearchKitTests/ORKRecorderConfiguration/ORKAudioRecorderConfiguration.swift create mode 100644 ResearchKitTests/ORKRecorderConfiguration/ORKAudioStreamerRecorderConfiguration.swift create mode 100644 ResearchKitTests/ORKRecorderConfiguration/ORKDeviceMotionRecorderConfiguration.swift create mode 100644 ResearchKitTests/ORKRecorderConfiguration/ORKHealthClinicalTypeRecorderConfiguration.swift create mode 100644 ResearchKitTests/ORKRecorderConfiguration/ORKHealthQuantityTypeRecordeConfiguration.swift create mode 100644 ResearchKitTests/ORKRecorderConfiguration/ORKLocationRecorderConfiguration.swift create mode 100644 ResearchKitTests/ORKRecorderConfiguration/ORKPedometerRecorderConfiguration.swift create mode 100644 ResearchKitTests/ORKRecorderConfiguration/ORKRecorderConfiguration.swift create mode 100644 ResearchKitTests/samples.bundle/ORKKeyValueStepModifier.json diff --git a/ResearchKit.xcodeproj/project.pbxproj b/ResearchKit.xcodeproj/project.pbxproj index 7feb41e180..ba64e75347 100644 --- a/ResearchKit.xcodeproj/project.pbxproj +++ b/ResearchKit.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 70; objects = { /* Begin PBXAggregateTarget section */ @@ -193,6 +193,7 @@ 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, ); }; }; 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 */; }; @@ -303,6 +304,9 @@ 5EB91CC82BCE2EFD00BBF23E /* splMeter_sensitivity_offset.plist in Resources */ = {isa = PBXBuildFile; fileRef = 71F3B27F21001DEC00FB1C41 /* splMeter_sensitivity_offset.plist */; }; 5EB91CC92BCE2F1D00BBF23E /* SentencesList.txt in Resources */ = {isa = PBXBuildFile; fileRef = BA22F76C20C4F884006E6E11 /* SentencesList.txt */; }; 622774402C5CF7DC00F2E741 /* retspl_dBFS_AIRPODSPROV2.plist in Resources */ = {isa = PBXBuildFile; fileRef = 6227743F2C5CF7DC00F2E741 /* retspl_dBFS_AIRPODSPROV2.plist */; }; + 62FDB4AB2E2AD5E100E92AEA /* retspl_AIRPODSV2.plist in Resources */ = {isa = PBXBuildFile; fileRef = 62FDB4AA2E2AD5E100E92AEA /* retspl_AIRPODSV2.plist */; }; + 62FDB4AD2E2AD66900E92AEA /* volume_curve_AIRPODSV2.plist in Resources */ = {isa = PBXBuildFile; fileRef = 62FDB4AC2E2AD66900E92AEA /* volume_curve_AIRPODSV2.plist */; }; + 62FDB4AF2E2AD68100E92AEA /* frequency_dBSPL_AIRPODSV2.plist in Resources */ = {isa = PBXBuildFile; fileRef = 62FDB4AE2E2AD68100E92AEA /* frequency_dBSPL_AIRPODSV2.plist */; }; 714080DB235FD14700281E04 /* ResearchKit.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 714080D9235FD14700281E04 /* ResearchKit.stringsdict */; }; 714151D0225C4A23002CA33B /* ORKPasscodeViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14A92C6922444F93007547F2 /* ORKPasscodeViewControllerTests.swift */; }; 7141EA2222EFBC0C00650145 /* ORKLoggingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7141EA2122EFBC0C00650145 /* ORKLoggingTests.m */; }; @@ -369,6 +373,8 @@ 86CC8EBA1AC09383001CCD89 /* ORKResultTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 86CC8EAF1AC09383001CCD89 /* ORKResultTests.m */; }; 86CC8EBB1AC09383001CCD89 /* ORKTextChoiceCellGroupTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 86CC8EB01AC09383001CCD89 /* ORKTextChoiceCellGroupTests.m */; }; 86D348021AC161B0006DB02B /* ORKRecorderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 86D348001AC16175006DB02B /* ORKRecorderTests.m */; }; + 8A7BE76E2E0CA36900C63085 /* ORKRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A7BE76D2E0CA36400C63085 /* ORKRecorder.swift */; }; + 8A945EEF2DF3D9FD00D67122 /* CMLogItem+timestampSince1970.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A945EEE2DF3D86900D67122 /* CMLogItem+timestampSince1970.swift */; }; 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, ); }; }; @@ -411,6 +417,7 @@ 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 */; }; + C38934CD2DE912F4008DF53C /* CMLogItem+TimestampSince1970.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38934CC2DE912DC008DF53C /* CMLogItem+TimestampSince1970.swift */; }; 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, ); }; }; @@ -1539,6 +1546,9 @@ 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 = ""; }; + 62FDB4AA2E2AD5E100E92AEA /* retspl_AIRPODSV2.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = retspl_AIRPODSV2.plist; sourceTree = ""; }; + 62FDB4AC2E2AD66900E92AEA /* volume_curve_AIRPODSV2.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = volume_curve_AIRPODSV2.plist; sourceTree = ""; }; + 62FDB4AE2E2AD68100E92AEA /* frequency_dBSPL_AIRPODSV2.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = frequency_dBSPL_AIRPODSV2.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 = ""; }; @@ -1873,6 +1883,8 @@ 86CC8EAF1AC09383001CCD89 /* ORKResultTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ORKResultTests.m; sourceTree = ""; }; 86CC8EB01AC09383001CCD89 /* ORKTextChoiceCellGroupTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ORKTextChoiceCellGroupTests.m; sourceTree = ""; }; 86D348001AC16175006DB02B /* ORKRecorderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ORKRecorderTests.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 8A7BE76D2E0CA36400C63085 /* ORKRecorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ORKRecorder.swift; sourceTree = ""; }; + 8A945EEE2DF3D86900D67122 /* CMLogItem+timestampSince1970.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CMLogItem+timestampSince1970.swift"; sourceTree = ""; }; 8DE27B3E1D5BC072009A26E3 /* ORKHTMLPDFPageRenderer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ORKHTMLPDFPageRenderer.h; sourceTree = ""; }; 8DE27B3F1D5BC0B9009A26E3 /* ORKHTMLPDFPageRenderer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ORKHTMLPDFPageRenderer.m; sourceTree = ""; }; 9550E6711D58DBCF00C691B8 /* ORKTouchAnywhereStep.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ORKTouchAnywhereStep.h; sourceTree = ""; }; @@ -2062,6 +2074,7 @@ BF9155A41BDE8DA9007FA459 /* ORKWaitStepView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ORKWaitStepView.m; sourceTree = ""; }; BF9155A51BDE8DA9007FA459 /* ORKWaitStepViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ORKWaitStepViewController.h; sourceTree = ""; }; BF9155A61BDE8DA9007FA459 /* ORKWaitStepViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ORKWaitStepViewController.m; sourceTree = ""; }; + C38934CC2DE912DC008DF53C /* CMLogItem+TimestampSince1970.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CMLogItem+TimestampSince1970.swift"; sourceTree = ""; }; C72B9DF1235695DC00B982B7 /* no */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = no; path = no.lproj/ResearchKit.strings; sourceTree = ""; }; C72B9DF42356960F00B982B7 /* pt_BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt_BR; path = pt_BR.lproj/ResearchKit.strings; sourceTree = ""; }; CA08053F28AD7CC8001695EF /* ORKViewControllerProviding.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ORKViewControllerProviding.h; sourceTree = ""; }; @@ -2180,6 +2193,10 @@ FFF65AB71E318F2D0043FB40 /* ORKMultipleValuePicker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ORKMultipleValuePicker.m; sourceTree = ""; }; /* End PBXFileReference section */ +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 8A18ADB32E0F374100BD0A88 /* ORKRecorderConfiguration */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = ORKRecorderConfiguration; sourceTree = ""; }; +/* End PBXFileSystemSynchronizedRootGroup section */ + /* Begin PBXFrameworksBuildPhase section */ 86CC8E971AC09332001CCD89 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; @@ -2925,6 +2942,8 @@ 86CC8EA61AC09383001CCD89 /* ResearchKitTests */ = { isa = PBXGroup; children = ( + 8A18ADB32E0F374100BD0A88 /* ORKRecorderConfiguration */, + 8A945EEE2DF3D86900D67122 /* CMLogItem+timestampSince1970.swift */, 519CE85D2C6BC1C5003BB584 /* Family History */, 51EB9A592B8D1CB80064A515 /* FormatterTests */, 51AF19512B583B5B00D3B399 /* ORKESerializationTests */, @@ -3225,6 +3244,7 @@ BC4194261AE8451F00073D6B /* Misc */ = { isa = PBXGroup; children = ( + 8A7BE76D2E0CA36400C63085 /* ORKRecorder.swift */, 03BD9EA1253E62A0008ADBE1 /* ORKBundleAsset.h */, 03BD9EA2253E62A0008ADBE1 /* ORKBundleAsset.m */, 86C40B471A8D7C5B00081FAC /* ORKRecorder.h */, @@ -4069,16 +4089,19 @@ E293655D25757CC700092A7C /* frequency_dBSPL_AIRPODSMAX.plist */, E29189B823855B96001AFF0F /* frequency_dBSPL_AIRPODSPRO.plist */, 71B7B4D820AA91D400C5768A /* frequency_dBSPL_AIRPODS.plist */, + 62FDB4AE2E2AD68100E92AEA /* frequency_dBSPL_AIRPODSV2.plist */, 2246E5122749350200261D5A /* frequency_dBSPL_AIRPODSV3.plist */, 713D4B1B20FE5464002BE28D /* frequency_dBSPL_EARPODS.plist */, E293655F25757E6100092A7C /* retspl_AIRPODSMAX.plist */, E29189BC23855BAE001AFF0F /* retspl_AIRPODSPRO.plist */, 71B7B4D420AA91D300C5768A /* retspl_AIRPODS.plist */, + 62FDB4AA2E2AD5E100E92AEA /* retspl_AIRPODSV2.plist */, 2246E5132749350200261D5A /* retspl_AIRPODSV3.plist */, 713D4B1D20FE5480002BE28D /* retspl_EARPODS.plist */, E293656125757E7700092A7C /* volume_curve_AIRPODSMAX.plist */, E29189BA23855BA2001AFF0F /* volume_curve_AIRPODSPRO.plist */, 71B7B4D920AA91D400C5768A /* volume_curve_AIRPODS.plist */, + 62FDB4AC2E2AD66900E92AEA /* volume_curve_AIRPODSV2.plist */, 2246E5142749350200261D5A /* volume_curve_AIRPODSV3.plist */, 713D4B0F20FE4702002BE28D /* volume_curve_WIRED.plist */, 224CD4FC283540FF0029B820 /* ORKAudiometryProtocol.h */, @@ -4190,6 +4213,7 @@ CAD0898D289DDBF5007B2A98 /* Recorders */ = { isa = PBXGroup; children = ( + C38934CC2DE912DC008DF53C /* CMLogItem+TimestampSince1970.swift */, CAD08994289DDC2D007B2A98 /* Accelerometer */, CAD08993289DDC29007B2A98 /* Audio */, CAD08992289DDC25007B2A98 /* Device Motion */, @@ -5164,6 +5188,9 @@ CAF8D2BA288F2DCB001E6992 /* PBXTargetDependency */, 86CC8EA21AC09332001CCD89 /* PBXTargetDependency */, ); + fileSystemSynchronizedGroups = ( + 8A18ADB32E0F374100BD0A88 /* ORKRecorderConfiguration */, + ); name = ResearchKitTests; productName = "ResearchKit Tests"; productReference = 86CC8E9A1AC09332001CCD89 /* ResearchKitTests.xctest */; @@ -5177,7 +5204,7 @@ B183A4F71A8535D100C76870 /* Sources */, B183A5591A8535D100C76870 /* Frameworks */, B183A5681A8535D100C76870 /* Resources */, - B1429E9C1AAA651C003DE546 /* ShellScript */, + B1429E9C1AAA651C003DE546 /* Run Script */, ); buildRules = ( ); @@ -5253,6 +5280,7 @@ }; CAD08966289DD747007B2A98 = { CreatedOnToolsVersion = 13.4.1; + LastSwiftMigration = 1700; ProvisioningStyle = Manual; }; }; @@ -5361,9 +5389,12 @@ 51BD8FCF29D60FB60001D54E /* retspl_AIRPODSV3.plist in Resources */, 51BD8FD129D60FB60001D54E /* retspl_AIRPODS.plist in Resources */, 5192BF7D2AE1A87D006E43FB /* retspl_AIRPODSPROV2.plist in Resources */, + 62FDB4AD2E2AD66900E92AEA /* volume_curve_AIRPODSV2.plist in Resources */, 51BD8FCA29D60FB60001D54E /* frequency_dBSPL_EARPODS.plist in Resources */, 0BD9B60D2B75991B00A64EF9 /* Sentence1.wav in Resources */, + 62FDB4AF2E2AD68100E92AEA /* frequency_dBSPL_AIRPODSV2.plist in Resources */, 51BD8FD529D60FB60001D54E /* volume_curve_AIRPODSPRO.plist in Resources */, + 62FDB4AB2E2AD5E100E92AEA /* retspl_AIRPODSV2.plist in Resources */, 51BD8FCD29D60FB60001D54E /* retspl_AIRPODSMAX.plist in Resources */, 51BD8FD629D60FB60001D54E /* retspl_AIRPODSPRO.plist in Resources */, 51AF1B232B69803A00D3B399 /* Window.wav in Resources */, @@ -5389,7 +5420,7 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - B1429E9C1AAA651C003DE546 /* ShellScript */ = { + B1429E9C1AAA651C003DE546 /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; @@ -5397,6 +5428,7 @@ ); inputPaths = ( ); + name = "Run Script"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -5437,6 +5469,7 @@ 2EBFE1201AE1B74100CB8254 /* ORKVoiceEngineTests.m in Sources */, 51AF19662B583D0F00D3B399 /* ORKESerialization.m in Sources */, BCAD50E81B0201EE0034806A /* ORKTaskTests.m in Sources */, + 8A945EEF2DF3D9FD00D67122 /* CMLogItem+timestampSince1970.swift in Sources */, CA0AC56928BD4FAC00E80040 /* ORKStepViewControllerHelpers.swift in Sources */, 86CC8EBB1AC09383001CCD89 /* ORKTextChoiceCellGroupTests.m in Sources */, FA7A9D2B1B082688005A2BEA /* ORKConsentDocumentTests.m in Sources */, @@ -5508,6 +5541,7 @@ CA2B901228A17D170025B773 /* ORKFileResult.m in Sources */, 866DA5241D63D04700C9AF3F /* ORKDataCollectionManager.m in Sources */, 86C40D421A8D7C5C00081FAC /* ORKInstructionStep.m in Sources */, + 8A7BE76E2E0CA36900C63085 /* ORKRecorder.swift in Sources */, FF919A5A1E81C628005C2A1E /* ORKQuestionResult.m in Sources */, BA473FE9224DB38900A362E3 /* ORKBodyItem.m in Sources */, 86C40E1A1A8D7C5C00081FAC /* ORKConsentSection.m in Sources */, @@ -5741,6 +5775,7 @@ CAD08A9D289DE7B5007B2A98 /* ORKWalkingTaskStep.m in Sources */, CAD08A0E289DE4E2007B2A98 /* ORKAudiometry.m in Sources */, 51A11F232BD152660060C07E /* ORKActiveStepCustomView.m in Sources */, + C38934CD2DE912F4008DF53C /* CMLogItem+TimestampSince1970.swift in Sources */, CA2B8FD128A176B40025B773 /* ORKRangeOfMotionStepViewController.m in Sources */, CA2B8F8228A16CF40025B773 /* ORK3DModelStepViewController.m in Sources */, 5156CA612B7E465500983535 /* ORKTouchAbilityRotationTrial.m in Sources */, @@ -6209,6 +6244,8 @@ SKIP_INSTALL = YES; SUPPORTS_MACCATALYST = NO; SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = ""; VERSION_INFO_PREFIX = ""; @@ -6279,6 +6316,7 @@ SKIP_INSTALL = YES; SUPPORTS_MACCATALYST = NO; SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = NO; VERSIONING_SYSTEM = ""; diff --git a/ResearchKit.xctestplan b/ResearchKit.xctestplan index f9a4dafcff..10504d9f1c 100644 --- a/ResearchKit.xctestplan +++ b/ResearchKit.xctestplan @@ -14,11 +14,7 @@ "testTargets" : [ { "skippedTests" : [ - "ORKDataCollectionTests", - "ORKJSONSerializationTests\/testEquality", - "ORKJSONSerializationTests\/testORKSampleDeserialization", - "ORKJSONSerializationTests\/testORKSerialization", - "ORKJSONSerializationTests\/testSecureCoding" + "ORKDataCollectionTests" ], "target" : { "containerPath" : "container:ResearchKit.xcodeproj", diff --git a/ResearchKit/Common/ORKDataLogger.h b/ResearchKit/Common/ORKDataLogger.h index 9dc21f88bc..02415e2ee5 100644 --- a/ResearchKit/Common/ORKDataLogger.h +++ b/ResearchKit/Common/ORKDataLogger.h @@ -122,12 +122,13 @@ ORK_CLASS_AVAILABLE @param url The URL of the directory in which to place log files @param logName The prefix on the log file name in an ASCII string. Note that the string must not contain the hyphen character ("-"), because a hyphen is used as a separator in the log naming scheme. + @param fileExtension The file extension to use when creating files on disk. @param formatter The type of formatter to use for the log, such as `ORKJSONLogFormatter`. @param delegate The initial delegate. May be `nil`. @return An initialized data logger. */ -- (instancetype)initWithDirectory:(NSURL *)url logName:(NSString *)logName formatter:(ORKLogFormatter *)formatter delegate:(nullable id)delegate NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithDirectory:(NSURL *)url logName:(NSString *)logName fileExtension:(NSString *)fileExtension formatter:(ORKLogFormatter *)formatter delegate:(nullable id)delegate NS_DESIGNATED_INITIALIZER; /// The delegate to be notified when file sizes change or the log rolls over. @property (weak, nullable) id delegate; @@ -483,12 +484,13 @@ ORK_CLASS_AVAILABLE Adds a data logger with a particular formatter to the directory. @param logName The log name prefix for the data logger. + @param fileExtension The file extension to use when creating files on disk. @param formatter The log formatter instance to use for this logger. @return The `ORKDataLogger` object that was added, or the existing one if one already existed for that log name. */ -- (ORKDataLogger *)addDataLoggerForLogName:(NSString *)logName formatter:(ORKLogFormatter *)formatter; +- (ORKDataLogger *)addDataLoggerForLogName:(NSString *)logName fileExtension:(NSString *)fileExtension formatter:(ORKLogFormatter *)formatter; /** Retrieves the already existing data logger for a log name. diff --git a/ResearchKit/Common/ORKDataLogger.m b/ResearchKit/Common/ORKDataLogger.m index 558554985c..47bb0ac427 100644 --- a/ResearchKit/Common/ORKDataLogger.m +++ b/ResearchKit/Common/ORKDataLogger.m @@ -73,25 +73,30 @@ - (void)resume; @implementation NSURL (ORKDataLogger) +- (NSString *)ork_fileName { + NSString *fileName = [[self lastPathComponent] stringByDeletingPathExtension]; + return fileName; +} + - (NSString *)ork_logName { - NSString *lastComponent = [self lastPathComponent]; - NSRange idx = [lastComponent rangeOfString:@"-"]; + NSString *fileName = [self ork_fileName]; + NSRange idx = [fileName rangeOfString:@"-"]; if (!idx.length) { @throw [NSException exceptionWithName:NSGenericException reason:@"URL is not a completed log file" userInfo:@{@"url":self}]; } - NSString *logName = [lastComponent substringToIndex:idx.location]; + NSString *logName = [fileName substringToIndex:idx.location]; return logName; } - (NSString *)ork_logDateComponent { - NSString *lastComponent = [self lastPathComponent]; - NSRange idx = [lastComponent rangeOfString:@"-"]; + NSString *fileName = [self ork_fileName]; + NSRange idx = [fileName rangeOfString:@"-"]; if (!idx.length) { @throw [NSException exceptionWithName:NSGenericException reason:@"URL is not a completed log file" userInfo:@{@"url":self}]; } - NSString *logDateComponent = [lastComponent substringFromIndex:idx.location + 1]; + NSString *logDateComponent = [fileName substringFromIndex:idx.location + 1]; return logDateComponent; } @@ -145,8 +150,8 @@ - (NSString *)ork_logNameInDirectory:(NSURL *)directory { @throw [NSException exceptionWithName:NSGenericException reason:@"URL is not a fileURL" userInfo:@{@"url":self}]; } - NSString *lastComponent = [self lastPathComponent]; - NSRange idx = [lastComponent rangeOfString:@"-"]; + NSString *fileName = [self ork_fileName]; + NSRange idx = [fileName rangeOfString:@"-"]; if (!idx.length) { @throw [NSException exceptionWithName:NSGenericException reason:@"URL is not a completed log file" userInfo:@{@"url":self}]; } @@ -155,7 +160,7 @@ - (NSString *)ork_logNameInDirectory:(NSURL *)directory { @throw [NSException exceptionWithName:NSGenericException reason:@"URL is not in expected directory" userInfo:@{@"url":self}]; } - NSString *logName = [lastComponent substringToIndex:idx.location]; + NSString *logName = [fileName substringToIndex:idx.location]; return logName; } @@ -446,6 +451,7 @@ @implementation ORKDataLogger { ORKObjectObserver *_observer; NSString *_oldLogsPrefix; + NSString *_fileExtension; NSFileHandle *_currentFileHandle; @@ -457,7 +463,7 @@ @implementation ORKDataLogger { } + (ORKDataLogger *)JSONDataLoggerWithDirectory:(NSURL *)url logName:(NSString *)logName delegate:(id)delegate { - return [[ORKDataLogger alloc] initWithDirectory:url logName:logName formatter:[ORKJSONLogFormatter new] delegate:delegate]; + return [[ORKDataLogger alloc] initWithDirectory:url logName:logName fileExtension:@"json" formatter:[ORKJSONLogFormatter new] delegate:delegate]; } + (instancetype)new { @@ -468,7 +474,7 @@ - (instancetype)init { ORKThrowMethodUnavailableException(); } -- (instancetype)initWithDirectory:(NSURL *)url logName:(NSString *)logName formatter:(ORKLogFormatter *)formatter delegate:(id)delegate { +- (instancetype)initWithDirectory:(NSURL *)url logName:(NSString *)logName fileExtension:(NSString *)fileExtension formatter:(ORKLogFormatter *)formatter delegate:(id)delegate { self = [super init]; if (self) { _url = [url copy]; @@ -486,13 +492,13 @@ - (instancetype)initWithDirectory:(NSURL *)url logName:(NSString *)logName forma _queue = dispatch_queue_create([queueId cStringUsingEncoding:NSUTF8StringEncoding], DISPATCH_QUEUE_SERIAL); _directoryUpdateGroup = dispatch_group_create(); - + self.logName = logName; self.logFormatter = formatter; self.delegate = delegate; self.fileProtectionMode = ORKFileProtectionNone; _oldLogsPrefix = [_logName stringByAppendingString:@"-"]; - + _fileExtension = fileExtension; _observer = [[ORKObjectObserver alloc] initWithObject:self keys:@[@"maximumCurrentLogFileLifetime", @"maximumCurrentLogFileSize"] selector:@selector(fileSizeLimitsDidChange)]; [self setupDirectorySource]; @@ -506,7 +512,7 @@ - (instancetype)initWithDirectory:(NSURL *)url configuration:(NSDictionary *)con @throw [NSException exceptionWithName:NSGenericException reason:[NSString stringWithFormat:@"%@ is not a class", configuration[@"formatterClass"]] userInfo:nil]; } - self = [self initWithDirectory:url logName:configuration[@"logName"] formatter:[[formatterClass alloc] init] delegate:delegate]; + self = [self initWithDirectory:url logName:configuration[@"logName"] fileExtension:configuration[@"fileExtension"] formatter:[[formatterClass alloc] init] delegate:delegate]; if (self) { // Don't notify about initial setup [_observer pause]; @@ -519,6 +525,7 @@ - (instancetype)initWithDirectory:(NSURL *)url configuration:(NSDictionary *)con - (NSDictionary *)configuration { return @{@"logName": self.logName, + @"fileExtension": _fileExtension, @"formatterClass": NSStringFromClass([self.logFormatter class]), @"fileProtectionMode": @(self.fileProtectionMode), @"maximumCurrentLogFileSize": @(self.maximumCurrentLogFileSize), @@ -574,12 +581,18 @@ - (void)finishCurrentLog { } - (NSURL *)currentLogFileURL { - return [_url URLByAppendingPathComponent:_logName]; + return [[_url URLByAppendingPathComponent:_logName] URLByAppendingPathExtension:_fileExtension]; } -- (BOOL)urlMatchesLogName:(NSURL *)url { - NSString *lastComponent = [url lastPathComponent]; - return ([lastComponent isEqualToString:_logName] || [lastComponent hasPrefix:_oldLogsPrefix]); +- (BOOL)doesURLMatchLogName:(NSURL *)url { + NSString *fileName = [url ork_fileName]; + return [fileName isEqualToString:_logName]; +} + +- (BOOL)didCreateLogAt:(NSURL *)url { + BOOL doesURLMatchLogName = [self doesURLMatchLogName:url]; + NSString *fileName = [url ork_fileName]; + return (doesURLMatchLogName || [fileName hasPrefix:_oldLogsPrefix]); } - (NSFileHandle *)fileHandle { @@ -724,10 +737,10 @@ - (BOOL)queue_enumerateLogs:(void (^)(NSURL *logFileUrl, BOOL *stop))block error NSError *error = nil; NSMutableArray *urls = [NSMutableArray array]; for (NSURL *url in enumerator) { - if (![self urlMatchesLogName:url]) { + if (![self didCreateLogAt:url]) { continue; } - if ( [[url lastPathComponent] isEqualToString:_logName]) { + if ([self doesURLMatchLogName:url]) { // Don't include the "current" log file continue; } @@ -839,7 +852,7 @@ - (NSFileHandle *)queue_fileHandleWithError:(NSError **)errorOut { return _currentFileHandle; } -+ (NSURL *)nextUrlForDirectoryUrl:(NSURL *)directory logName:(NSString *)logName { ++ (NSURL *)nextUrlForDirectoryUrl:(NSURL *)directory logName:(NSString *)logName fileExtension:(NSString *)fileExtension { static NSDateFormatter *dateFromatter = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ @@ -849,14 +862,14 @@ + (NSURL *)nextUrlForDirectoryUrl:(NSURL *)directory logName:(NSString *)logName }); NSString *datedLog = [NSString stringWithFormat:@"%@-%@",logName, [dateFromatter stringFromDate:[NSDate date]]]; - NSURL *destinationUrl = [directory URLByAppendingPathComponent:datedLog]; + NSURL *destinationUrl = [[directory URLByAppendingPathComponent:datedLog] URLByAppendingPathExtension:fileExtension]; NSFileManager *fileManager = [NSFileManager defaultManager]; int digit = 0; while ([fileManager fileExistsAtPath:[destinationUrl path] isDirectory:NULL]) { digit ++; - NSString *lastComponent = [datedLog stringByAppendingFormat:@"-%02d",digit]; - destinationUrl = [directory URLByAppendingPathComponent:lastComponent]; + NSString *newDatedLog = [datedLog stringByAppendingFormat:@"-%02d",digit]; + destinationUrl = [[directory URLByAppendingPathComponent:newDatedLog] URLByAppendingPathExtension:fileExtension]; } return destinationUrl; @@ -878,7 +891,7 @@ - (void)queue_closeAndRenameLog { if (((NSNumber *)parameters[NSURLIsRegularFileKey]).boolValue) { if (((NSNumber *)parameters[NSURLFileSizeKey]).intValue > 0) { - NSURL *destinationUrl = [ORKDataLogger nextUrlForDirectoryUrl:_url logName:_logName]; + NSURL *destinationUrl = [ORKDataLogger nextUrlForDirectoryUrl:_url logName:_logName fileExtension:_fileExtension]; ORK_Log_Debug("Rollover: %@ to %@", [url lastPathComponent], [destinationUrl lastPathComponent]); [fileManager moveItemAtURL:url toURL:destinationUrl error:nil]; if (self.fileProtectionMode == ORKFileProtectionCompleteUnlessOpen) { @@ -1135,17 +1148,17 @@ - (void)configurationDidChange { } - (ORKDataLogger *)addJSONDataLoggerForLogName:(NSString *)logName { - return [self addDataLoggerForLogName:logName formatter:[ORKJSONLogFormatter new]]; + return [self addDataLoggerForLogName:logName fileExtension:@"json" formatter:[ORKJSONLogFormatter new]]; } -- (ORKDataLogger *)queue_addDataLoggerForLogName:(NSString *)logName formatter:(ORKLogFormatter *)formatter { - ORKDataLogger *dataLogger = [[ORKDataLogger alloc] initWithDirectory:_directory logName:logName formatter:formatter delegate:self]; - dataLogger.delegate = nil; +- (ORKDataLogger *)queue_addDataLoggerForLogName:(NSString *)logName fileExtension:(NSString *)fileExtension formatter:(ORKLogFormatter *)formatter { + ORKDataLogger *dataLogger = [[ORKDataLogger alloc] initWithDirectory:_directory logName:logName fileExtension:fileExtension formatter:formatter delegate:self]; + // Pick suitable defaults for a typical use pattern dataLogger.maximumCurrentLogFileLifetime = ORKDataLoggerManagerDefaultLogFileLifetime; dataLogger.maximumCurrentLogFileSize = ORKDataLoggerManagerDefaultLogFileSize; dataLogger.delegate = self; - + _records[logName] = dataLogger; [self queue_synchronizeConfiguration]; @@ -1154,14 +1167,14 @@ - (ORKDataLogger *)queue_addDataLoggerForLogName:(NSString *)logName formatter:( return dataLogger; } -- (ORKDataLogger *)addDataLoggerForLogName:(NSString *)logName formatter:(ORKLogFormatter *)formatter { +- (ORKDataLogger *)addDataLoggerForLogName:(NSString *)logName fileExtension:(NSString *)fileExtension formatter:(ORKLogFormatter *)formatter { if (_records[logName]) { @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"Duplicate logger with log name '%@'",logName] userInfo:nil]; } __block ORKDataLogger *dataLogger = nil; dispatch_sync(_queue, ^{ - dataLogger = [self queue_addDataLoggerForLogName:logName formatter:formatter]; + dataLogger = [self queue_addDataLoggerForLogName:logName fileExtension:fileExtension formatter:formatter]; }); return dataLogger; } diff --git a/ResearchKit/Common/ORKFormStep.m b/ResearchKit/Common/ORKFormStep.m index 1a0a0bf4e4..372ace0b79 100644 --- a/ResearchKit/Common/ORKFormStep.m +++ b/ResearchKit/Common/ORKFormStep.m @@ -55,6 +55,7 @@ - (instancetype)initWithIdentifier:(NSString *)identifier self.useCardView = YES; self.autoScrollEnabled = YES; self.cardViewStyle = ORKCardViewStyleDefault; + self.headerTextAlignment = NSTextAlignmentNatural; } return self; } diff --git a/ResearchKit/Common/ORKHelpers.m b/ResearchKit/Common/ORKHelpers.m index 1fda3e8cb9..0c1d7569fc 100644 --- a/ResearchKit/Common/ORKHelpers.m +++ b/ResearchKit/Common/ORKHelpers.m @@ -560,3 +560,24 @@ void ORKAdjustPageViewControllerNavigationDirectionForRTL(UIPageViewControllerNa return numberFormatter; } + +// MARK: - NSPredicate + +NSPredicate* _Nullable ORKPredicateWithFormat(NSString * _Nonnull predicateFormat, + NSString * _Nonnull callerID) { + NSPredicate *predicate; + @try { + predicate = [NSPredicate predicateWithFormat:predicateFormat]; + } @catch (NSException *exception) { + ORK_Log_Fault( + "%{public}@: Error creating predicate from predicateWithFormat string: \ + '%{public}@' (error: %{public}@)", + callerID, + predicateFormat, + [exception debugDescription] + ); + predicate = nil; + } + return predicate; +} + diff --git a/ResearchKit/Common/ORKHelpers_Internal.h b/ResearchKit/Common/ORKHelpers_Internal.h index 40ce9fed07..ea273629cc 100644 --- a/ResearchKit/Common/ORKHelpers_Internal.h +++ b/ResearchKit/Common/ORKHelpers_Internal.h @@ -421,3 +421,18 @@ ORKLocalizedHiddenString(key) [NSNumberFormatter localizedStringFromNumber:number numberStyle:NSNumberFormatterNoStyle] NS_ASSUME_NONNULL_END + +// MARK: - NSPredicate + +/** + This function attempts to create an `NSPredicate` from a predicate format `NSString`. + If this fails, an error message starting with the provided `callerID` `NSString` will be logged, + and the `NSPredicate` returned will be `nil`. + + @param predicateFormat The `NSString` to attempt creating the `NSPredicate` from. + @param callerID An `NSString` identifying the calling class for error logging purposes. + + @return An `NSPredicate` created from `predicateFormat` if the operation was successful, `nil` if not. + */ +NSPredicate* _Nullable ORKPredicateWithFormat(NSString * _Nonnull predicateFormat, + NSString * _Nonnull callerID); diff --git a/ResearchKit/Common/ORKPredicateFormItemVisibilityRule.m b/ResearchKit/Common/ORKPredicateFormItemVisibilityRule.m index 86dfdf465f..4e13b9796e 100644 --- a/ResearchKit/Common/ORKPredicateFormItemVisibilityRule.m +++ b/ResearchKit/Common/ORKPredicateFormItemVisibilityRule.m @@ -43,13 +43,8 @@ - (instancetype)init { } - (nullable instancetype)initWithPredicateFormat:(NSString *)predicateFormat { - NSPredicate *predicate; - @try { - predicate = [NSPredicate predicateWithFormat:predicateFormat]; - } @catch (NSException *exception) { - ORK_Log_Fault("ORKPredicateFormItemVisibilityRule: Error creating predicate from predicateFormat string: '%{public}@' (error: %{public}@)", predicateFormat, [exception debugDescription]); - predicate = nil; - } + NSPredicate *predicate = ORKPredicateWithFormat(predicateFormat, @"ORKPredicateFormItemVisibilityRule"); + if (predicate != nil) { self = [self initWithPredicate:predicate]; _predicateFormat = [predicateFormat copy]; diff --git a/ResearchKit/Common/ORKPredicateFormItemVisibilityRule_Private.h b/ResearchKit/Common/ORKPredicateFormItemVisibilityRule_Private.h index a0a90884c2..a0e743f7c0 100644 --- a/ResearchKit/Common/ORKPredicateFormItemVisibilityRule_Private.h +++ b/ResearchKit/Common/ORKPredicateFormItemVisibilityRule_Private.h @@ -36,11 +36,16 @@ NS_ASSUME_NONNULL_BEGIN /** To support deserialization, where deserialization of NSPredicates isn't supported, this class extension allows initializing - an `ORKPredicateFormItemVisibilityRule` with a predicateFormat `NSString` instead of an `NSPredicate` - and retains the original predicateFormat for serialization. + an `ORKPredicateFormItemVisibilityRule` with a `predicateFormat` `NSString` instead of an `NSPredicate` + and retains the original `predicateFormat` for serialization. */ - (nullable instancetype)initWithPredicateFormat:(NSString *)predicateFormat; +/** + To support deserialization, where deserialization of NSPredicates isn't supported, this class extension allows initializing + an `ORKPredicateFormItemVisibilityRule` with a `predicateFormat` `NSString` instead of an `NSPredicate` + and retains the original `predicateFormat` for serialization by setting this property. +*/ @property (nonatomic, nullable, copy, readonly) NSString *predicateFormat; @end diff --git a/ResearchKit/Common/ORKRecorder.h b/ResearchKit/Common/ORKRecorder.h index c57b2b2101..4a2c39e191 100644 --- a/ResearchKit/Common/ORKRecorder.h +++ b/ResearchKit/Common/ORKRecorder.h @@ -40,6 +40,7 @@ NS_ASSUME_NONNULL_BEGIN +@class ORKFileResult; @class ORKRecorder; @class ORKResult; @class ORKStep; @@ -86,6 +87,29 @@ ORK_CLASS_AVAILABLE */ @property (nonatomic, copy, readonly) NSString *identifier; +/** + The URL pointing to the directory in which the recorder should write all output file data as needed + (if producing `ORKFileResult` instances). + + It must be readwrite while this property can be set via ORKTaskViewController. + */ +@property (nonatomic, copy, readwrite, nullable) NSURL *outputDirectory; + +/** + The file-size threshold in bytes used to determine when data is rolled over to multiple files as data is being written. + If the value is 0, data is written to only one file and not rolled over to multiple files. + */ +@property (nonatomic, assign, readonly) size_t rollingFileSizeThreshold; + +/** + Returns a recorder instance using this configuration. + + @param step The step for which this recorder is being created. + + @return A configured recorder instance. + */ +- (nullable ORKRecorder *)recorderForStep:(nullable ORKStep *)step; + /** Returns a recorder instance using this configuration. @@ -93,8 +117,11 @@ ORK_CLASS_AVAILABLE @param outputDirectory The directory in which all output file data should be written (if producing `ORKFileResult` instances). @return A configured recorder instance. + + This method is being deprecated. Set the `outputDirectory` when initializing your configuration instead, then call `recorderForStep:` instead. */ -- (nullable ORKRecorder *)recorderForStep:(nullable ORKStep *)step outputDirectory:(nullable NSURL *)outputDirectory; +- (nullable ORKRecorder *)recorderForStep:(ORKStep *)step + outputDirectory:(nullable NSURL *)outputDirectory DEPRECATED_MSG_ATTRIBUTE("This function is being deprecated. Instead, set the `outputDirectory` property when initializing your configuration, then call recorderForStep:."); /** Returns the HealthKit types for which this recorder requires read access in a set of `HKSampleType` objects. @@ -131,6 +158,16 @@ ORK_CLASS_AVAILABLE */ @property (nonatomic, readonly) double frequency; +/** + Returns an initialized accelerometer recorder configuration using the specified frequency. + + @param identifier The unique identifier of the recorder configuration. + @param frequency The frequency of accelerometer data collection in samples per second (Hz). + + @return An initialized accelerometer recorder configuration. + */ +- (instancetype)initWithIdentifier:(NSString *)identifier frequency:(double)frequency; + /** Returns an initialized accelerometer recorder configuration using the specified frequency. @@ -138,10 +175,30 @@ ORK_CLASS_AVAILABLE @param identifier The unique identifier of the recorder configuration. @param frequency The frequency of accelerometer data collection in samples per second (Hz). + @param outputDirectory The url to the directory in which all output file data should be written (if producing `ORKFileResult` instances). @return An initialized accelerometer recorder configuration. */ -- (instancetype)initWithIdentifier:(NSString *)identifier frequency:(double)frequency NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithIdentifier:(NSString *)identifier + frequency:(double)frequency + outputDirectory:(nullable NSURL *)outputDirectory; + +/** + Returns an initialized accelerometer recorder configuration using the specified frequency. + + This method is the designated initializer. + + @param identifier The unique identifier of the recorder configuration. + @param frequency The frequency of accelerometer data collection in samples per second (Hz). + @param outputDirectory The url to the directory in which all output file data should be written (if producing `ORKFileResult` instances). + @param rollingFileSizeThreshold The file-size threshold in bytes used to determine when data is rolled over to multiple files as data is being written. If the value is 0, data is written to only one file and not rolled over to multiple files. + + @return An initialized accelerometer recorder configuration. + */ +- (instancetype)initWithIdentifier:(NSString *)identifier + frequency:(double)frequency + outputDirectory:(nullable NSURL *)outputDirectory + rollingFileSizeThreshold:(size_t)rollingFileSizeThreshold NS_DESIGNATED_INITIALIZER; /** Returns a new accelerometer recorder configuration initialized from data in the given unarchiver. @@ -178,6 +235,18 @@ ORK_CLASS_AVAILABLE */ @property (nonatomic, readonly, nullable) NSDictionary *recorderSettings; +/** + Returns an initialized audio recorder configuration using the specified settings. + + For information on the settings available for an audio recorder, see "AV Foundation Audio Settings Constants". + + @param identifier The unique identifier of the recorder configuration. + @param recorderSettings The settings for the recording session. + + @return An initialized audio recorder configuration. + */ +- (instancetype)initWithIdentifier:(NSString *)identifier recorderSettings:(NSDictionary *)recorderSettings; + /** Returns an initialized audio recorder configuration using the specified settings. @@ -187,10 +256,32 @@ ORK_CLASS_AVAILABLE @param identifier The unique identifier of the recorder configuration. @param recorderSettings The settings for the recording session. + @param outputDirectory The url to the directory in which all output file data should be written (if producing `ORKFileResult` instances). @return An initialized audio recorder configuration. */ -- (instancetype)initWithIdentifier:(NSString *)identifier recorderSettings:(NSDictionary *)recorderSettings NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithIdentifier:(NSString *)identifier + recorderSettings:(NSDictionary *)recorderSettings + outputDirectory:(nullable NSURL *)outputDirectory; + +/** + Returns an initialized audio recorder configuration using the specified settings. + + This method is the designated initializer. + + For information on the settings available for an audio recorder, see "AV Foundation Audio Settings Constants". + + @param identifier The unique identifier of the recorder configuration. + @param recorderSettings The settings for the recording session. + @param outputDirectory The url to the directory in which all output file data should be written (if producing `ORKFileResult` instances). + @param rollingFileSizeThreshold The file-size threshold in bytes used to determine when data is rolled over to multiple files as data is being written. If the value is 0, data is written to only one file and not rolled over to multiple files. + + @return An initialized audio recorder configuration. + */ +- (instancetype)initWithIdentifier:(NSString *)identifier + recorderSettings:(NSDictionary *)recorderSettings + outputDirectory:(nullable NSURL *)outputDirectory + rollingFileSizeThreshold:(size_t)rollingFileSizeThreshold NS_DESIGNATED_INITIALIZER; /** Returns a new audio recorder configuration initialized from data in the given unarchiver. @@ -226,6 +317,16 @@ ORK_CLASS_AVAILABLE */ @property (nonatomic, readonly) double frequency; +/** + Returns an initialized device motion recorder configuration using the specified frequency. + + @param identifier The unique identifier of the recorder configuration. + @param frequency Motion data collection frequency in samples per second (Hz). + + @return An initialized device motion recorder configuration. + */ +- (instancetype)initWithIdentifier:(NSString *)identifier frequency:(double)frequency; + /** Returns an initialized device motion recorder configuration using the specified frequency. @@ -233,10 +334,30 @@ ORK_CLASS_AVAILABLE @param identifier The unique identifier of the recorder configuration. @param frequency Motion data collection frequency in samples per second (Hz). + @param outputDirectory The url to the directory in which all output file data should be written (if producing `ORKFileResult` instances). @return An initialized device motion recorder configuration. */ -- (instancetype)initWithIdentifier:(NSString *)identifier frequency:(double)frequency NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithIdentifier:(NSString *)identifier + frequency:(double)frequency + outputDirectory:(nullable NSURL *)outputDirectory; + +/** + Returns an initialized device motion recorder configuration using the specified frequency. + + This method is the designated initializer. + + @param identifier The unique identifier of the recorder configuration. + @param frequency Motion data collection frequency in samples per second (Hz). + @param outputDirectory The url to the directory in which all output file data should be written (if producing `ORKFileResult` instances). + @param rollingFileSizeThreshold The file-size threshold in bytes used to determine when data is rolled over to multiple files as data is being written. If the value is 0, data is written to only one file and not rolled over to multiple files. + + @return An initialized device motion recorder configuration. + */ +- (instancetype)initWithIdentifier:(NSString *)identifier + frequency:(double)frequency + outputDirectory:(nullable NSURL *)outputDirectory + rollingFileSizeThreshold:(size_t)rollingFileSizeThreshold NS_DESIGNATED_INITIALIZER; /** Returns a new device motion recorder configuration initialized from data in the given unarchiver. @@ -274,13 +395,39 @@ ORK_CLASS_AVAILABLE The recorder instantiates a `CMPedometer` object, so no additional parameters besides the identifier are required. - This method is the designated initializer. + @param identifier The unique identifier of the recorder configuration. + + @return An initialized pedometer recorder configuration. + */ +- (instancetype)initWithIdentifier:(NSString *)identifier; +/** + Returns an initialized pedometer recorder configuration. + + The recorder instantiates a `CMPedometer` object. + @param identifier The unique identifier of the recorder configuration. + @param outputDirectory The url to the directory in which all output file data should be written (if producing `ORKFileResult` instances). @return An initialized pedometer recorder configuration. */ -- (instancetype)initWithIdentifier:(NSString *)identifier NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithIdentifier:(NSString *)identifier + outputDirectory:(nullable NSURL *)outputDirectory; + +/** + Returns an initialized pedometer recorder configuration. + + The recorder instantiates a `CMPedometer` object. + + @param identifier The unique identifier of the recorder configuration. + @param outputDirectory The url to the directory in which all output file data should be written (if producing `ORKFileResult` instances). + @param rollingFileSizeThreshold The file-size threshold in bytes used to determine when data is rolled over to multiple files as data is being written. If the value is 0, data is written to only one file and not rolled over to multiple files. + + @return An initialized pedometer recorder configuration. + */ +- (instancetype)initWithIdentifier:(NSString *)identifier + outputDirectory:(nullable NSURL *)outputDirectory + rollingFileSizeThreshold:(size_t)rollingFileSizeThreshold NS_DESIGNATED_INITIALIZER; /** Returns a new pedometer recorder configuration initialized from data in the given unarchiver. @@ -317,16 +464,42 @@ ORK_CLASS_AVAILABLE ORK_CLASS_AVAILABLE @interface ORKLocationRecorderConfiguration : ORKRecorderConfiguration +/** + Returns an initialized location recorder configuration. + + @param identifier The unique identifier of the recorder configuration. + + @return An initialized location recorder configuration. + */ +- (instancetype)initWithIdentifier:(NSString *)identifier; + +/** + Returns an initialized location recorder configuration. + + This method is the designated initializer. + + @param identifier The unique identifier of the recorder configuration. + @param outputDirectory The url to the directory in which all output file data should be written (if producing `ORKFileResult` instances). + + @return An initialized location recorder configuration. + */ +- (instancetype)initWithIdentifier:(NSString *)identifier + outputDirectory:(nullable NSURL *)outputDirectory; + /** Returns an initialized location recorder configuration. This method is the designated initializer. @param identifier The unique identifier of the recorder configuration. + @param outputDirectory The url to the directory in which all output file data should be written (if producing `ORKFileResult` instances). + @param rollingFileSizeThreshold The file-size threshold in bytes used to determine when data is rolled over to multiple files as data is being written. If the value is 0, data is written to only one file and not rolled over to multiple files. @return An initialized location recorder configuration. */ -- (instancetype)initWithIdentifier:(NSString *)identifier NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithIdentifier:(NSString *)identifier + outputDirectory:(nullable NSURL *)outputDirectory + rollingFileSizeThreshold:(size_t)rollingFileSizeThreshold NS_DESIGNATED_INITIALIZER; /** Returns a new location recorder configuration initialized from data in the given unarchiver. @@ -353,7 +526,7 @@ ORK_CLASS_AVAILABLE @interface ORKStreamingAudioRecorderConfiguration : ORKRecorderConfiguration /** - Returns an initialized audio recorder configuration. + Returns an initialized streaming audio recorder configuration. This method is the designated initializer. @@ -361,12 +534,40 @@ ORK_CLASS_AVAILABLE @return An initialized audio recorder configuration. */ -- (instancetype)initWithIdentifier:(NSString *)identifier NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithIdentifier:(NSString *)identifier; /** - Returns a new audio recorder configuration initialized from data in the given unarchiver. + Returns an initialized streaming audio recorder configuration. - @param aDecoder Coder from which to initialize the audio recorder configuration. + This method is the designated initializer. + + @param identifier The unique identifier of the recorder configuration. + @param outputDirectory The url to the directory in which all output file data should be written (if producing `ORKFileResult` instances). + + @return An initialized streaming audio recorder configuration. +*/ +- (instancetype)initWithIdentifier:(NSString *)identifier + outputDirectory:(nullable NSURL *)outputDirectory; + +/** + Returns an initialized streaming audio recorder configuration. + + This method is the designated initializer. + + @param identifier The unique identifier of the recorder configuration. + @param outputDirectory The url to the directory in which all output file data should be written (if producing `ORKFileResult` instances). + @param rollingFileSizeThreshold The file-size threshold in bytes used to determine when data is rolled over to multiple files as data is being written. If the value is 0, data is written to only one file and not rolled over to multiple files. + + @return An initialized streaming audio recorder configuration. +*/ +- (instancetype)initWithIdentifier:(NSString *)identifier + outputDirectory:(nullable NSURL *)outputDirectory + rollingFileSizeThreshold:(size_t)rollingFileSizeThreshold NS_DESIGNATED_INITIALIZER; + +/** + Returns a new streaming audio recorder configuration initialized from data in the given unarchiver. + + @param aDecoder Coder from which to initialize the streaming audio recorder configuration. @return A new audio recorder configuration. */ @@ -389,9 +590,9 @@ need to implement it. Typically, this method is called once when recording is stopped. @param recorder The generating recorder object. - @param result The generated result. + @param results The generated results. */ -- (void)recorder:(ORKRecorder *)recorder didCompleteWithResult:(nullable ORKResult *)result; +- (void)recorder:(ORKRecorder *)recorder didCompleteWithResults:(NSArray *)results; /** Tells the delegate that recording failed. @@ -421,7 +622,8 @@ need to implement it. The step view controller starts the recorder when the active step is started, and stops the recorder when the active step is finished. - The results of recording are typically written to a file specified by the value of the `outputDirectory` property. + The results of recording are typically written to a file specified by the value of the configuration's + `outputDirectory` property. Usually, the `ORKActiveStepViewController` object is the recorder's delegate, and it receives callbacks when errors occur or when recording is complete. @@ -436,20 +638,6 @@ ORK_CLASS_AVAILABLE @property (nonatomic, weak, nullable) id delegate; -/** - A short string that uniquely identifies the recorder (usually assigned by the recorder configuration). - - The identifier is reproduced in the results of a recorder created from this configuration. In fact, the only way to link a result - (an `ORKFileResult` object) to the recorder that generated it is to look at the value of - `identifier`. To accurately identify recorder results, you need to ensure that recorder identifiers - are unique within each step. - - In some cases, it can be useful to link the recorder identifier to a unique identifier in a - database; in other cases, it can make sense to make the identifier human - readable. - */ -@property (nonatomic, copy, readonly) NSString *identifier; - /** The step that produced this recorder, configured during initialization. */ @@ -458,15 +646,7 @@ ORK_CLASS_AVAILABLE /** The configuration that produced this recorder. */ -@property (nonatomic, strong, readonly, nullable) ORKRecorderConfiguration *configuration; - -/** - The file URL of the output directory configured during initialization. - - Typically, you set the `outputDirectory` property for the `ORKTaskViewController` object - before presenting the task. - */ -@property (nonatomic, copy, readonly, nullable) NSURL *outputDirectory; +@property (nonatomic, strong, readonly) ORKRecorderConfiguration *configuration; /** Returns the log prefix for the log file. diff --git a/ResearchKit/Common/ORKRecorder.m b/ResearchKit/Common/ORKRecorder.m index def1d0eddc..cc0aee647d 100644 --- a/ResearchKit/Common/ORKRecorder.m +++ b/ResearchKit/Common/ORKRecorder.m @@ -32,13 +32,14 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE #import "ORKRecorder.h" #import "ORKRecorder_Internal.h" +#import "ORKRecorder_Private.h" +#import "ResearchKit/ResearchKit-Swift.h" #import "ORKDataLogger.h" #import "ORKFileResult.h" #import "ORKHelpers_Internal.h" - @implementation ORKRecorderConfiguration + (instancetype)new { @@ -50,10 +51,22 @@ - (instancetype)init { } - (instancetype)initWithIdentifier:(NSString *)identifier { + return [self initWithIdentifier:identifier + outputDirectory:nil + rollingFileSizeThreshold:0]; +} + +- (instancetype)initWithIdentifier:(NSString *)identifier + outputDirectory:(NSURL *)outputDirectory + rollingFileSizeThreshold:(size_t)rollingFileSizeThreshold { self = [super init]; if (self) { ORKThrowInvalidArgumentExceptionIfNil(identifier); _identifier = [identifier copy]; + if (outputDirectory != nil) { + _outputDirectory = [outputDirectory copy]; + } + _rollingFileSizeThreshold = rollingFileSizeThreshold; } return self; } @@ -62,12 +75,20 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder { self = [super init]; if (self) { ORK_DECODE_OBJ_CLASS(aDecoder, identifier, NSString); + ORK_DECODE_URL(aDecoder, outputDirectory); + NSNumber *rollingFileSizeThresholdAsNumber = (NSNumber *)[aDecoder decodeObjectOfClass:[NSNumber class] + forKey:@ORK_STRINGIFY(rollingFileSizeThreshold)]; + _rollingFileSizeThreshold = rollingFileSizeThresholdAsNumber.integerValue; } return self; } - (void)encodeWithCoder:(NSCoder *)aCoder { ORK_ENCODE_OBJ(aCoder, identifier); + ORK_ENCODE_URL(aCoder, outputDirectory); + NSNumber *rollingFileSizeThreshold = @(_rollingFileSizeThreshold); + [aCoder encodeObject:rollingFileSizeThreshold + forKey:@ORK_STRINGIFY(rollingFileSizeThreshold)]; } - (BOOL)isEqual:(id)object { @@ -85,10 +106,17 @@ + (BOOL)supportsSecureCoding { return YES; } -- (ORKRecorder *)recorderForStep:(ORKStep *)step outputDirectory:(NSURL *)outputDirectory { +- (ORKRecorder *)recorderForStep:(ORKStep *)step { return nil; } +- (ORKRecorder *)recorderForStep:(ORKStep *)step outputDirectory:(nullable NSURL *)outputDirectory { + if (outputDirectory != nil) { + self.outputDirectory = [outputDirectory copy]; + } + return [self recorderForStep:step]; +} + #if ORK_FEATURE_HEALTHKIT_AUTHORIZATION - (NSSet *)requestedHealthKitTypesForReading { return nil; @@ -101,7 +129,6 @@ - (ORKPermissionMask)requestedPermissionMask { @end - @implementation ORKRecorder { UIBackgroundTaskIdentifier _backgroundTask; NSUUID *_recorderUUID; @@ -115,15 +142,23 @@ - (instancetype)init { @throw [NSException exceptionWithName:NSGenericException reason:@"Use designated initializer" userInfo:nil]; } -- (instancetype)initWithIdentifier:(NSString *)identifier step:(ORKStep *)step outputDirectory:(NSURL *)outputDirectory { +- (instancetype)initWithIdentifier:(NSString *)identifier step:(ORKStep *)step { + return [self initWithIdentifier:identifier step:step outputDirectory:nil rollingFileSizeThreshold:0]; +} + +- (instancetype)initWithIdentifier:(NSString *)identifier + step:(ORKStep *)step + outputDirectory:(nullable NSURL *)outputDirectory + rollingFileSizeThreshold:(size_t)rollingFileSizeThreshold { self = [super init]; if (self) { if (nil == identifier) { @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"identifier cannot be nil." userInfo:nil]; } - _identifier = [identifier copy]; - _outputDirectory = outputDirectory; + _configuration = [[ORKRecorderConfiguration alloc] initWithIdentifier:identifier + outputDirectory:outputDirectory + rollingFileSizeThreshold: rollingFileSizeThreshold]; self.step = step; _backgroundTask = NSNotFound; _recorderUUID = [NSUUID UUID]; @@ -181,10 +216,10 @@ - (void)finishRecordingWithError:(NSError *)error { } - (NSURL *)recordingDirectoryURL { - if (!_outputDirectory) { + if (!self.outputDirectory) { return nil; } - return [NSURL fileURLWithPath:[_outputDirectory.path stringByAppendingPathComponent:[NSString stringWithFormat:@"recorder-%@", _recorderUUID.UUIDString]]]; + return [NSURL fileURLWithPath:[self.outputDirectory.path stringByAppendingPathComponent:[NSString stringWithFormat:@"recorder-%@", _recorderUUID.UUIDString]]]; } - (NSString *)recorderType { @@ -211,9 +246,14 @@ - (ORKDataLogger *)makeJSONDataLoggerWithError:(NSError **)errorOut { NSString *logName = [identifier stringByReplacingOccurrencesOfString:@"-" withString:@"_"]; // Class B data protection for temporary file during active task logging. - ORKDataLogger *logger = [[ORKDataLogger alloc] initWithDirectory:workingDir logName:logName formatter:[ORKJSONLogFormatter new] delegate:nil]; + ORKDataLogger *logger = [ORKDataLogger JSONDataLoggerWithDirectory:workingDir logName:logName delegate:nil]; logger.fileProtectionMode = ORKFileProtectionCompleteUnlessOpen; + + if (self.rollingFileSizeThreshold > 0) { + logger.maximumCurrentLogFileSize = self.rollingFileSizeThreshold; + } + return logger; } @@ -237,19 +277,23 @@ - (void)applyFileProtection:(ORKFileProtectionMode)fileProtection toFileAtURL:(N } } -- (void)reportFileResultWithFile:(NSURL *)fileUrl error:(NSError *)error { - +- (void)reportFileResultsWithFiles:(NSArray *)fileUrls error:(NSError *)error { id localDelegate = self.delegate; - if (fileUrl && !error) { - if (localDelegate && [localDelegate respondsToSelector:@selector(recorder:didCompleteWithResult:)]) { - ORKFileResult *result = [[ORKFileResult alloc] initWithIdentifier:self.identifier]; - result.contentType = [self mimeType]; - result.fileURL = fileUrl; - result.fileName = [fileUrl lastPathComponent]; - result.userInfo = self.userInfo; - result.startDate = self.startDate; + if (fileUrls.count != 0 && !error) { + if (localDelegate && [localDelegate respondsToSelector:@selector(recorder:didCompleteWithResults:)]) { + NSMutableArray *fileResults = [[NSMutableArray alloc] init]; + for (NSURL *fileURL in fileUrls) { + ORKFileResult *fileResult = [[ORKFileResult alloc] initWithIdentifier:self.identifier]; + fileResult.contentType = [self mimeType]; + fileResult.fileURL = fileURL; + fileResult.fileName = [fileURL lastPathComponent]; + fileResult.userInfo = self.userInfo; + fileResult.startDate = self.startDate; + + [fileResults addObject:fileResult]; + } - [localDelegate recorder:self didCompleteWithResult:result]; + [localDelegate recorder:self didCompleteWithResults:fileResults]; // Point future recording at a new directory [self reset]; diff --git a/ResearchKit/Common/ORKRecorder.swift b/ResearchKit/Common/ORKRecorder.swift new file mode 100644 index 0000000000..e62b913fc5 --- /dev/null +++ b/ResearchKit/Common/ORKRecorder.swift @@ -0,0 +1,66 @@ +/* + Copyright (c) 2025, 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. + */ + +@objc +public extension ORKRecorder { + /** + A short string that uniquely identifies the recorder (usually assigned by the recorder configuration). + + The identifier is reproduced in the results of a recorder created from this configuration. In fact, the only way to link a result + (an `ORKFileResult` object) to the recorder that generated it is to look at the value of + `identifier`. To accurately identify recorder results, you need to ensure that recorder identifiers + are unique within each step. + + In some cases, it can be useful to link the recorder identifier to a unique identifier in a + database; in other cases, it can make sense to make the identifier human + readable. + */ + var identifier: String? { + configuration.identifier + } + + /** + The file URL of the output directory configured during initialization. + + Typically, you set the `outputDirectory` property for the `ORKTaskViewController` object + before presenting the task. + */ + var outputDirectory: URL? { + configuration.outputDirectory + } + + /** + The file-size threshold in bytes used to determine when data is rolled over to multiple files as data is being written. + If the value is 0, data is written to only one file and not rolled over to multiple files. + */ + var rollingFileSizeThreshold: Int { + configuration.rollingFileSizeThreshold + } +} diff --git a/ResearchKit/Common/ORKRecorder_Internal.h b/ResearchKit/Common/ORKRecorder_Internal.h index 72c4b8fe67..d0200d0f0e 100644 --- a/ResearchKit/Common/ORKRecorder_Internal.h +++ b/ResearchKit/Common/ORKRecorder_Internal.h @@ -40,7 +40,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, strong, nullable) ORKStep *step; -@property (nonatomic, strong, nullable) ORKRecorderConfiguration *configuration; +@property (nonatomic, strong) ORKRecorderConfiguration *configuration; @property (nonatomic) BOOL continuesInBackground; @@ -52,7 +52,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)reset NS_REQUIRES_SUPER; -- (void)reportFileResultWithFile:(nullable NSURL *)fileUrl error:(nullable NSError *)error; +- (void)reportFileResultsWithFiles:(NSArray *)fileUrls error:(nullable NSError *)error; - (nullable NSURL *)recordingDirectoryURL; diff --git a/ResearchKit/Common/ORKRecorder_Private.h b/ResearchKit/Common/ORKRecorder_Private.h index e2d7cf1e33..d24434d436 100644 --- a/ResearchKit/Common/ORKRecorder_Private.h +++ b/ResearchKit/Common/ORKRecorder_Private.h @@ -39,6 +39,16 @@ NS_ASSUME_NONNULL_BEGIN @interface ORKRecorder () +/** + Returns an initialized recorder. + + @param identifier The unique identifier of the recorder. + @param step The step for which this recorder is being created. + + @return An initialized recorder. + */ +- (instancetype)initWithIdentifier:(NSString *)identifier step:(nullable ORKStep *)step; + /** Returns an initialized recorder. @@ -46,11 +56,16 @@ NS_ASSUME_NONNULL_BEGIN @param identifier The unique identifier of the recorder. @param step The step for which this recorder is being created. - @param outputDirectory The directory in which all output file data should be written (if producing `ORKFileResult` instances). + @param outputDirectory The url to the directory in which all output file data should be written (if producing `ORKFileResult` instances). + @param rollingFileSizeThreshold The file-size threshold in bytes used to determine when data is rolled over to multiple files as data is being written. + If the value is 0, data is written to only one file and not rolled over to multiple files. @return An initialized recorder. */ -- (instancetype)initWithIdentifier:(NSString *)identifier step:(nullable ORKStep *)step outputDirectory:(nullable NSURL *)outputDirectory; +- (instancetype)initWithIdentifier:(NSString *)identifier + step:(nullable ORKStep *)step + outputDirectory:(nullable NSURL *)outputDirectory + rollingFileSizeThreshold:(size_t)rollingFileSizeThreshold NS_DESIGNATED_INITIALIZER; /** A preparation step to provide viewController and view before record starting. @@ -87,16 +102,30 @@ NS_ASSUME_NONNULL_BEGIN @interface ORKRecorderConfiguration () +/** + Returns an initialized recorder configuration. + + @param identifier The unique identifier of the recorder configuration. + + @return An initialized recorder configuration. + */ +- (instancetype)initWithIdentifier:(NSString *)identifier; + /** Returns an initialized recorder configuration. This method is the designated initializer. @param identifier The unique identifier of the recorder configuration. + @param outputDirectory The url to the directory in which all output file data should be written (if producing `ORKFileResult` instances). + @param rollingFileSizeThreshold The file-size threshold in bytes used to determine when data is rolled over to multiple files as data is being written. + If the value is 0, data is written to only one file and not rolled over to multiple files. @return An initialized recorder configuration. */ -- (instancetype)initWithIdentifier:(NSString *)identifier NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithIdentifier:(NSString *)identifier + outputDirectory:(nullable NSURL *)outputDirectory + rollingFileSizeThreshold:(size_t)rollingFileSizeThreshold NS_DESIGNATED_INITIALIZER; /** Returns a new recorder configuration initialized from data in the given unarchiver. @@ -120,7 +149,44 @@ NS_ASSUME_NONNULL_BEGIN ORK_CLASS_AVAILABLE @interface ORKAudioStreamerConfiguration : ORKRecorderConfiguration -- (instancetype)initWithIdentifier:(NSString *)identifier NS_DESIGNATED_INITIALIZER; + +/** + Returns an initialized recorder configuration. + + @param identifier The unique identifier of the recorder configuration. + + @return An initialized recorder configuration. + */ +- (instancetype)initWithIdentifier:(NSString *)identifier; + +/** + Returns an initialized recorder configuration. + + This method is the designated initializer. + + @param identifier The unique identifier of the recorder configuration. + @param outputDirectory The url to the directory in which all output file data should be written (if producing `ORKFileResult` instances). + + @return An initialized recorder configuration. + */ +- (instancetype)initWithIdentifier:(NSString *)identifier + outputDirectory:(nullable NSURL *)outputDirectory; + +/** + Returns an initialized recorder configuration. + + This method is the designated initializer. + + @param identifier The unique identifier of the recorder configuration. + @param outputDirectory The url to the directory in which all output file data should be written (if producing `ORKFileResult` instances). + @param rollingFileSizeThreshold The file-size threshold in bytes used to determine when data is rolled over to multiple files as data is being written. + If the value is 0, data is written to only one file and not rolled over to multiple files. + + @return An initialized recorder configuration. + */ +- (instancetype)initWithIdentifier:(NSString *)identifier + outputDirectory:(nullable NSURL *)outputDirectory + rollingFileSizeThreshold:(size_t)rollingFileSizeThreshold NS_DESIGNATED_INITIALIZER; - (instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER; diff --git a/ResearchKit/Common/ORKStep.h b/ResearchKit/Common/ORKStep.h index a6b53b65b0..900dba67b9 100644 --- a/ResearchKit/Common/ORKStep.h +++ b/ResearchKit/Common/ORKStep.h @@ -51,19 +51,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. 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)) @@ -240,7 +240,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 @@ -303,7 +303,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. */ diff --git a/ResearchKit/Common/ORKStepNavigationRule.m b/ResearchKit/Common/ORKStepNavigationRule.m index 5a42c8e36e..8dfbcd2a70 100644 --- a/ResearchKit/Common/ORKStepNavigationRule.m +++ b/ResearchKit/Common/ORKStepNavigationRule.m @@ -137,6 +137,27 @@ - (instancetype)initWithResultPredicates:(NSArray *)resultPredica return self; } +- (nullable instancetype)initWithResultPredicateFormats:(NSArray *)resultPredicateFormats + destinationStepIdentifiers:(NSArray *)destinationStepIdentifiers + defaultStepIdentifier:(nullable NSString *)defaultStepIdentifier + validateArrays:(BOOL)validateArrays { + NSMutableArray *resultPredicates = [NSMutableArray arrayWithCapacity:[resultPredicateFormats count]]; + for (NSString *resultPredicateFormat in resultPredicateFormats) { + NSPredicate *predicate = ORKPredicateWithFormat(resultPredicateFormat, @"ORKPredicateStepNavigationRule"); + if (predicate != nil) { + [resultPredicates addObject:predicate]; + } + } + + if ([resultPredicates count] != 0) { + self = [self initWithResultPredicates:[resultPredicates copy] + destinationStepIdentifiers:destinationStepIdentifiers + defaultStepIdentifier:defaultStepIdentifier validateArrays:validateArrays]; + _resultPredicateFormats = [resultPredicateFormats copy]; + } + return self; +} + #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wobjc-designated-initializers" - (instancetype)initWithResultPredicates:(NSArray *)resultPredicates @@ -218,6 +239,7 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if (self) { ORK_DECODE_OBJ_ARRAY(aDecoder, resultPredicates, NSPredicate); + ORK_DECODE_OBJ_ARRAY(aDecoder, resultPredicateFormats, NSString); ORK_DECODE_OBJ_ARRAY(aDecoder, destinationStepIdentifiers, NSString); ORK_DECODE_OBJ_CLASS(aDecoder, defaultStepIdentifier, NSString); ORK_DECODE_OBJ_ARRAY(aDecoder, additionalTaskResults, ORKTaskResult); @@ -228,6 +250,7 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder { - (void)encodeWithCoder:(NSCoder *)aCoder { [super encodeWithCoder:aCoder]; ORK_ENCODE_OBJ(aCoder, resultPredicates); + ORK_ENCODE_OBJ(aCoder, resultPredicateFormats); ORK_ENCODE_OBJ(aCoder, destinationStepIdentifiers); ORK_ENCODE_OBJ(aCoder, defaultStepIdentifier); ORK_ENCODE_OBJ(aCoder, additionalTaskResults); @@ -241,6 +264,7 @@ - (instancetype)copyWithZone:(NSZone *)zone { defaultStepIdentifier:[_defaultStepIdentifier copy] validateArrays:YES]; rule->_additionalTaskResults = ORKArrayCopyObjects(_additionalTaskResults); + rule->_resultPredicateFormats = [_resultPredicateFormats copy]; return rule; } @@ -249,13 +273,14 @@ - (BOOL)isEqual:(id)object { __typeof(self) castObject = object; return (isParentSame && ORKEqualObjects(self.resultPredicates, castObject.resultPredicates) + && ORKEqualObjects(self.resultPredicateFormats, castObject.resultPredicateFormats) && ORKEqualObjects(self.destinationStepIdentifiers, castObject.destinationStepIdentifiers) && ORKEqualObjects(self.defaultStepIdentifier, castObject.defaultStepIdentifier) && ORKEqualObjects(self.additionalTaskResults, castObject.additionalTaskResults)); } - (NSUInteger)hash { - return _resultPredicates.hash ^ _destinationStepIdentifiers.hash ^ _defaultStepIdentifier.hash ^ _additionalTaskResults.hash; + return _resultPredicates.hash ^ _destinationStepIdentifiers.hash ^ _defaultStepIdentifier.hash ^ _additionalTaskResults.hash ^ _resultPredicateFormats.hash; } @end @@ -536,6 +561,16 @@ - (instancetype)initWithResultPredicate:(NSPredicate *)resultPredicate return self; } +- (nullable instancetype)initWithResultPredicateFormat:(NSString *)resultPredicateFormat + keyValueMap:(NSDictionary *)keyValueMap { + NSPredicate *resultPredicate = ORKPredicateWithFormat(resultPredicateFormat, @"ORKPredicateStepNavigationRule"); + if (resultPredicate != nil) { + self = [self initWithResultPredicate:resultPredicate keyValueMap:keyValueMap]; + _resultPredicateFormat = [resultPredicateFormat copy]; + } + return self; +} + - (void)modifyStep:(ORKStep *)step withTaskResult:(ORKTaskResult *)taskResult { // The predicate can either have: @@ -564,6 +599,7 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if (self) { ORK_DECODE_OBJ_CLASS(aDecoder, resultPredicate, NSPredicate); + ORK_DECODE_OBJ_CLASS(aDecoder, resultPredicateFormat, NSString); ORK_DECODE_OBJ_MUTABLE_DICTIONARY_PROPS(aDecoder, keyValueMap, NSString); } return self; @@ -572,14 +608,16 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder { - (void)encodeWithCoder:(NSCoder *)aCoder { [super encodeWithCoder:aCoder]; ORK_ENCODE_OBJ(aCoder, resultPredicate); + ORK_ENCODE_OBJ(aCoder, resultPredicateFormat); ORK_ENCODE_OBJ(aCoder, keyValueMap); } #pragma mark NSCopying - (instancetype)copyWithZone:(NSZone *)zone { - return [[[self class] allocWithZone:zone] initWithResultPredicate:self.resultPredicate - keyValueMap:self.keyValueMap]; + __typeof(self) stepModifier = [[[self class] allocWithZone:zone] initWithResultPredicate:self.resultPredicate keyValueMap:self.keyValueMap]; + stepModifier->_resultPredicateFormat = [_resultPredicateFormat copy]; + return stepModifier; } - (BOOL)isEqual:(id)object { @@ -587,11 +625,12 @@ - (BOOL)isEqual:(id)object { __typeof(self) castObject = object; return (isParentSame && ORKEqualObjects(self.resultPredicate, castObject.resultPredicate) + && ORKEqualObjects(self.resultPredicateFormat, castObject.resultPredicateFormat) && ORKEqualObjects(self.keyValueMap, castObject.keyValueMap)); } - (NSUInteger)hash { - return _resultPredicate.hash ^ _keyValueMap.hash; + return _resultPredicate.hash ^ _keyValueMap.hash ^ _resultPredicateFormat.hash; } @end diff --git a/ResearchKit/Common/ORKStepNavigationRule_Private.h b/ResearchKit/Common/ORKStepNavigationRule_Private.h index 439679f3eb..c512acc117 100644 --- a/ResearchKit/Common/ORKStepNavigationRule_Private.h +++ b/ResearchKit/Common/ORKStepNavigationRule_Private.h @@ -63,6 +63,42 @@ NS_ASSUME_NONNULL_BEGIN defaultStepIdentifier:(nullable NSString *)defaultStepIdentifier validateArrays:(BOOL)validateArrays NS_DESIGNATED_INITIALIZER; +/** + To support deserialization, where deserialization of NSPredicates isn't supported, this class extension allows initializing + an `ORKPredicateFormItemVisibilityRule` with an array of `NSString` `resultPredicateFormats` instead of an `NSPredicate` + and retains the original `resultPredicateFormats` for serialization. +*/ +- (nullable instancetype)initWithResultPredicateFormats:(NSArray *)resultPredicateFormats + destinationStepIdentifiers:(NSArray *)destinationStepIdentifiers + defaultStepIdentifier:(nullable NSString *)defaultStepIdentifier + validateArrays:(BOOL)validateArrays; + +/** + To support deserialization, where deserialization of NSPredicates isn't supported, this class extension allows initializing + an `ORKPredicateFormItemVisibilityRule` with an array of `NSString` `resultPredicateFormats` instead of an `NSPredicate` + and retains the original `resultPredicateFormats` for serialization by setting this property. +*/ +@property (nonatomic, nullable, copy, readonly) NSArray *resultPredicateFormats; + @end + +@interface ORKKeyValueStepModifier () + +/** + To support deserialization, where deserialization of NSPredicates isn't supported, this class extension allows initializing + an `ORKPredicateFormItemVisibilityRule` with a `predicateFormat` `NSString` instead of an `NSPredicate` + and retains the original `predicateFormat` for serialization. +*/ +- (nullable instancetype)initWithResultPredicateFormat:(NSString *)resultPredicateFormat + keyValueMap:(NSDictionary *)keyValueMap; + +/** + To support deserialization, where deserialization of NSPredicates isn't supported, this class extension allows initializing + an `ORKPredicateFormItemVisibilityRule` with a `predicateFormat` `NSString` instead of an `NSPredicate` + and retains the original `predicateFormat` for serialization by setting this property. +*/ +@property (nonatomic, nullable, copy, readonly) NSString *resultPredicateFormat; + +@end NS_ASSUME_NONNULL_END diff --git a/ResearchKit/Configuration/ResearchKit/ResearchKit-Shared.xcconfig b/ResearchKit/Configuration/ResearchKit/ResearchKit-Shared.xcconfig index 3b29f32f3e..029e5a4696 100644 --- a/ResearchKit/Configuration/ResearchKit/ResearchKit-Shared.xcconfig +++ b/ResearchKit/Configuration/ResearchKit/ResearchKit-Shared.xcconfig @@ -23,7 +23,7 @@ PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*] = SWIFT_VERSION = 5.0 CLANG_STATIC_ANALYZER_MODE = deep -ORK_FRAMEWORK_VERSION_NUMBER = 3.1.1 +ORK_FRAMEWORK_VERSION_NUMBER = 3.2.0 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) diff --git a/ResearchKit/Localized/en.lproj/ResearchKit.strings b/ResearchKit/Localized/en.lproj/ResearchKit.strings index 1a1a0b3bd0..9c9cd8398b 100644 --- a/ResearchKit/Localized/en.lproj/ResearchKit.strings +++ b/ResearchKit/Localized/en.lproj/ResearchKit.strings @@ -54,21 +54,21 @@ "CONSENT_LEARN_MORE_TITLE" = "Learn More"; /* Consent section learn more */ -"LEARN_MORE_DATA_GATHERING" = "Learn more about how data is gathered"; -"LEARN_MORE_DATA_USE" = "Learn more about how data is used"; -"LEARN_MORE_PRIVACY" = "Learn more about how your privacy and identity are protected"; -"LEARN_MORE_WELCOME" = "Learn more about the study first"; -"LEARN_MORE_STUDY_SURVEY" = "Learn more about the study survey"; -"LEARN_MORE_TIME_COMMITMENT" = "Learn more about the study’s impact on your time"; -"LEARN_MORE_TASKS" = "Learn more about the tasks involved"; -"LEARN_MORE_WITHDRAWING" = "Learn more about withdrawing"; +"LEARN_MORE_DATA_GATHERING" = "Learn more about how data is gathered."; +"LEARN_MORE_DATA_USE" = "Learn more about how data is used."; +"LEARN_MORE_PRIVACY" = "Learn more about how your privacy and identity are protected."; +"LEARN_MORE_WELCOME" = "Learn more about the study first."; +"LEARN_MORE_STUDY_SURVEY" = "Learn more about the study survey."; +"LEARN_MORE_TIME_COMMITMENT" = "Learn more about the study’s impact on your time."; +"LEARN_MORE_TASKS" = "Learn more about the tasks involved."; +"LEARN_MORE_WITHDRAWING" = "Learn more about withdrawing."; /* Consent sharing step */ "CONSENT_SHARING_TITLE" = "Sharing Options"; "CONSENT_SHARE_WIDELY_%@" = "Share my data with %@ and qualified researchers worldwide"; "CONSENT_SHARE_ONLY_%@" = "Only share my data with %@"; "CONSENT_SHARING_DESCRIPTION_%@" = "%@ will receive your study data from your participation in this study.\n\nSharing your coded study data more broadly (without information such as your name) may benefit this and future research."; -"LEARN_MORE_CONSENT_SHARING" = "Learn more about data sharing"; +"LEARN_MORE_CONSENT_SHARING" = "Learn more about data sharing."; /* Lines beneath typewritten signatures on documents.*/ "CONSENT_DOC_LINE_PRINTED_NAME" = "Printed Name"; diff --git a/ResearchKitActiveTask/AmslerGrid/ORKAmslerGridResult.h b/ResearchKitActiveTask/AmslerGrid/ORKAmslerGridResult.h index 9428d56496..f4fa51968c 100644 --- a/ResearchKitActiveTask/AmslerGrid/ORKAmslerGridResult.h +++ b/ResearchKitActiveTask/AmslerGrid/ORKAmslerGridResult.h @@ -33,6 +33,8 @@ NS_ASSUME_NONNULL_BEGIN +@class ORKFileResult; + /** The `ORKAmslerGridResult` class represents the result of a successful attempt of `ORKAmslerGridStep`. @@ -42,26 +44,27 @@ NS_ASSUME_NONNULL_BEGIN ORK_CLASS_AVAILABLE @interface ORKAmslerGridResult : ORKResult - -- (instancetype)initWithIdentifier:(NSString *)identifier - image:(UIImage *)image - path:(NSArray *)path - eyeSide:(ORKAmslerGridEyeSide)eyeSide; - /** Eye side closed. */ @property (nonatomic) ORKAmslerGridEyeSide eyeSide; -/** - The image for the step. - */ -@property (nonatomic, nullable) UIImage *image; /** The path used to mark the area in the image. */ @property (nonatomic, copy, nullable) NSArray *path; +/** + The file result for the image generated during the step. + */ +@property (nonatomic, copy, nullable) ORKFileResult *imageFileResult; + +/** + The file result for the drawing paths made by the user + during the step. + */ +@property (nonatomic, copy, nullable) ORKFileResult *drawingPathFileResult; + @end NS_ASSUME_NONNULL_END diff --git a/ResearchKitActiveTask/AmslerGrid/ORKAmslerGridResult.m b/ResearchKitActiveTask/AmslerGrid/ORKAmslerGridResult.m index bcde804452..7dad68a899 100644 --- a/ResearchKitActiveTask/AmslerGrid/ORKAmslerGridResult.m +++ b/ResearchKitActiveTask/AmslerGrid/ORKAmslerGridResult.m @@ -33,35 +33,25 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE #import "ORKResult_Private.h" #import "ORKHelpers_Internal.h" -@implementation ORKAmslerGridResult - +#import -- (instancetype)initWithIdentifier:(NSString *)identifier - image:(UIImage *)image - path:(NSArray *)path - eyeSide:(ORKAmslerGridEyeSide)eyeSide{ - self = [super initWithIdentifier:identifier]; - if (self) { - _image = [image copy]; - _path = ORKArrayCopyObjects(path); - _eyeSide = eyeSide; - } - return self; -} +@implementation ORKAmslerGridResult - (void)encodeWithCoder:(NSCoder *)aCoder { [super encodeWithCoder:aCoder]; - ORK_ENCODE_IMAGE(aCoder, image); ORK_ENCODE_OBJ(aCoder, path); ORK_ENCODE_ENUM(aCoder, eyeSide); + ORK_ENCODE_OBJ(aCoder, imageFileResult); + ORK_ENCODE_OBJ(aCoder, drawingPathFileResult); } - (instancetype)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if (self) { - ORK_DECODE_IMAGE(aDecoder, image); ORK_DECODE_OBJ_ARRAY(aDecoder, path, UIBezierPath); ORK_DECODE_ENUM(aDecoder, eyeSide); + ORK_DECODE_OBJ_CLASS(aDecoder, imageFileResult, ORKFileResult); + ORK_DECODE_OBJ_CLASS(aDecoder, drawingPathFileResult, ORKFileResult); } return self; } @@ -71,7 +61,7 @@ + (BOOL)supportsSecureCoding { } - (NSUInteger)hash { - return super.hash ^ self.image.hash ^ self.path.hash; + return super.hash ^ self.path.hash ^ self.imageFileResult.hash ^ self.drawingPathFileResult.hash; } - (BOOL)isEqual:(id)object { @@ -79,16 +69,18 @@ - (BOOL)isEqual:(id)object { __typeof(self) castObject = object; return (isParentSame && - ORKEqualObjects(self.image, castObject.image) && ORKEqualObjects(self.path, castObject.path) && + ORKEqualObjects(self.imageFileResult, castObject.imageFileResult) && + ORKEqualObjects(self.drawingPathFileResult, castObject.drawingPathFileResult) && (self.eyeSide == castObject.eyeSide)); } - (instancetype)copyWithZone:(NSZone *)zone { ORKAmslerGridResult *result = [super copyWithZone:zone]; - result->_image = [_image copy]; result->_path = ORKArrayCopyObjects(_path); result->_eyeSide = _eyeSide; + result->_imageFileResult = _imageFileResult; + result->_drawingPathFileResult = _drawingPathFileResult; return result; } diff --git a/ResearchKitActiveTask/AmslerGrid/ORKAmslerGridStepViewController.m b/ResearchKitActiveTask/AmslerGrid/ORKAmslerGridStepViewController.m index 9ddcee9314..a82ebf4100 100644 --- a/ResearchKitActiveTask/AmslerGrid/ORKAmslerGridStepViewController.m +++ b/ResearchKitActiveTask/AmslerGrid/ORKAmslerGridStepViewController.m @@ -44,6 +44,15 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE #import "ORKResult_Private.h" #import "ORKCollectionResult_Private.h" +#import + + +NSString * const DRAWING_PATH_FILE_RESULT_IDENTIFIER = @"DrawingPathFileResultIdentifier"; +NSString * const FILE_URL_CREATION_ERROR = @"Failed to generate a fileURL for the amsler grid result image."; +NSString * const IMAGE_EXTENSION = @"png"; +NSString * const IMAGE_FILE_RESULT_IDENTIFIER = @"ImageFileResultIdentifier"; +NSString * const PATHS_EXTENSION = @"dat"; +NSString * const PATHS_ARRAY_STORAGE_ERROR = @"Failed to store paths array to file."; @interface ORKAmslerGridStepViewController () { ORKFreehandDrawingView *_freehandDrawingView; @@ -52,7 +61,10 @@ @interface ORKAmslerGridStepViewController () { @end -@implementation ORKAmslerGridStepViewController +@implementation ORKAmslerGridStepViewController { + NSURL *_fileURL; + NSURL *_pathsURL; +} - (instancetype)initWithStep:(ORKStep *)step { self = [super initWithStep:step]; @@ -154,7 +166,37 @@ - (ORKStepResult *)result { if (_freehandDrawingView.freehandDrawingExists) { UIImage *image = [self getImage]; - ORKAmslerGridResult *amslerGridResult = [[ORKAmslerGridResult alloc] initWithIdentifier:self.step.identifier image:image path:_freehandDrawingView.freehandDrawingPath eyeSide: [self amslerGridStep].eyeSide]; + + NSError *error = nil; + NSData *data = UIImagePNGRepresentation(image); + _fileURL = [self _writeCapturedDataWithFileName:self.step.identifier data:data error:&error]; + _pathsURL = [self _writePathDataWithFileName:self.step.identifier path:_freehandDrawingView.freehandDrawingPath]; + + if (error) { + @throw [NSException exceptionWithName:NSFileHandleOperationException + reason:FILE_URL_CREATION_ERROR + userInfo:nil]; + } + + // construct the imageFileResult + ORKFileResult *imageFileResult = [[ORKFileResult alloc] initWithIdentifier:IMAGE_FILE_RESULT_IDENTIFIER]; + imageFileResult.fileURL = _fileURL; + imageFileResult.fileName = [_fileURL lastPathComponent]; + imageFileResult.contentType = [NSString stringWithFormat:@"image/%@", IMAGE_EXTENSION]; + + // construct the drawingPathFileResult + ORKFileResult *drawingPathFileResult = [[ORKFileResult alloc] initWithIdentifier:DRAWING_PATH_FILE_RESULT_IDENTIFIER]; + drawingPathFileResult.fileURL = _pathsURL; + drawingPathFileResult.fileName = [_pathsURL lastPathComponent]; + imageFileResult.contentType = @"application/octet-stream"; + + // construct the ORKAmslerGridResult + ORKAmslerGridResult *amslerGridResult = [[ORKAmslerGridResult alloc] initWithIdentifier:self.step.identifier]; + amslerGridResult.path = _freehandDrawingView.freehandDrawingPath; + amslerGridResult.eyeSide = [self amslerGridStep].eyeSide; + amslerGridResult.imageFileResult = imageFileResult; + amslerGridResult.drawingPathFileResult = drawingPathFileResult; + parentResult.results = @[amslerGridResult]; } @@ -177,4 +219,57 @@ - (UIImage *)getImage { return image; } +- (NSURL *)_writeCapturedDataWithFileName:(NSString *)fileName data:(NSData *)data error:(NSError **)errorOut { + NSURL *URL = [[self.outputDirectory URLByAppendingPathComponent:fileName] URLByAppendingPathExtension:IMAGE_EXTENSION]; + + // Confirm the outputDirectory was set properly + if (!URL) { + if (errorOut == NULL) { + *errorOut = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteInvalidFileNameError userInfo:@{NSLocalizedDescriptionKey:ORKLocalizedString(@"CAPTURE_ERROR_NO_OUTPUT_DIRECTORY", nil)}]; + } + return nil; + } + + // If set properly, the outputDirectory is already created, so write the file into it + NSError *writeError = nil; + if (![data writeToURL:URL options:NSDataWritingAtomic|NSDataWritingFileProtectionCompleteUnlessOpen error:&writeError]) { + if (writeError) { + ORK_Log_Error("%@", writeError); + } + + if (errorOut == NULL) { + *errorOut = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteInvalidFileNameError userInfo:@{NSLocalizedDescriptionKey:ORKLocalizedString(@"CAPTURE_ERROR_CANNOT_WRITE_FILE", nil)}]; + } + return nil; + } + + return URL; +} + +- (NSURL *)_writePathDataWithFileName:(NSString *)fileName path:(NSArray *)paths { + NSError *error = nil; + NSData *data = [NSKeyedArchiver archivedDataWithRootObject:paths + requiringSecureCoding:NO + error:&error]; + + if (error) { + [self _throwPathStorageError]; + } + + NSURL *URL = [[self.outputDirectory URLByAppendingPathComponent:fileName] URLByAppendingPathExtension:PATHS_EXTENSION]; + BOOL success = [data writeToURL:URL atomically:YES]; + + if (!success) { + [self _throwPathStorageError]; + } + + return URL; +} + +- (void)_throwPathStorageError { + @throw [NSException exceptionWithName:NSFileHandleOperationException + reason:PATHS_ARRAY_STORAGE_ERROR + userInfo:nil]; +} + @end diff --git a/ResearchKitActiveTask/Common/Active Step/ORKActiveStepViewController.m b/ResearchKitActiveTask/Common/Active Step/ORKActiveStepViewController.m index cbd3e447a3..8d95970765 100644 --- a/ResearchKitActiveTask/Common/Active Step/ORKActiveStepViewController.m +++ b/ResearchKitActiveTask/Common/Active Step/ORKActiveStepViewController.m @@ -63,7 +63,7 @@ @interface ORKActiveStepViewController () { ORKActiveStepView *_activeStepView; ORKActiveStepTimer *_activeStepTimer; - NSArray *_recorderResults; + NSArray *_recorderResults; SystemSoundID _alertSound; NSURL *_alertSoundURL; @@ -298,11 +298,10 @@ - (void)prepareRecorders { NSMutableArray *recorders = [NSMutableArray array]; for (ORKRecorderConfiguration * provider in self.activeStep.recorderConfigurations) { - // If the outputDirectory is nil, recorders which require one will generate an error. - // We start them anyway, because we don't know which recorders will require an outputDirectory. - ORKRecorder *recorder = [provider recorderForStep:self.step - outputDirectory:self.outputDirectory]; - recorder.configuration = provider; + if (self.outputDirectory != nil) { // Deprecation strategy + provider.outputDirectory = [self.outputDirectory copy]; // Needed while the output directory can be set from ORKTaskViewController. + } + ORKRecorder *recorder = [provider recorderForStep:self.step]; recorder.delegate = self; if (recorder) { [recorders addObject:recorder]; @@ -540,8 +539,8 @@ - (void)stepDidFinish { #pragma mark - ORKRecorderDelegate -- (void)recorder:(ORKRecorder *)recorder didCompleteWithResult:(ORKResult *)result { - _recorderResults = [_recorderResults arrayByAddingObject:result]; +- (void)recorder:(ORKRecorder *)recorder didCompleteWithResults:(NSArray *)results { + _recorderResults = [_recorderResults arrayByAddingObjectsFromArray:results]; [self notifyDelegateOnResultChange]; } diff --git a/ResearchKitActiveTask/Common/ORKOrderedTask+ORKPredefinedActiveTask.h b/ResearchKitActiveTask/Common/ORKOrderedTask+ORKPredefinedActiveTask.h index cddca3cb7a..cae9bbd8cc 100644 --- a/ResearchKitActiveTask/Common/ORKOrderedTask+ORKPredefinedActiveTask.h +++ b/ResearchKitActiveTask/Common/ORKOrderedTask+ORKPredefinedActiveTask.h @@ -40,44 +40,79 @@ NS_ASSUME_NONNULL_BEGIN @interface ORKOrderedTask (ORKPredefinedActiveTask) -/** - Returns a predefined Amsler Grid task that helps in detecting problems in user's vision. - - In an Amsler Grid task, the participant is shown a square grid. The participant is asked to mark the areas where they notice disctortions in the grid. - - Data collected by the task is in the form of an `ORKAmslerGridResult` object. - - @param identifier The task identifier to use for this task, appropriate to the study. - @param intendedUseDescription A localized string describing the intended use of the data collected. - If the value of this parameter is `nil`, the default localized text is displayed. - @param options Options that affect the features of the predefined task. - */ +/// Returns a predefined Amsler Grid task that helps in detecting problems in a user's vision. +/// +/// In an Amsler Grid task, the participant is shown a square grid. The participant is asked to mark the +/// areas where they notice distortions in the grid. +/// +/// The data collected by the task is in the form of an `ORKAmslerGridResult` object. +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, the default localized text +/// is displayed. +/// - options: Options that affect the features of the predefined task. +/// +/// - Returns: An active Amsler Grid task that must be presented using an `ORKTaskViewController` object. +/// +/// - Important: This active task must be presented using an `ORKTaskViewController` object. Make sure to +/// set the `ORKTaskViewController`'s `outputDirectory` property to indicate the directory in which to +/// temporarily store the result files the task may generate. Otherwise, no data will be logged. + (ORKNavigableOrderedTask *)amslerGridTaskWithIdentifier:(NSString *)identifier intendedUseDescription: (nullable NSString *)intendedUseDescription options:(ORKPredefinedTaskOption)options; -/** - Returns a predefined task that measures the upper extremity function. - - In a hole peg test task, the participant is asked to fill holes with pegs. - - A hole peg test task can be used to assess arm and hand function, especially in patients with severe disability. - - Data collected in this task is in the form of an `ORKHolePegTestResult` object. - - @param identifier The task identifier to use for this task, appropriate to the study. - @param intendedUseDescription A localized string describing the intended use of the data - collected. If the value of this parameter is `nil`, the default - localized text will be displayed. - @param dominantHand The participant dominant hand that will be tested first. - @param numberOfPegs The number of pegs to place in the pegboard. - @param threshold The threshold value used for the detection area. - @param rotated A test variant that also requires peg rotation. - @param timeLimit The duration allowed to validate the peg position. - @param options Options that affect the features of the predefined task. - - @return An active hole peg test task that can be presented with an `ORKTaskViewController` object. - */ +/// Returns a predefined Amsler Grid task that helps in detecting problems in a user's vision. +/// +/// In an Amsler Grid task, the participant is shown a square grid. The participant is asked to mark the +/// areas where they notice distortions in the grid. +/// +/// The data collected by the task is in the form of an `ORKAmslerGridResult` object. +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, the default localized text +/// is displayed. +/// - options: Options that affect the features of the predefined task. +/// - outputDirectory: The url to the directory in which all output file data should be written. +/// +/// - Returns: An active Amsler Grid task that can be presented. +/// +/// - Important: If you choose to present this task using an `ORKTaskViewController` object, the value of the +/// controller's `outputDirectory` property - if it is set - will override the `outputDirectory` value passed +/// here. ++ (ORKNavigableOrderedTask *)amslerGridTaskWithIdentifier:(NSString *)identifier + intendedUseDescription: (nullable NSString *)intendedUseDescription + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL *)outputDirectory; + +/// Returns a predefined task that measures the upper extremity function. +/// +/// In a hole peg test task, the participant is asked to fill holes with pegs. +/// +/// A hole peg test task can be used to assess arm and hand function, especially in patients with severe disability. +/// +/// The data collected in this task is in the form of an `ORKHolePegTestResult` object. +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, the default localized text +/// is displayed. +/// - dominantHand: The participant's dominant hand that will be tested first. +/// - numberOfPegs: The number of pegs to place in the pegboard. +/// - threshold: The threshold value used for the detection area. +/// - rotated: A test variant that also requires peg rotation. +/// - timeLimit: The duration allowed to validate the peg position. +/// - options: Options that affect the features of the predefined task. +/// +/// - Returns: An active hole peg test task that must be presented using an `ORKTaskViewController` object. +/// +/// - Important: This active task must be presented using an `ORKTaskViewController` object. Make sure to +/// set the `ORKTaskViewController`'s `outputDirectory` property to indicate the directory in which to +/// temporarily store the result files the task may generate. Otherwise, no data will be logged. + (ORKNavigableOrderedTask *)holePegTestTaskWithIdentifier:(NSString *)identifier intendedUseDescription:(nullable NSString *)intendedUseDescription dominantHand:(ORKBodySagittal)dominantHand @@ -87,85 +122,193 @@ NS_ASSUME_NONNULL_BEGIN timeLimit:(NSTimeInterval)timeLimit options:(ORKPredefinedTaskOption)options; - -/** - Returns a predefined task that consists of a fitness check. - - In a fitness check task, the participant is asked to walk for a specified duration - (typically several minutes). During this period, various sensor data is collected and returned by - the task view controller's delegate. Sensor data can include accelerometer, device motion, - pedometer, location, and heart rate data where available. - - At the conclusion of the walk, if heart rate data is available, the participant is asked to sit - down and rest for a period. Data collection continues during this period. - - By default, the task includes an instruction step that explains what the user needs to do during - the task, but this can be excluded with `ORKPredefinedTaskOptionExcludeInstructions`. - - Data collected from this task can be used to compute measures of general fitness. - - @param identifier The task identifier to use for this task, appropriate to the study. - @param intendedUseDescription A localized string describing the intended use of the data - collected. If the value of this parameter is `nil`, the default - localized text is displayed. - @param walkDuration The duration of the walk (the maximum is 10 minutes). - @param restDuration The duration of the post walk rest period. - @param options Options that affect the features of the predefined task. - - @return An active fitness check task that can be presented with an `ORKTaskViewController` object. - */ +/// Returns a predefined task that measures the upper extremity function. +/// +/// In a hole peg test task, the participant is asked to fill holes with pegs. +/// +/// A hole peg test task can be used to assess arm and hand function, especially in patients with severe disability. +/// +/// The data collected in this task is in the form of an `ORKHolePegTestResult` object. +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, the default localized text +/// is displayed. +/// - dominantHand: The participant's dominant hand that will be tested first. +/// - numberOfPegs: The number of pegs to place in the pegboard. +/// - threshold: The threshold value used for the detection area. +/// - rotated: A test variant that also requires peg rotation. +/// - timeLimit: The duration allowed to validate the peg position. +/// - options: Options that affect the features of the predefined task. +/// - outputDirectory: The url to the directory in which all output file data should be written. +/// +/// - Returns: An active hole peg test task that can be presented. +/// +/// - Important: If you choose to present this task using an `ORKTaskViewController` object, the value of the +/// controller's `outputDirectory` property - if it is set - will override the `outputDirectory` value passed +/// here. ++ (ORKNavigableOrderedTask *)holePegTestTaskWithIdentifier:(NSString *)identifier + intendedUseDescription:(nullable NSString *)intendedUseDescription + dominantHand:(ORKBodySagittal)dominantHand + numberOfPegs:(int)numberOfPegs + threshold:(double)threshold + rotated:(BOOL)rotated + timeLimit:(NSTimeInterval)timeLimit + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL *)outputDirectory; + +/// Returns a predefined task that consists of a fitness check. +/// +/// In a fitness check task, the participant is asked to walk for a specified duration +/// (typically several minutes). During this period, data from various sensors is collected and returned by +/// the task view controller's delegate. Sensor data can include accelerometer, device motion, +/// pedometer, location, and heart rate data where available. +/// +/// At the conclusion of the walk, if heart rate data is available, the participant is asked to sit down and +/// rest for a period. Data collection continues during this period. +/// +/// By default, the task includes an instruction step that explains what the user needs to do during the task, +/// but this can be excluded with `ORKPredefinedTaskOptionExcludeInstructions`. +/// +/// The data collected from this task can be used to compute measures of general fitness. +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, the default localized text is +/// displayed. +/// - walkDuration: The duration of the walk (the maximum is 10 minutes). +/// - restDuration: The duration of the post walk rest period. +/// - options: Options that affect the features of the predefined task. +/// +/// - Returns: An active fitness check task that must be presented using an `ORKTaskViewController` object. +/// +/// - Important: This active task must be presented using an `ORKTaskViewController` object. Make sure to +/// set the `ORKTaskViewController`'s `outputDirectory` property to indicate the directory in which to +/// temporarily store the result files the task may generate. Otherwise, no data will be logged. + (ORKOrderedTask *)fitnessCheckTaskWithIdentifier:(NSString *)identifier intendedUseDescription:(nullable NSString *)intendedUseDescription walkDuration:(NSTimeInterval)walkDuration restDuration:(NSTimeInterval)restDuration options:(ORKPredefinedTaskOption)options; -/** - Returns a predefined task that consists of a 6 Minute Walk Test (6MWT). - - In a 6MWT task, the participant is asked to walk as far as they can in a 6 minute interval. - During this period, various sensor data is collected and returned by the task view controller's - delegate. Sensor data can include accelerometer, device motion, pedometer, location, and - heart rate data where available. - - By default, the task includes an instruction step that explains what the user needs to do during - the task, but this can be excluded with `ORKPredefinedTaskOptionExcludeInstructions`. - - @param identifier The task identifier to use for this task, appropriate to the study. - @param intendedUseDescription A localized string describing the intended use of the data - collected. If the value of this parameter is `nil`, the default - localized text is displayed. - @param options Options that affect the features of the predefined task. - - @return A 6 Minute Walk Test task that can be presented with an `ORKTaskViewController` object. - */ +/// Returns a predefined task that consists of a fitness check. +/// +/// In a fitness check task, the participant is asked to walk for a specified duration +/// (typically several minutes). During this period, data from various sensors is collected and returned by +/// the task view controller's delegate. Sensor data can include accelerometer, device motion, +/// pedometer, location, and heart rate data where available. +/// +/// At the conclusion of the walk, if heart rate data is available, the participant is asked to sit down and +/// rest for a period. Data collection continues during this period. +/// +/// By default, the task includes an instruction step that explains what the user needs to do during the task, +/// but this can be excluded with `ORKPredefinedTaskOptionExcludeInstructions`. +/// +/// The data collected from this task can be used to compute measures of general fitness. +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, the default localized text is +/// displayed. +/// - walkDuration: The duration of the walk (the maximum is 10 minutes). +/// - restDuration: The duration of the post walk rest period. +/// - options: Options that affect the features of the predefined task. +/// - outputDirectory: The url to the directory in which all output file data should be written. +/// +/// - Returns: An active fitness check task that can be presented. +/// +/// - Important: If you choose to present this task using an `ORKTaskViewController` object, the value of the +/// controller's `outputDirectory` property - if it is set - will override the `outputDirectory` value passed +/// here. ++ (ORKOrderedTask *)fitnessCheckTaskWithIdentifier:(NSString *)identifier + intendedUseDescription:(nullable NSString *)intendedUseDescription + walkDuration:(NSTimeInterval)walkDuration + restDuration:(NSTimeInterval)restDuration + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL *)outputDirectory; + +/// Returns a predefined task that consists of a 6 Minute Walk Test (6MWT). +/// +/// In a 6MWT task, the participant is asked to walk as far as they can in a 6 minute interval. +/// During this period, data from various sensors is collected and returned by the task view controller's +/// delegate. Sensor data can include accelerometer, device motion, pedometer, location, and +/// heart rate data where available. +/// +/// By default, the task includes an instruction step that explains what the user needs to do during the task, +/// but this can be excluded with `ORKPredefinedTaskOptionExcludeInstructions`. +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, the default localized text +/// is displayed. +/// - options: Options that affect the features of the predefined task. +/// +/// - Returns: An active 6 Minute Walk Test task that must be presented using an `ORKTaskViewController` object. +/// +/// - Important: This active task must be presented using an `ORKTaskViewController` object. Make sure to +/// set the `ORKTaskViewController`'s `outputDirectory` property to indicate the directory in which to +/// temporarily store the result files the task may generate. Otherwise, no data will be logged. + (ORKOrderedTask *)sixMinuteWalkTaskWithIdentifier:(NSString *)identifier intendedUseDescription:(nullable NSString *)intendedUseDescription options:(ORKPredefinedTaskOption)options API_AVAILABLE(ios(14.0)); -/** - Returns a predefined task that consists of a Tecumseh Cube Test. - - In a Tecumseh Cube Task task, the participant is asked to step up and down onto 20cm high - step or block for 3 minutes, and then prompted to result for 3 minutes. During this period, - various sensor data is collected and returned by the task view controller's delegate. Sensor - data can include accelerometer, device motion, pedometer, location, and heart rate data - where available. - - By default, the task includes an instruction step that explains what the user needs to do during - the task, but this can be excluded with `ORKPredefinedTaskOptionExcludeInstructions`. - - @param identifier The task identifier to use for this task, appropriate to the study. - @param intendedUseDescription A localized string describing the intended use of the data - collected. If the value of this parameter is `nil`, the default - localized text is displayed. - @param audioBundleIdentifier The identifier for the bundle in which the audio file can be found. - @param audioResourceName The name of the audio file to be played. - @param audioFileExtension The file extension for the audio file to be played. - @param options Options that affect the features of the predefined task. - - @return An active Tecumseh Cube Test task that can be presented with an `ORKTaskViewController` object. - */ +/// Returns a predefined task that consists of a 6 Minute Walk Test (6MWT). +/// +/// In a 6MWT task, the participant is asked to walk as far as they can in a 6 minute interval. +/// During this period, data from various sensors is collected and returned by the task view controller's +/// delegate. Sensor data can include accelerometer, device motion, pedometer, location, and +/// heart rate data where available. +/// +/// By default, the task includes an instruction step that explains what the user needs to do during the task, +/// but this can be excluded with `ORKPredefinedTaskOptionExcludeInstructions`. +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, the default localized text +/// is displayed. +/// - options: Options that affect the features of the predefined task. +/// - outputDirectory: The url to the directory in which all output file data should be written. +/// +/// - Returns: An active 6 Minute Walk Test task that can be presented. +/// +/// - Important: If you choose to present this task using an `ORKTaskViewController` object, the value of the +/// controller's `outputDirectory` property - if it is set - will override the `outputDirectory` value passed +/// here. ++ (ORKOrderedTask *)sixMinuteWalkTaskWithIdentifier:(NSString *)identifier + intendedUseDescription:(nullable NSString *)intendedUseDescription + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL*)outputDirectory API_AVAILABLE(ios(14.0)); + +/// Returns a predefined task that consists of a Tecumseh Cube Test. +/// +/// In a Tecumseh Cube Task task, the participant is asked to step up and down onto 20cm high step or block +/// for 3 minutes, and then prompted to rest for 3 minutes. During this period, data from various sensors is +/// collected and returned by the task view controller's delegate. Sensor data can include accelerometer, +/// device motion, pedometer, location, and heart rate data where available. +/// +/// By default, the task includes an instruction step that explains what the user needs to do during the task, +/// but this can be excluded with `ORKPredefinedTaskOptionExcludeInstructions`. +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, the default localized text +/// is displayed. +/// - audioBundleIdentifier: The identifier for the bundle in which the audio file can be found. +/// - audioResourceName: The name of the audio file to be played. +/// - audioFileExtension: The file extension for the audio file to be played. +/// - options: Options that affect the features of the predefined task. +/// +/// - Returns: An active Tecumseh Cube Test task that must be presented using an `ORKTaskViewController` object. +/// +/// - Important: This active task must be presented using an `ORKTaskViewController` object. Make sure to +/// set the `ORKTaskViewController`'s `outputDirectory` property to indicate the directory in which to +/// temporarily store the result files the task may generate. Otherwise, no data will be logged. + (ORKOrderedTask *)tecumsehCubeTaskWithIdentifier:(NSString *)identifier intendedUseDescription:(nullable NSString *)intendedUseDescription audioBundleIdentifier:(NSString *)audioBundleIdentifier @@ -173,139 +316,313 @@ NS_ASSUME_NONNULL_BEGIN audioFileExtension:(nullable NSString*)audioFileExtension options:(ORKPredefinedTaskOption)options API_AVAILABLE(ios(14.0)); -/** - Returns a predefined task that consists of a short walk. - - In a short walk task, the participant is asked to walk a short distance, which may be indoors. - Typical uses of the resulting data are to assess stride length, smoothness, sway, or other aspects - of the participant's gait. - - The presentation of the short walk task differs from the fitness check task in that the distance is - replaced by the number of steps taken, and the walk is split into a series of legs. After each leg, - the user is asked to turn and reverse direction. - - The data collected by this task can include accelerometer, device motion, and pedometer data. - - @param identifier The task identifier to use for this task, appropriate to the study. - @param intendedUseDescription A localized string describing the intended use of the data - collected. If the value of this parameter is `nil`, the default - localized text is displayed. - @param numberOfStepsPerLeg The number of steps the participant is asked to walk. If the - pedometer is unavailable, a distance is suggested and a suitable - count down timer is displayed for each leg of the walk. - @param restDuration The duration of the rest period. When the value of this parameter is - nonzero, the user is asked to stand still for the specified rest - period after the turn sequence has been completed, and baseline - data is collected. - @param options Options that affect the features of the predefined task. - - @return An active short walk task that can be presented with an `ORKTaskViewController` object. - */ +/// Returns a predefined task that consists of a Tecumseh Cube Test. +/// +/// In a Tecumseh Cube Task task, the participant is asked to step up and down onto 20cm high step or block +/// for 3 minutes, and then prompted to rest for 3 minutes. During this period, data from various sensors is +/// collected and returned by the task view controller's delegate. Sensor data can include accelerometer, +/// device motion, pedometer, location, and heart rate data where available. +/// +/// By default, the task includes an instruction step that explains what the user needs to do during the task, +/// but this can be excluded with `ORKPredefinedTaskOptionExcludeInstructions`. +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, the default localized text +/// is displayed. +/// - audioBundleIdentifier: The identifier for the bundle in which the audio file can be found. +/// - audioResourceName: The name of the audio file to be played. +/// - audioFileExtension: The file extension for the audio file to be played. +/// - options: Options that affect the features of the predefined task. +/// - outputDirectory: The url to the directory in which all output file data should be written. +/// +/// - Returns: An active Tecumseh Cube Test task that can be presented. +/// +/// - Important: If you choose to present this task using an `ORKTaskViewController` object, the value of the +/// controller's `outputDirectory` property - if it is set - will override the `outputDirectory` value passed +/// here. ++ (ORKOrderedTask *)tecumsehCubeTaskWithIdentifier:(NSString *)identifier + intendedUseDescription:(nullable NSString *)intendedUseDescription + audioBundleIdentifier:(NSString *)audioBundleIdentifier + audioResourceName:(NSString *)audioResourceName + audioFileExtension:(nullable NSString*)audioFileExtension + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL*)outputDirectory API_AVAILABLE(ios(14.0)); + +/// Returns a predefined task that consists of a short walk. +/// +/// In a short walk task, the participant is asked to walk a short distance, which may be indoors. +/// Typical uses of the resulting data are to assess stride length, smoothness, sway, or other aspects +/// of the participant's gait. +/// +/// The presentation of the short walk task differs from the fitness check task in that the distance is +/// replaced by the number of steps taken, and the walk is split into a series of legs. After each leg, +/// the user is asked to turn and reverse direction. +/// +/// The data collected by this task can include accelerometer, device motion, and pedometer data. +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, the default localized text +/// is displayed. +/// - numberOfStepsPerLeg: The number of steps the participant is asked to walk. If the pedometer is +/// unavailable, a distance is suggested and a suitable count down timer +/// is displayed for each leg of the walk. +/// - restDuration: The duration of the rest period. When the value of this parameter is +/// nonzero, the user is asked to stand still for the specified rest +/// period after the turn sequence has been completed, and baseline data +/// is collected. +/// - options: Options that affect the features of the predefined task. +/// +/// - Returns An active short walk task that must be presented using an `ORKTaskViewController` object. +/// +/// - Important: This active task must be presented using an `ORKTaskViewController` object. Make sure to +/// set the `ORKTaskViewController`'s `outputDirectory` property to indicate the directory in which to +/// temporarily store the result files the task may generate. Otherwise, no data will be logged. + (ORKOrderedTask *)shortWalkTaskWithIdentifier:(NSString *)identifier intendedUseDescription:(nullable NSString *)intendedUseDescription numberOfStepsPerLeg:(NSInteger)numberOfStepsPerLeg restDuration:(NSTimeInterval)restDuration options:(ORKPredefinedTaskOption)options; -/** - Returns a predefined task that consists of a short walk back and forth. - - In a short walk task, the participant is asked to walk a short distance, which may be indoors. - Typical uses of the resulting data are to assess stride length, smoothness, sway, or other aspects - of the participant's gait. - - The presentation of the back and forth walk task differs from the short walk in that the participant - is asked to walk back and forth rather than walking in a straight line for a certain number of steps. - - The participant is then asked to turn in a full circle and then stand still. - - This task is intended to allow the participant to walk in a confined space where the participant - does not have access to a long hallway to walk in a continuous straight line. Additionally, by asking - the participant to turn in a full circle and then stand still, the activity can access balance and - concentration. - - The data collected by this task can include accelerometer, device motion, and pedometer data. - - @param identifier The task identifier to use for this task, appropriate to the study. - @param intendedUseDescription A localized string describing the intended use of the data - collected. If the value of this parameter is `nil`, the default - localized text is displayed. - @param walkDuration The duration of the walking period. - @param restDuration The duration of the rest period. When the value of this parameter is - nonzero, the user is asked to stand still for the specified rest - period after the turn sequence has been completed, and baseline - data is collected. - @param options Options that affect the features of the predefined task. - - @return An active short walk task that can be presented with an `ORKTaskViewController` object. - */ +/// Returns a predefined task that consists of a short walk. +/// +/// In a short walk task, the participant is asked to walk a short distance, which may be indoors. +/// Typical uses of the resulting data are to assess stride length, smoothness, sway, or other aspects +/// of the participant's gait. +/// +/// The presentation of the short walk task differs from the fitness check task in that the distance is +/// replaced by the number of steps taken, and the walk is split into a series of legs. After each leg, +/// the user is asked to turn and reverse direction. +/// +/// The data collected by this task can include accelerometer, device motion, and pedometer data. +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, the default localized text +/// is displayed. +/// - numberOfStepsPerLeg: The number of steps the participant is asked to walk. If the pedometer is +/// unavailable, a distance is suggested and a suitable count down timer +/// is displayed for each leg of the walk. +/// - restDuration: The duration of the rest period. When the value of this parameter is +/// nonzero, the user is asked to stand still for the specified rest +/// period after the turn sequence has been completed, and baseline data +/// is collected. +/// - options: Options that affect the features of the predefined task. +/// - outputDirectory: The url to the directory in which all output file data should be written. +/// +/// - Returns An active short walk task that can be presented. +/// +/// - Important: If you choose to present this task using an `ORKTaskViewController` object, the value of the +/// controller's `outputDirectory` property - if it is set - will override the `outputDirectory` value passed +/// here. ++ (ORKOrderedTask *)shortWalkTaskWithIdentifier:(NSString *)identifier + intendedUseDescription:(nullable NSString *)intendedUseDescription + numberOfStepsPerLeg:(NSInteger)numberOfStepsPerLeg + restDuration:(NSTimeInterval)restDuration + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL*)outputDirectory; + +/// Returns a predefined task that consists of a short walk back and forth. +/// +/// In a short walk task, the participant is asked to walk a short distance, which may be indoors. +/// Typical uses of the resulting data are to assess stride length, smoothness, sway, or other aspects +/// of the participant's gait. +/// +/// The presentation of the back and forth walk task differs from the short walk in that the participant +/// is asked to walk back and forth rather than walking in a straight line for a certain number of steps. +/// +/// The participant is then asked to turn in a full circle and then stand still. +/// +/// This task is intended to allow the participant to walk in a confined space where the participant +/// does not have access to a long hallway to walk in a continuous straight line. Additionally, by asking +/// the participant to turn in a full circle and then stand still, the activity can assess balance and +/// concentration. +/// +/// The data collected by this task can include accelerometer, device motion, and pedometer data. +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, the default localized text +/// is displayed. +/// - walkDuration: The duration of the walking period. +/// - restDuration: The duration of the rest period. When the value of this parameter is +/// nonzero, the user is asked to stand still for the specified rest +/// period after the turn sequence has been completed, and baseline data +/// is collected. +/// - options: Options that affect the features of the predefined task. +/// +/// - Returns: An active back and forth walk task that must be presented using an `ORKTaskViewController` object. +/// +/// - Important: This active task must be presented using an `ORKTaskViewController` object. Make sure to +/// set the `ORKTaskViewController`'s `outputDirectory` property to indicate the directory in which to +/// temporarily store the result files the task may generate. Otherwise, no data will be logged. + (ORKOrderedTask *)walkBackAndForthTaskWithIdentifier:(NSString *)identifier intendedUseDescription:(nullable NSString *)intendedUseDescription walkDuration:(NSTimeInterval)walkDuration restDuration:(NSTimeInterval)restDuration options:(ORKPredefinedTaskOption)options; - -/** - The knee range of motion task returns a task that measures the range of motion for either a left or right knee. - - @param identifier The task identifier to use for this task, appropriate to the study. - @param limbOption Which knee is being measured. - @param intendedUseDescription A localized string describing the intended use of the data collected. If the value of this parameter is `nil`, default localized text is used. - @param options Options that affect the features of the predefined task. - */ +/// Returns a predefined task that consists of a short walk back and forth. +/// +/// In a short walk task, the participant is asked to walk a short distance, which may be indoors. +/// Typical uses of the resulting data are to assess stride length, smoothness, sway, or other aspects +/// of the participant's gait. +/// +/// The presentation of the back and forth walk task differs from the short walk in that the participant +/// is asked to walk back and forth rather than walking in a straight line for a certain number of steps. +/// +/// The participant is then asked to turn in a full circle and then stand still. +/// +/// This task is intended to allow the participant to walk in a confined space where the participant +/// does not have access to a long hallway to walk in a continuous straight line. Additionally, by asking +/// the participant to turn in a full circle and then stand still, the activity can assess balance and +/// concentration. +/// +/// The data collected by this task can include accelerometer, device motion, and pedometer data. +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, the default localized text +/// is displayed. +/// - walkDuration: The duration of the walking period. +/// - restDuration: The duration of the rest period. When the value of this parameter is +/// nonzero, the user is asked to stand still for the specified rest +/// period after the turn sequence has been completed, and baseline data +/// is collected. +/// - options: Options that affect the features of the predefined task. +/// - outputDirectory: The url to the directory in which all output file data should be written. +/// +/// - Returns: An active back and forth walk task that can be presented. +/// +/// - Important: If you choose to present this task using an `ORKTaskViewController` object, the value of the +/// controller's `outputDirectory` property - if it is set - will override the `outputDirectory` value passed +/// here. ++ (ORKOrderedTask *)walkBackAndForthTaskWithIdentifier:(NSString *)identifier + intendedUseDescription:(nullable NSString *)intendedUseDescription + walkDuration:(NSTimeInterval)walkDuration + restDuration:(NSTimeInterval)restDuration + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL*)outputDirectory; + +/// The knee range of motion task returns a task that measures the range of motion for either a left or right knee. +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - limbOption: Which knee is being measured. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, default localized text is used. +/// - options: Options that affect the features of the predefined task. +/// +/// - Returns: An active knee range of motion task that must be presented using an `ORKTaskViewController` object. +/// +/// - Important: This active task must be presented using an `ORKTaskViewController` object. Make sure to +/// set the `ORKTaskViewController`'s `outputDirectory` property to indicate the directory in which to +/// temporarily store the result files the task may generate. Otherwise, no data will be logged. + (ORKOrderedTask *)kneeRangeOfMotionTaskWithIdentifier:(NSString *)identifier limbOption:(ORKPredefinedTaskLimbOption)limbOption intendedUseDescription:(nullable NSString *)intendedUseDescription options:(ORKPredefinedTaskOption)options; - -/** - The shoulder range of motion task returns a task that measures the range of motion for either a left or right shoulder. - - @param identifier The task identifier to use for this task, appropriate to the study. - @param limbOption Which shoulder is being measured. - @param intendedUseDescription A localized string describing the intended use of the data collected. If the value of this parameter is `nil`, default localized text is used. - @param options Options that affect the features of the predefined task. - */ +/// The knee range of motion task returns a task that measures the range of motion for either a left or right knee. +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - limbOption: Which knee is being measured. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, default localized text is used. +/// - options: Options that affect the features of the predefined task. +/// - outputDirectory: The url to the directory in which all output file data should be written. +/// +/// - Returns: An active knee range of motion task that can be presented. +/// +/// - Important: If you choose to present this task using an `ORKTaskViewController` object, the value of the +/// controller's `outputDirectory` property - if it is set - will override the `outputDirectory` value passed +/// here. ++ (ORKOrderedTask *)kneeRangeOfMotionTaskWithIdentifier:(NSString *)identifier + limbOption:(ORKPredefinedTaskLimbOption)limbOption + intendedUseDescription:(nullable NSString *)intendedUseDescription + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL*)outputDirectory; + +/// The shoulder range of motion task returns a task that measures the range of motion for either a left or right shoulder. +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - limbOption: Which shoulder is being measured. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, default localized text is used. +/// - options: Options that affect the features of the predefined task. +/// +/// - Returns: An active shoulder range of motion task that must be presented using an `ORKTaskViewController` object. +/// +/// - Important: This active task must be presented using an `ORKTaskViewController` object. Make sure to +/// set the `ORKTaskViewController`'s `outputDirectory` property to indicate the directory in which to +/// temporarily store the result files the task may generate. Otherwise, no data will be logged. + (ORKOrderedTask *)shoulderRangeOfMotionTaskWithIdentifier:(NSString *)identifier limbOption:(ORKPredefinedTaskLimbOption)limbOption intendedUseDescription:(nullable NSString *)intendedUseDescription options:(ORKPredefinedTaskOption)options; - -/** - Returns a predefined task that enables an audio recording WITH a check of the audio level. - - In an audio recording task, the participant is asked to make some kind of sound - with their voice, and the audio data is collected. - - An audio task can be used to measure properties of the user's voice, such as - frequency range, or the ability to pronounce certain sounds. - - If `checkAudioLevel == YES` then a navigation rule is added to do a simple check of the background - noise level. If the background noise is too loud, then the participant is instructed to move to a - quieter location before trying again. - - Data collected in this task consists of audio information. - - @param identifier The task identifier to use for this task, appropriate to the study. - @param intendedUseDescription A localized string describing the intended use of the data - collected. If the value of this parameter is `nil`, default - localized text is used. - @param speechInstruction Instructional content describing what the user needs to do when - recording begins. If the value of this parameter is `nil`, - default localized text is used. - @param shortSpeechInstruction Instructional content shown during audio recording. If the value of - this parameter is `nil`, default localized text is used. - @param duration The length of the count down timer that runs while audio data is - collected. - @param recordingSettings See "AV Foundation Audio Settings Constants" for possible values. - @param checkAudioLevel If `YES` then add navigational rules to check the background noise level. - @param options Options that affect the features of the predefined task. - - @return An active audio task that can be presented with an `ORKTaskViewController` object. - */ +/// The shoulder range of motion task returns a task that measures the range of motion for either a left or right shoulder. +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - limbOption: Which shoulder is being measured. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, default localized text is used. +/// - options: Options that affect the features of the predefined task. +/// - outputDirectory: The url to the directory in which all output file data should be written. +/// +/// - Returns: An active shoulder range of motion task that can be presented. +/// +/// - Important: If you choose to present this task using an `ORKTaskViewController` object, the value of the +/// controller's `outputDirectory` property - if it is set - will override the `outputDirectory` value passed +/// here. ++ (ORKOrderedTask *)shoulderRangeOfMotionTaskWithIdentifier:(NSString *)identifier + limbOption:(ORKPredefinedTaskLimbOption)limbOption + intendedUseDescription:(nullable NSString *)intendedUseDescription + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL*)outputDirectory; + +/// Returns a predefined task that enables an audio recording WITH a check of the audio level. +/// +/// In an audio recording task, the participant is asked to make some kind of sound +/// with their voice, and the audio data is collected. +/// +/// An audio task can be used to measure properties of the user's voice, such as +/// frequency range, or the ability to pronounce certain sounds. +/// +/// If `checkAudioLevel == YES` then a navigation rule is added to do a simple check of the background +/// noise level. If the background noise is too loud, then the participant is instructed to move to a +/// quieter location before trying again. +/// +/// Data collected in this task consists of audio information. +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, default localized text is used. +/// - speechInstruction: Instructional content describing what the user needs to do when recording +/// begins. If the value of this parameter is `nil`, default localized +/// text is used. +/// - shortSpeechInstruction: Instructional content shown during audio recording. If the value of this +/// parameter is `nil`, default localized text is used. +/// - duration: The length of the count down timer that runs while audio data is collected. +/// - recordingSettings: See "AV Foundation Audio Settings Constants" for possible values. +/// - checkAudioLevel: If `YES` then add navigational rules to check the background noise level. +/// - options: Options that affect the features of the predefined task. +/// +/// - Returns: An active audio task that must be presented using an `ORKTaskViewController` object. +/// +/// - Important: This active task must be presented using an `ORKTaskViewController` object. Make sure to +/// set the `ORKTaskViewController`'s `outputDirectory` property to indicate the directory in which to +/// temporarily store the result files the task may generate. Otherwise, no data will be logged. + (ORKNavigableOrderedTask *)audioTaskWithIdentifier:(NSString *)identifier intendedUseDescription:(nullable NSString *)intendedUseDescription speechInstruction:(nullable NSString *)speechInstruction @@ -315,29 +632,74 @@ NS_ASSUME_NONNULL_BEGIN checkAudioLevel:(BOOL)checkAudioLevel options:(ORKPredefinedTaskOption)options; - -/** - Returns a predefined task that consists of two finger tapping (Optionally with a hand specified) - - In a two finger tapping task, the participant is asked to rhythmically and alternately tap two - targets on the device screen. - - A two finger tapping task can be used to assess basic motor capabilities including speed, accuracy, - and rhythm. - - Data collected in this task includes touch activity and accelerometer information. - - @param identifier The task identifier to use for this task, appropriate to the study. - @param intendedUseDescription A localized string describing the intended use of the data - collected. If the value of this parameter is `nil`, the default - localized text will be displayed. - @param duration The length of the count down timer that runs while touch data is - collected. - @param handOptions Options for determining which hand(s) to test. - @param options Options that affect the features of the predefined task. - - @return An active two finger tapping task that can be presented with an `ORKTaskViewController` object. - */ +/// Returns a predefined task that enables an audio recording WITH a check of the audio level. +/// +/// In an audio recording task, the participant is asked to make some kind of sound +/// with their voice, and the audio data is collected. +/// +/// An audio task can be used to measure properties of the user's voice, such as +/// frequency range, or the ability to pronounce certain sounds. +/// +/// If `checkAudioLevel == YES` then a navigation rule is added to do a simple check of the background +/// noise level. If the background noise is too loud, then the participant is instructed to move to a +/// quieter location before trying again. +/// +/// Data collected in this task consists of audio information. +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, default localized text is used. +/// - speechInstruction: Instructional content describing what the user needs to do when recording +/// begins. If the value of this parameter is `nil`, default localized +/// text is used. +/// - shortSpeechInstruction: Instructional content shown during audio recording. If the value of this +/// parameter is `nil`, default localized text is used. +/// - duration: The length of the count down timer that runs while audio data is collected. +/// - recordingSettings: See "AV Foundation Audio Settings Constants" for possible values. +/// - checkAudioLevel: If `YES` then add navigational rules to check the background noise level. +/// - options: Options that affect the features of the predefined task. +/// - outputDirectory: The url to the directory in which all output file data should be written. +/// +/// - Returns: An active audio task that can be presented. +/// +/// - Important: If you choose to present this task using an `ORKTaskViewController` object, the value of the +/// controller's `outputDirectory` property - if it is set - will override the `outputDirectory` value passed +/// here. ++ (ORKNavigableOrderedTask *)audioTaskWithIdentifier:(NSString *)identifier + intendedUseDescription:(nullable NSString *)intendedUseDescription + speechInstruction:(nullable NSString *)speechInstruction + shortSpeechInstruction:(nullable NSString *)shortSpeechInstruction + duration:(NSTimeInterval)duration + recordingSettings:(nullable NSDictionary *)recordingSettings + checkAudioLevel:(BOOL)checkAudioLevel + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL*)outputDirectory; + +/// Returns a predefined task that consists of two finger tapping (Optionally with a hand specified) +/// +/// In a two finger tapping task, the participant is asked to rhythmically and alternately tap two +/// targets on the device screen. +/// +/// A two finger tapping task can be used to assess basic motor capabilities including speed, accuracy, +/// and rhythm. +/// +/// Data collected in this task includes touch activity and accelerometer information. +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, the default localized text +/// is displayed. +/// - duration: The length of the count down timer that runs while touch data is collected. +/// - handOptions: Options for determining which hand(s) to test. +/// - options: Options that affect the features of the predefined task. +/// +/// - Returns: An active two finger tapping task that must be presented using an `ORKTaskViewController` object. +/// +/// - Important: This active task must be presented using an `ORKTaskViewController` object. Make sure to +/// set the `ORKTaskViewController`'s `outputDirectory` property to indicate the directory in which to +/// temporarily store the result files the task may generate. Otherwise, no data will be logged. + (ORKOrderedTask *)twoFingerTappingIntervalTaskWithIdentifier:(NSString *)identifier intendedUseDescription:(nullable NSString *)intendedUseDescription duration:(NSTimeInterval)duration @@ -345,53 +707,87 @@ NS_ASSUME_NONNULL_BEGIN options:(ORKPredefinedTaskOption)options; -/** - Returns a predefined task that tests spatial span memory. - - In a spatial span memory task, the participant is asked to repeat pattern sequences of increasing - length in a game-like environment. You can use this task to assess visuospatial memory and - executive function. - - - In each round of the task, an array of - target images are shown in a grid (by default, the images are flowers). The round consists of a - demonstration phase and an interactive phase. In the demonstration phase, some of the flowers - change color in a specific sequence. After the demonstration, the user is asked to tap the flowers - in the same sequence for the interactive phase. - - The span (that is, the length of the pattern sequence) is automatically varied during the task, - increasing after users succeed and decreasing after they fail, within the range specified by - minimum and maximum values that you specify. You can also customize the speed of sequence playback - and the shape of the tap target. - - A spatial span memory task finishes when the user has either completed the maximum number of tests - or made the maximum number of errors. - - Data collected by the task is in the form of an `ORKSpatialSpanMemoryResult` object. - - @param identifier The task identifier to use for this task, appropriate to the study. - @param intendedUseDescription A localized string describing the intended use of the data - collected. If the value of this parameter is `nil`, the default - localized text is displayed. - @param initialSpan The sequence length of the initial memory pattern. - @param minimumSpan The minimum pattern sequence length. - @param maximumSpan The maximum pattern sequence length. - @param playSpeed The time per sequence item; a smaller value means faster sequence - play. - @param maximumTests The maximum number of rounds to conduct. - @param maximumConsecutiveFailures The maximum number of consecutive failures the user can make before - the task is terminated. - @param customTargetImage The image to use for the task. By default, and if the value of this - parameter is `nil`, the image is a flower. To supply a custom - image, create a template image to which iOS adds the tint color. - @param customTargetPluralName The name associated with `customTargetImage`; by default, the value - of this parameter is @"flowers". - @param requireReversal A Boolean value that indicates whether to require the user to tap - the sequence in reverse order. - @param options Options that affect the features of the predefined task. - - @return An active spatial span memory task that can be presented with an `ORKTaskViewController` object. - */ +/// Returns a predefined task that consists of two finger tapping (Optionally with a hand specified) +/// +/// In a two finger tapping task, the participant is asked to rhythmically and alternately tap two +/// targets on the device screen. +/// +/// A two finger tapping task can be used to assess basic motor capabilities including speed, accuracy, +/// and rhythm. +/// +/// Data collected in this task includes touch activity and accelerometer information. +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, the default localized text +/// is displayed. +/// - duration: The length of the count down timer that runs while touch data is collected. +/// - handOptions: Options for determining which hand(s) to test. +/// - options: Options that affect the features of the predefined task. +/// - outputDirectory: The url to the directory in which all output file data should be written. +/// +/// - Returns: An active two finger tapping task that can be presented. +/// +/// - Important: If you choose to present this task using an `ORKTaskViewController` object, the value of the +/// controller's `outputDirectory` property - if it is set - will override the `outputDirectory` value passed +/// here. ++ (ORKOrderedTask *)twoFingerTappingIntervalTaskWithIdentifier:(NSString *)identifier + intendedUseDescription:(nullable NSString *)intendedUseDescription + duration:(NSTimeInterval)duration + handOptions:(ORKPredefinedTaskHandOption)handOptions + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL*)outputDirectory; + +/// Returns a predefined task that tests spatial span memory. +/// +/// In a spatial span memory task, the participant is asked to repeat pattern sequences of increasing +/// length in a game-like environment. You can use this task to assess visuospatial memory and +/// executive function. +/// +/// +/// In each round of the task, an array of +/// target images are shown in a grid (by default, the images are flowers). The round consists of a +/// demonstration phase and an interactive phase. In the demonstration phase, some of the flowers +/// change color in a specific sequence. After the demonstration, the user is asked to tap the flowers +/// in the same sequence for the interactive phase. +/// +/// The span (that is, the length of the pattern sequence) is automatically varied during the task, +/// increasing after users succeed and decreasing after they fail, within the range specified by +/// minimum and maximum values that you specify. You can also customize the speed of sequence playback +/// and the shape of the tap target. +/// +/// A spatial span memory task finishes when the user has either completed the maximum number of tests +/// or made the maximum number of errors. +/// +/// Data collected by the task is in the form of an `ORKSpatialSpanMemoryResult` object. +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, the default localized +/// text is displayed. +/// - initialSpan: The sequence length of the initial memory pattern. +/// - minimumSpan: The minimum pattern sequence length. +/// - maximumSpan: The maximum pattern sequence length. +/// - playSpeed: The time per sequence item; a smaller value means faster sequence play. +/// - maximumTests: The maximum number of rounds to conduct. +/// - maximumConsecutiveFailures: The maximum number of consecutive failures the user can make before +/// the task is terminated. +/// - customTargetImage: The image to use for the task. By default, and if the value of this +/// parameter is `nil`, the image is a flower. To supply a custom image, +/// create a template image to which iOS adds the tint color. +/// - customTargetPluralName: The name associated with `customTargetImage`; by default, the value of +/// this parameter is @"flowers". +/// - requireReversal: A Boolean value that indicates whether to require the user to tap the +/// sequence in reverse order. +/// - options: Options that affect the features of the predefined task. +/// +/// - Returns: An active spatial span memory task that must be presented using an `ORKTaskViewController` object. +/// +/// - Important: This active task must be presented using an `ORKTaskViewController` object. Make sure to +/// set the `ORKTaskViewController`'s `outputDirectory` property to indicate the directory in which to +/// temporarily store the result files the task may generate. Otherwise, no data will be logged. + (ORKOrderedTask *)spatialSpanMemoryTaskWithIdentifier:(NSString *)identifier intendedUseDescription:(nullable NSString *)intendedUseDescription initialSpan:(NSInteger)initialSpan @@ -405,46 +801,156 @@ NS_ASSUME_NONNULL_BEGIN requireReversal:(BOOL)requireReversal options:(ORKPredefinedTaskOption)options; -/** - Returns a predefined Stroop task that tests participants selective attention and cognitive flexibility. - - In a stroop task, the participant is shown a text. The text is a name of a color, but the text is printed in a color that may or may not be denoted by the name. In each attempt of the task, the participant has to press the button that corresponds to the first letter of the color in which the text is printed. The participant has to ignore the name of the color written in the text, but respond based on the color of the text. - - A stroop task finishes when the user has completed all the attempts, irrespective of correct or incorrect answers. - - Data collected by the task is in the form of an `ORKStroopResult` object. - - @param identifier The task identifier to use for this task, appropriate to the study. - @param intendedUseDescription A localized string describing the intended use of the data - collected. If the value of this parameter is `nil`, the default - localized text is displayed. - @param numberOfAttempts Total number of stroop questions to include in the task. - @param options Options that affect the features of the predefined task. - */ +/// Returns a predefined task that tests spatial span memory. +/// +/// In a spatial span memory task, the participant is asked to repeat pattern sequences of increasing +/// length in a game-like environment. You can use this task to assess visuospatial memory and +/// executive function. +/// +/// +/// In each round of the task, an array of +/// target images are shown in a grid (by default, the images are flowers). The round consists of a +/// demonstration phase and an interactive phase. In the demonstration phase, some of the flowers +/// change color in a specific sequence. After the demonstration, the user is asked to tap the flowers +/// in the same sequence for the interactive phase. +/// +/// The span (that is, the length of the pattern sequence) is automatically varied during the task, +/// increasing after users succeed and decreasing after they fail, within the range specified by +/// minimum and maximum values that you specify. You can also customize the speed of sequence playback +/// and the shape of the tap target. +/// +/// A spatial span memory task finishes when the user has either completed the maximum number of tests +/// or made the maximum number of errors. +/// +/// Data collected by the task is in the form of an `ORKSpatialSpanMemoryResult` object. +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, the default localized +/// text is displayed. +/// - initialSpan: The sequence length of the initial memory pattern. +/// - minimumSpan: The minimum pattern sequence length. +/// - maximumSpan: The maximum pattern sequence length. +/// - playSpeed: The time per sequence item; a smaller value means faster sequence play. +/// - maximumTests: The maximum number of rounds to conduct. +/// - maximumConsecutiveFailures: The maximum number of consecutive failures the user can make before +/// the task is terminated. +/// - customTargetImage: The image to use for the task. By default, and if the value of this +/// parameter is `nil`, the image is a flower. To supply a custom image, +/// create a template image to which iOS adds the tint color. +/// - customTargetPluralName: The name associated with `customTargetImage`; by default, the value of +/// this parameter is @"flowers". +/// - requireReversal: A Boolean value that indicates whether to require the user to tap the +/// sequence in reverse order. +/// - options: Options that affect the features of the predefined task. +/// - outputDirectory: The url to the directory in which all output file data should be written. +/// +/// - Returns: An active spatial span memory task that can be presented. +/// +/// - Important: If you choose to present this task using an `ORKTaskViewController` object, the value of the +/// controller's `outputDirectory` property - if it is set - will override the `outputDirectory` value passed +/// here. ++ (ORKOrderedTask *)spatialSpanMemoryTaskWithIdentifier:(NSString *)identifier + intendedUseDescription:(nullable NSString *)intendedUseDescription + initialSpan:(NSInteger)initialSpan + minimumSpan:(NSInteger)minimumSpan + maximumSpan:(NSInteger)maximumSpan + playSpeed:(NSTimeInterval)playSpeed + maximumTests:(NSInteger)maximumTests + maximumConsecutiveFailures:(NSInteger)maximumConsecutiveFailures + customTargetImage:(nullable UIImage *)customTargetImage + customTargetPluralName:(nullable NSString *)customTargetPluralName + requireReversal:(BOOL)requireReversal + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL*)outputDirectory; + +/// Returns a predefined Stroop task that tests participants selective attention and cognitive flexibility. +/// +/// In a stroop task, the participant is shown a text. The text is a name of a color, but the text is printed +/// in a color that may or may not be denoted by the name. In each attempt of the task, the participant has to +/// press the button that corresponds to the first letter of the color in which the text is printed. +/// The participant has to ignore the name of the color written in the text, but respond based on the color +/// of the text. +/// +/// A stroop task finishes when the user has completed all the attempts, irrespective of correct or incorrect answers. +/// +/// Data collected by the task is in the form of an `ORKStroopResult` object. +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, the default localized text is +/// displayed. +/// - numberOfAttempts: Total number of stroop questions to include in the task. +/// - options: Options that affect the features of the predefined task. +/// +/// - Returns: An active stroop task that must be presented using an `ORKTaskViewController` object. +/// +/// - Important: This active task must be presented using an `ORKTaskViewController` object. Make sure to +/// set the `ORKTaskViewController`'s `outputDirectory` property to indicate the directory in which to +/// temporarily store the result files the task may generate. Otherwise, no data will be logged. + (ORKOrderedTask *)stroopTaskWithIdentifier:(NSString *)identifier intendedUseDescription:(nullable NSString *)intendedUseDescription numberOfAttempts:(NSInteger)numberOfAttempts options:(ORKPredefinedTaskOption)options; -/** - Returns a predefined Speech Recognition task that transcribes participant's speech. - - In a Speech Recognition task, the participant is shown a text or image or both. The participant has to read the text aloud, or describe the image. - - A Speech Recognition task finishes when the user presses the Stop Recording button. - - Data collected by the task is in the form of an `ORKSpeechRecognitionResult` object. - - @param identifier The task identifier to use for this task, appropriate to the study. - @param intendedUseDescription A localized string describing the intended use of the data collected. - If the value of this parameter is `nil`, the default localized text is displayed. - @param speechRecognizerLocale An enum that represents the locale to be used by speech recognition API. - @param speechRecognitionImage The image shown to the participant. - @param speechRecognitionText The text shown to the participant. - @param shouldHideTranscript The boolean value used to show or hide the transcription from the user. - @param allowsEdittingTranscript The boolean value used to present step that allows editting transcription. - @param options Options that affect the features of the predefined task. - */ +/// Returns a predefined Stroop task that tests participants selective attention and cognitive flexibility. +/// +/// In a stroop task, the participant is shown a text. The text is a name of a color, but the text is printed +/// in a color that may or may not be denoted by the name. In each attempt of the task, the participant has to +/// press the button that corresponds to the first letter of the color in which the text is printed. +/// The participant has to ignore the name of the color written in the text, but respond based on the color +/// of the text. +/// +/// A stroop task finishes when the user has completed all the attempts, irrespective of correct or incorrect answers. +/// +/// Data collected by the task is in the form of an `ORKStroopResult` object. +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, the default localized text is +/// displayed. +/// - numberOfAttempts: Total number of stroop questions to include in the task. +/// - options: Options that affect the features of the predefined task. +/// - outputDirectory: The url to the directory in which all output file data should be written. +/// +/// - Returns: An active stroop task that can be presented. +/// +/// - Important: If you choose to present this task using an `ORKTaskViewController` object, the value of the +/// controller's `outputDirectory` property - if it is set - will override the `outputDirectory` value passed +/// here. ++ (ORKOrderedTask *)stroopTaskWithIdentifier:(NSString *)identifier + intendedUseDescription:(nullable NSString *)intendedUseDescription + numberOfAttempts:(NSInteger)numberOfAttempts + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL*)outputDirectory; + +/// Returns a predefined Speech Recognition task that transcribes participant's speech. +/// +/// In a Speech Recognition task, the participant is shown a text or image or both. The participant has to read the text aloud, or describe the image. +/// +/// A Speech Recognition task finishes when the user presses the Stop Recording button. +/// +/// Data collected by the task is in the form of an `ORKSpeechRecognitionResult` object. +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, the default localized text is displayed. +/// - speechRecognizerLocale: An enum that represents the locale to be used by speech recognition API. +/// - speechRecognitionImage: The image shown to the participant. +/// - speechRecognitionText: The text shown to the participant. +/// - shouldHideTranscript: The boolean value used to show or hide the transcription from the user. +/// - allowsEdittingTranscript: The boolean value used to present step that allows editting transcription. +/// - options: Options that affect the features of the predefined task. +/// +/// - Returns: An active speech recognition task that must be presented using an `ORKTaskViewController` object. +/// +/// - Important: This active task must be presented using an `ORKTaskViewController` object. Make sure to +/// set the `ORKTaskViewController`'s `outputDirectory` property to indicate the directory in which to +/// temporarily store the result files the task may generate. Otherwise, no data will be logged. + (ORKOrderedTask *)speechRecognitionTaskWithIdentifier:(NSString *)identifier intendedUseDescription:(nullable NSString *)intendedUseDescription speechRecognizerLocale:(ORKSpeechRecognizerLocale)speechRecognizerLocale @@ -454,56 +960,114 @@ NS_ASSUME_NONNULL_BEGIN allowsEdittingTranscript:(BOOL)allowsEdittingTranscript options:(ORKPredefinedTaskOption)options; -/** - Returns a predefined task that tests speech audiometry. - - In a speech in noise task, the participant is asked to listen to some sentences mixed with background noise at varying signal to noise ratio (SNR). - - You can use a speech in noise task to measure the speech reception threshold (SRT) of an individual. - - @param identifier The task identifier to use for this task, appropriate to the study. - @param intendedUseDescription A localized string describing the intended use of the data - collected. If the value of this parameter is `nil`, default - localized text is used. - @param options Options that affect the features of the predefined task. - - @return An active speech in noise task that can be presented with an `ORKTaskViewController` object. - */ +/// Returns a predefined Speech Recognition task that transcribes participant's speech. +/// +/// In a Speech Recognition task, the participant is shown a text or image or both. The participant has to read the text aloud, or describe the image. +/// +/// A Speech Recognition task finishes when the user presses the Stop Recording button. +/// +/// Data collected by the task is in the form of an `ORKSpeechRecognitionResult` object. +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, the default localized text is displayed. +/// - speechRecognizerLocale: An enum that represents the locale to be used by speech recognition API. +/// - speechRecognitionImage: The image shown to the participant. +/// - speechRecognitionText: The text shown to the participant. +/// - shouldHideTranscript: The boolean value used to show or hide the transcription from the user. +/// - allowsEdittingTranscript: The boolean value used to present step that allows editting transcription. +/// - options: Options that affect the features of the predefined task. +/// - outputDirectory: The url to the directory in which all output file data should be written. +/// +/// - Returns: An active speech recognition task that can be presented. +/// +/// - Important: If you choose to present this task using an `ORKTaskViewController` object, the value of the +/// controller's `outputDirectory` property - if it is set - will override the `outputDirectory` value passed +/// here. ++ (ORKOrderedTask *)speechRecognitionTaskWithIdentifier:(NSString *)identifier + intendedUseDescription:(nullable NSString *)intendedUseDescription + speechRecognizerLocale:(ORKSpeechRecognizerLocale)speechRecognizerLocale + speechRecognitionImage:(nullable UIImage *)speechRecognitionImage + speechRecognitionText:(nullable NSString *)speechRecognitionText + shouldHideTranscript:(BOOL)shouldHideTranscript + allowsEdittingTranscript:(BOOL)allowsEdittingTranscript + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL*)outputDirectory; + +/// Returns a predefined task that tests speech audiometry. +/// +/// In a speech in noise task, the participant is asked to listen to some sentences mixed with background noise at varying signal to noise ratio (SNR). +/// +/// You can use a speech in noise task to measure the speech reception threshold (SRT) of an individual. +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, default localized text is used. +/// - options: Options that affect the features of the predefined task. +/// +/// - Returns: An active speech in noise task that must be presented using an `ORKTaskViewController` object. +/// +/// - Important: This active task must be presented using an `ORKTaskViewController` object. Make sure to +/// set the `ORKTaskViewController`'s `outputDirectory` property to indicate the directory in which to +/// temporarily store the result files the task may generate. Otherwise, no data will be logged. + (ORKOrderedTask *)speechInNoiseTaskWithIdentifier:(NSString *)identifier intendedUseDescription:(nullable NSString *)intendedUseDescription options:(ORKPredefinedTaskOption)options; - - -/** - Returns a predefined task that tests tone audiometry. - - In a tone audiometry task, the participant is asked to listen to some tones with different audio - frequencies, playing on different channels (left and right), with the volume being progressively - increased until the participant taps a button. - - You can use a tone audiometry task to measure properties of the user's hearing, 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. - - @param identifier The task identifier to use for this task, appropriate to the study. - @param intendedUseDescription A localized string describing the intended use of the data - collected. If the value of this parameter is `nil`, default - localized text is used. - @param speechInstruction Instructional content describing what the user needs to do when - recording begins. If the value of this parameter is `nil`, - default localized text is used. - @param shortSpeechInstruction Instructional content shown during audio recording. If the value of - this parameter is `nil`, default localized text is used. - @param toneDuration The maximum length of the duration for each tone (each tone can be - interrupted sooner, after the participant presses the main - button). - @param options Options that affect the features of the predefined task. - - @return An active tone audiometry task that can be presented with an `ORKTaskViewController` object. - */ +/// Returns a predefined task that tests speech audiometry. +/// +/// In a speech in noise task, the participant is asked to listen to some sentences mixed with background noise at varying signal to noise ratio (SNR). +/// +/// You can use a speech in noise task to measure the speech reception threshold (SRT) of an individual. +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, default localized text is used. +/// - options: Options that affect the features of the predefined task. +/// - outputDirectory: The url to the directory in which all output file data should be written. +/// +/// - Returns: An active speech in noise task that can be presented. +/// +/// - Important: If you choose to present this task using an `ORKTaskViewController` object, the value of the +/// controller's `outputDirectory` property - if it is set - will override the `outputDirectory` value passed +/// here. ++ (ORKOrderedTask *)speechInNoiseTaskWithIdentifier:(NSString *)identifier + intendedUseDescription:(nullable NSString *)intendedUseDescription + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL*)outputDirectory; + +/// Returns a predefined task that tests tone audiometry. +/// +/// In a tone audiometry task, the participant is asked to listen to some tones with different audio +/// frequencies, playing on different channels (left and right), with the volume being progressively +/// increased until the participant taps a button. +/// +/// You can use a tone audiometry task to measure properties of the user's hearing, 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. +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, default localized text is used. +/// - speechInstruction: Instructional content describing what the user needs to do when recording +/// begins. If the value of this parameter is `nil`, default localized text +/// is used. +/// - shortSpeechInstruction: Instructional content shown during audio recording. If the value of this +/// parameter is `nil`, default localized text is used. +/// - toneDuration: The maximum length of the duration for each tone (each tone can be +/// interrupted sooner, after the participant presses the main button). +/// - options: Options that affect the features of the predefined task. +/// +/// - Returns: An active tone audiometry task that must be presented using an `ORKTaskViewController` object. +/// +/// - Important: This active task must be presented using an `ORKTaskViewController` object. Make sure to +/// set the `ORKTaskViewController`'s `outputDirectory` property to indicate the directory in which to +/// temporarily store the result files the task may generate. Otherwise, no data will be logged. + (ORKOrderedTask *)toneAudiometryTaskWithIdentifier:(NSString *)identifier intendedUseDescription:(nullable NSString *)intendedUseDescription speechInstruction:(nullable NSString *)speechInstruction @@ -511,67 +1075,132 @@ NS_ASSUME_NONNULL_BEGIN toneDuration:(NSTimeInterval)toneDuration options:(ORKPredefinedTaskOption)options; -/** - Returns a predefined task that tests dBHL tone audiometry. - - In the dBHL tone audiometry task, the participant is asked to listen to some tones with different audio - frequencies, playing on different channels (left and right), that vary in dB HL values depending on whether or not the user tapped the button. - - You can use a tone audiometry task to measure the hearing threshold of the user. - - @param identifier The task identifier to use for this task, appropriate to the study. - @param intendedUseDescription A localized string describing the intended use of the data - collected. If the value of this parameter is `nil`, default - localized text is used. - @param options Options that affect the features of the predefined task. - - @return An active dBHL tone audiometry task that can be presented with an `ORKTaskViewController` object. - */ +/// Returns a predefined task that tests tone audiometry. +/// +/// In a tone audiometry task, the participant is asked to listen to some tones with different audio +/// frequencies, playing on different channels (left and right), with the volume being progressively +/// increased until the participant taps a button. +/// +/// You can use a tone audiometry task to measure properties of the user's hearing, 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. +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, default localized text is used. +/// - speechInstruction: Instructional content describing what the user needs to do when recording +/// begins. If the value of this parameter is `nil`, default localized text +/// is used. +/// - shortSpeechInstruction: Instructional content shown during audio recording. If the value of this +/// parameter is `nil`, default localized text is used. +/// - toneDuration: The maximum length of the duration for each tone (each tone can be +/// interrupted sooner, after the participant presses the main button). +/// - options: Options that affect the features of the predefined task. +/// - outputDirectory: The url to the directory in which all output file data should be written. +/// +/// - Returns: An active tone audiometry task that can be presented. +/// +/// - Important: If you choose to present this task using an `ORKTaskViewController` object, the value of the +/// controller's `outputDirectory` property - if it is set - will override the `outputDirectory` value passed +/// here. ++ (ORKOrderedTask *)toneAudiometryTaskWithIdentifier:(NSString *)identifier + intendedUseDescription:(nullable NSString *)intendedUseDescription + speechInstruction:(nullable NSString *)speechInstruction + shortSpeechInstruction:(nullable NSString *)shortSpeechInstruction + toneDuration:(NSTimeInterval)toneDuration + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL*)outputDirectory; + +/// Returns a predefined task that tests dBHL tone audiometry. +/// +/// In the dBHL tone audiometry task, the participant is asked to listen to some tones with different audio +/// frequencies, playing on different channels (left and right), that vary in dB HL values depending on whether or not the user tapped the button. +/// +/// You can use a tone audiometry task to measure the hearing threshold of the user. +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, default localized text is used. +/// - options: Options that affect the features of the predefined task. +/// +/// - Returns: An active dBHL tone audiometry task that must be presented using an `ORKTaskViewController` object. +/// +/// - Important: This active task must be presented using an `ORKTaskViewController` object. Make sure to +/// set the `ORKTaskViewController`'s `outputDirectory` property to indicate the directory in which to +/// temporarily store the result files the task may generate. Otherwise, no data will be logged. + (ORKNavigableOrderedTask *)dBHLToneAudiometryTaskWithIdentifier:(NSString *)identifier intendedUseDescription:(nullable NSString *)intendedUseDescription options:(ORKPredefinedTaskOption)options; -/** - Returns a predefined task that tests the participant's reaction time. - - In a reaction time task, the participant is asked to move the device sharply in any - direction in response to a visual cue. You can use this task to accurately assess the participant's - simple reaction time. - - A reaction time task finishes when the participant has completed the required - number of attempts successfully. An attempt is successful when the participant exerts acceleration - greater than `thresholdAcceleration` to the device after the stimulus has been delivered and before - `timeout` has elapsed. An attempt is unsuccessful if acceleration greater than - `thresholdAcceleration` is applied to the device before the stimulus or if this does not occur - before `timeout` has elapsed. If unsuccessful, the result is not reported and the participant must - try again to proceed with the task. - - Data collected by the task is in the form of ORKReactionTimeResult objects. These - objects contain a timestamp representing the delivery of the stimulus and an ORKFileResult, which - references the motion data collected during an attempt. The researcher can use these to evaluate - the response to the stimulus and calculate the reaction time. - - @param identifier The task identifier to use for this task, appropriate to the - study. - @param intendedUseDescription A localized string describing the intended use of the data - collected. If the value of this parameter is `nil`, the - default localized text is displayed. - @param maximumStimulusInterval The maximum interval before the stimulus is delivered. - @param minimumStimulusInterval The minimum interval before the stimulus is delivered. - @param thresholdAcceleration The acceleration required to end a reaction time test. - @param numberOfAttempts The number of successful attempts required before the task is - complete. The active step result will contain this many - child results if the task is completed. - @param timeout The interval permitted after the stimulus until the test fails, - if the threshold is not reached. - @param successSoundID The sound to play after a successful attempt. - @param timeoutSoundID The sound to play after an attempt that times out. - @param failureSoundID The sound to play after an unsuccessful attempt. - @param options Options that affect the features of the predefined task. - - @return An active device motion reaction time task that can be presented with an `ORKTaskViewController` object. - */ - +/// Returns a predefined task that tests dBHL tone audiometry. +/// +/// In the dBHL tone audiometry task, the participant is asked to listen to some tones with different audio +/// frequencies, playing on different channels (left and right), that vary in dB HL values depending on whether or not the user tapped the button. +/// +/// You can use a tone audiometry task to measure the hearing threshold of the user. +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, default localized text is used. +/// - options: Options that affect the features of the predefined task. +/// - outputDirectory: The url to the directory in which all output file data should be written. +/// +/// - Returns: An active dBHL tone audiometry task that can be presented. +/// +/// - Important: If you choose to present this task using an `ORKTaskViewController` object, the value of the +/// controller's `outputDirectory` property - if it is set - will override the `outputDirectory` value passed +/// here. ++ (ORKNavigableOrderedTask *)dBHLToneAudiometryTaskWithIdentifier:(NSString *)identifier + intendedUseDescription:(nullable NSString *)intendedUseDescription + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL*)outputDirectory; + +/// Returns a predefined task that tests the participant's reaction time. +/// +/// In a reaction time task, the participant is asked to move the device sharply in any +/// direction in response to a visual cue. You can use this task to accurately assess the participant's +/// simple reaction time. +/// +/// A reaction time task finishes when the participant has completed the required +/// number of attempts successfully. An attempt is successful when the participant exerts acceleration +/// greater than `thresholdAcceleration` to the device after the stimulus has been delivered and before +/// `timeout` has elapsed. An attempt is unsuccessful if acceleration greater than +/// `thresholdAcceleration` is applied to the device before the stimulus or if this does not occur +/// before `timeout` has elapsed. If unsuccessful, the result is not reported and the participant must +/// try again to proceed with the task. +/// +/// Data collected by the task is in the form of ORKReactionTimeResult objects. These +/// objects contain a timestamp representing the delivery of the stimulus and an ORKFileResult, which +/// references the motion data collected during an attempt. The researcher can use these to evaluate +/// the response to the stimulus and calculate the reaction time. +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, the default localized text +/// is displayed. +/// - maximumStimulusInterval: The maximum interval before the stimulus is delivered. +/// - minimumStimulusInterval: The minimum interval before the stimulus is delivered. +/// - thresholdAcceleration : The acceleration required to end a reaction time test. +/// - numberOfAttempts: The number of successful attempts required before the task is complete. +/// The active step result will contain this many child results if the +/// task is completed. +/// - timeout: The interval permitted after the stimulus until the test fails, if the +/// threshold is not reached. +/// - successSoundID: The sound to play after a successful attempt. +/// - timeoutSoundID: The sound to play after an attempt that times out. +/// - failureSoundID: The sound to play after an unsuccessful attempt. +/// - options: Options that affect the features of the predefined task. +/// +/// - Returns: An active device motion reaction time task that must be presented using an `ORKTaskViewController` object. +/// +/// - Important: This active task must be presented using an `ORKTaskViewController` object. Make sure to +/// set the `ORKTaskViewController`'s `outputDirectory` property to indicate the directory in which to +/// temporarily store the result files the task may generate. Otherwise, no data will be logged. + (ORKOrderedTask *)reactionTimeTaskWithIdentifier:(NSString *)identifier intendedUseDescription:(nullable NSString *)intendedUseDescription maximumStimulusInterval:(NSTimeInterval)maximumStimulusInterval @@ -584,45 +1213,103 @@ NS_ASSUME_NONNULL_BEGIN failureSound:(UInt32)failureSoundID options:(ORKPredefinedTaskOption)options; -/** - Returns a predefined task that tests the participant's normalized reaction time. - - In a reaction time task, the participant is asked to tap the "hold" button and not release the tap until seeing the visual cue, - in which they should they should tap the visual cue. You can use this task to accurately assess the participant's simple reaction time. - - A reaction time task finishes when the participant has completed the required - number of attempts successfully. An attempt is successful when the participant exerts acceleration - greater than `thresholdAcceleration` to the device after the stimulus has been delivered and before - `timeout` has elapsed. An attempt is unsuccessful if acceleration greater than - `thresholdAcceleration` is applied to the device before the stimulus or if this does not occur - before `timeout` has elapsed. If unsuccessful, the result is not reported and the participant must - try again to proceed with the task. - - Data collected by the task is in the form of ORKNormalizedReactionTimeResult objects. These - objects contain a timestamp representing the delivery of the stimulus and an ORKFileResult, which - references the motion data collected during an attempt. The researcher can use these to evaluate - the response to the stimulus and calculate the reaction time. - - @param identifier The task identifier to use for this task, appropriate to the - study. - @param intendedUseDescription A localized string describing the intended use of the data - collected. If the value of this parameter is `nil`, the - default localized text is displayed. - @param maximumStimulusInterval The maximum interval before the stimulus is delivered. - @param minimumStimulusInterval The minimum interval before the stimulus is delivered. - @param thresholdAcceleration The acceleration required to end a reaction time test. - @param numberOfAttempts The number of successful attempts required before the task is - complete. The active step result will contain this many - child results if the task is completed. - @param timeout The interval permitted after the stimulus until the test fails, - if the threshold is not reached. - @param successSoundID The sound to play after a successful attempt. - @param timeoutSoundID The sound to play after an attempt that times out. - @param failureSoundID The sound to play after an unsuccessful attempt. - @param options Options that affect the features of the predefined task. - - @return An active device motion reaction time task that can be presented with an `ORKTaskViewController` object. - */ +/// Returns a predefined task that tests the participant's reaction time. +/// +/// In a reaction time task, the participant is asked to move the device sharply in any +/// direction in response to a visual cue. You can use this task to accurately assess the participant's +/// simple reaction time. +/// +/// A reaction time task finishes when the participant has completed the required +/// number of attempts successfully. An attempt is successful when the participant exerts acceleration +/// greater than `thresholdAcceleration` to the device after the stimulus has been delivered and before +/// `timeout` has elapsed. An attempt is unsuccessful if acceleration greater than +/// `thresholdAcceleration` is applied to the device before the stimulus or if this does not occur +/// before `timeout` has elapsed. If unsuccessful, the result is not reported and the participant must +/// try again to proceed with the task. +/// +/// Data collected by the task is in the form of ORKReactionTimeResult objects. These +/// objects contain a timestamp representing the delivery of the stimulus and an ORKFileResult, which +/// references the motion data collected during an attempt. The researcher can use these to evaluate +/// the response to the stimulus and calculate the reaction time. +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, the default localized text +/// is displayed. +/// - maximumStimulusInterval: The maximum interval before the stimulus is delivered. +/// - minimumStimulusInterval: The minimum interval before the stimulus is delivered. +/// - thresholdAcceleration : The acceleration required to end a reaction time test. +/// - numberOfAttempts: The number of successful attempts required before the task is complete. +/// The active step result will contain this many child results if the +/// task is completed. +/// - timeout: The interval permitted after the stimulus until the test fails, if the +/// threshold is not reached. +/// - successSoundID: The sound to play after a successful attempt. +/// - timeoutSoundID: The sound to play after an attempt that times out. +/// - failureSoundID: The sound to play after an unsuccessful attempt. +/// - options: Options that affect the features of the predefined task. +/// - outputDirectory: The url to the directory in which all output file data should be written. +/// +/// - Returns: An active device motion reaction time task that can be presented. +/// +/// - Important: If you choose to present this task using an `ORKTaskViewController` object, the value of the +/// controller's `outputDirectory` property - if it is set - will override the `outputDirectory` value passed +/// here. ++ (ORKOrderedTask *)reactionTimeTaskWithIdentifier:(NSString *)identifier + intendedUseDescription:(nullable NSString *)intendedUseDescription + maximumStimulusInterval:(NSTimeInterval)maximumStimulusInterval + minimumStimulusInterval:(NSTimeInterval)minimumStimulusInterval + thresholdAcceleration:(double)thresholdAcceleration + numberOfAttempts:(int)numberOfAttempts + timeout:(NSTimeInterval)timeout + successSound:(UInt32)successSoundID + timeoutSound:(UInt32)timeoutSoundID + failureSound:(UInt32)failureSoundID + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL*)outputDirectory; + +/// Returns a predefined task that tests the participant's normalized reaction time. +/// +/// In a reaction time task, the participant is asked to tap the "hold" button and not release the tap until seeing the visual cue, +/// in which they should they should tap the visual cue. You can use this task to accurately assess the participant's simple reaction time. +/// +/// A reaction time task finishes when the participant has completed the required +/// number of attempts successfully. An attempt is successful when the participant exerts acceleration +/// greater than `thresholdAcceleration` to the device after the stimulus has been delivered and before +/// `timeout` has elapsed. An attempt is unsuccessful if acceleration greater than +/// `thresholdAcceleration` is applied to the device before the stimulus or if this does not occur +/// before `timeout` has elapsed. If unsuccessful, the result is not reported and the participant must +/// try again to proceed with the task. +/// +/// Data collected by the task is in the form of ORKNormalizedReactionTimeResult objects. These +/// objects contain a timestamp representing the delivery of the stimulus and an ORKFileResult, which +/// references the motion data collected during an attempt. The researcher can use these to evaluate +/// the response to the stimulus and calculate the reaction time. +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, the default localized text +/// is displayed. +/// - maximumStimulusInterval: The maximum interval before the stimulus is delivered. +/// - minimumStimulusInterval: The minimum interval before the stimulus is delivered. +/// - thresholdAcceleration: The acceleration required to end a reaction time test. +/// - numberOfAttempts: The number of successful attempts required before the task is complete. +/// The active step result will contain this many child results if the +/// task is completed. +/// - timeout The interval permitted after the stimulus until the test fails, if the +/// threshold is not reached. +/// - successSoundID The sound to play after a successful attempt. +/// - timeoutSoundID The sound to play after an attempt that times out. +/// - failureSoundID The sound to play after an unsuccessful attempt. +/// - options Options that affect the features of the predefined task. +/// +/// - Returns: An active device motion normalized reaction time task that must be presented using an `ORKTaskViewController` object. +/// +/// - Important: This active task must be presented using an `ORKTaskViewController` object. Make sure to +/// set the `ORKTaskViewController`'s `outputDirectory` property to indicate the directory in which to +/// temporarily store the result files the task may generate. Otherwise, no data will be logged. + (ORKOrderedTask *)normalizedReactionTimeTaskWithIdentifier:(NSString *)identifier intendedUseDescription:(nullable NSString *)intendedUseDescription maximumStimulusInterval:(NSTimeInterval)maximumStimulusInterval @@ -634,61 +1321,154 @@ NS_ASSUME_NONNULL_BEGIN timeoutSound:(UInt32)timeoutSoundID failureSound:(UInt32)failureSoundID options:(ORKPredefinedTaskOption)options; - -/** - Returns a predefined task that consists of a Tower of Hanoi puzzle. - - In a Tower of Hanoi task, the participant is asked to solve the classic puzzle in as few moves as possible. - You can use this task to assess the participant's problem-solving skills. - - A Tower of Hanoi task finishes when the participant has completed the puzzle correctly or concedes that he or she cannot solve it. - - Data collected by the task is in the form of an `ORKTowerOfHanoiResult` object. Data collected in this task consists of how many moves were taken and whether the puzzle was successfully completed or not. - - @param identifier The task identifier to use for this task, appropriate to the study. - @param intendedUseDescription A localized string describing the intended use of the data - collected. If the value of this parameter is `nil`, the - default localized text is displayed. - @param numberOfDisks The number of disks in the puzzle; the default value for this property is 3. - @param options Options that affect the features of the predefined task. - - @return An active device motion reaction time task that can be presented with an `ORKTaskViewController` object. - */ + +/// Returns a predefined task that tests the participant's normalized reaction time. +/// +/// In a reaction time task, the participant is asked to tap the "hold" button and not release the tap until seeing the visual cue, +/// in which they should they should tap the visual cue. You can use this task to accurately assess the participant's simple reaction time. +/// +/// A reaction time task finishes when the participant has completed the required +/// number of attempts successfully. An attempt is successful when the participant exerts acceleration +/// greater than `thresholdAcceleration` to the device after the stimulus has been delivered and before +/// `timeout` has elapsed. An attempt is unsuccessful if acceleration greater than +/// `thresholdAcceleration` is applied to the device before the stimulus or if this does not occur +/// before `timeout` has elapsed. If unsuccessful, the result is not reported and the participant must +/// try again to proceed with the task. +/// +/// Data collected by the task is in the form of ORKNormalizedReactionTimeResult objects. These +/// objects contain a timestamp representing the delivery of the stimulus and an ORKFileResult, which +/// references the motion data collected during an attempt. The researcher can use these to evaluate +/// the response to the stimulus and calculate the reaction time. +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, the default localized text +/// is displayed. +/// - maximumStimulusInterval: The maximum interval before the stimulus is delivered. +/// - minimumStimulusInterval: The minimum interval before the stimulus is delivered. +/// - thresholdAcceleration: The acceleration required to end a reaction time test. +/// - numberOfAttempts: The number of successful attempts required before the task is complete. +/// The active step result will contain this many child results if the +/// task is completed. +/// - timeout The interval permitted after the stimulus until the test fails, if the +/// threshold is not reached. +/// - successSoundID The sound to play after a successful attempt. +/// - timeoutSoundID The sound to play after an attempt that times out. +/// - failureSoundID The sound to play after an unsuccessful attempt. +/// - options Options that affect the features of the predefined task. +/// - outputDirectory: The url to the directory in which all output file data should be written. +/// +/// - Returns: An active device motion normalized reaction time task that can be presented. +/// +/// - Important: If you choose to present this task using an `ORKTaskViewController` object, the value of the +/// controller's `outputDirectory` property - if it is set - will override the `outputDirectory` value passed +/// here. ++ (ORKOrderedTask *)normalizedReactionTimeTaskWithIdentifier:(NSString *)identifier + intendedUseDescription:(nullable NSString *)intendedUseDescription + maximumStimulusInterval:(NSTimeInterval)maximumStimulusInterval + minimumStimulusInterval:(NSTimeInterval)minimumStimulusInterval + thresholdAcceleration:(double)thresholdAcceleration + numberOfAttempts:(int)numberOfAttempts + timeout:(NSTimeInterval)timeout + successSound:(UInt32)successSoundID + timeoutSound:(UInt32)timeoutSoundID + failureSound:(UInt32)failureSoundID + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL*)outputDirectory; + +/// Returns a predefined task that consists of a Tower of Hanoi puzzle. +/// +/// In a Tower of Hanoi task, the participant is asked to solve the classic puzzle in as few moves as possible. +/// You can use this task to assess the participant's problem-solving skills. +/// +/// A Tower of Hanoi task finishes when the participant has completed the puzzle correctly or concedes that he or she cannot solve it. +/// +/// Data collected by the task is in the form of an `ORKTowerOfHanoiResult` object. Data collected in this task consists of how many moves were taken and whether the puzzle was successfully completed or not. +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, the default localized text +/// is displayed. +/// - numberOfDisks: The number of disks in the puzzle; the default value for this property +/// is 3. +/// - options: Options that affect the features of the predefined task. +/// +/// - Returns: An active device motion reaction time task that must be presented using an `ORKTaskViewController` object. +/// +/// - Important: This active task must be presented using an `ORKTaskViewController` object. Make sure to +/// set the `ORKTaskViewController`'s `outputDirectory` property to indicate the directory in which to +/// temporarily store the result files the task may generate. Otherwise, no data will be logged. + (ORKOrderedTask *)towerOfHanoiTaskWithIdentifier:(NSString *)identifier intendedUseDescription:(nullable NSString *)intendedUseDescription numberOfDisks:(NSUInteger)numberOfDisks options:(ORKPredefinedTaskOption)options; -/** - Returns a predefined task that consists of a timed walk, with a distinct turn around step. - - In a timed walk task, the participant is asked to walk for a specific distance as quickly as - possible, but safely. Then the participant is asked to turn around. The task is immediately - administered again by having the patient walk back the same distance. - A timed walk task can be used to measure lower extremity function. - - The presentation of the timed walk task differs from both the fitness check task and the short - walk task in that the distance is fixed. After a first walk, the user is asked to turn, then reverse - direction. - - The data collected by this task can include accelerometer, device motion, pedometer data, - and location where available. - - Data collected by the task is in the form of an `ORKTimedWalkResult` object. - - @param identifier The task identifier to use for this task, appropriate to the study. - @param intendedUseDescription A localized string describing the intended use of the data - collected. If the value of this parameter is `nil`, the default - localized text is displayed. - @param distanceInMeters The timed walk distance in meters. - @param timeLimit The time limit to complete the trials. - @param turnAroundTimeLimit The time limit to complete the turn around step, passing zero or negative value to this parameter will bypass the turnAroundTime step. - @param includeAssistiveDeviceForm A Boolean value that indicates whether to inlude the form step - about the usage of an assistive device. - @param options Options that affect the features of the predefined task. - - @return An active timed walk task that can be presented with an `ORKTaskViewController` object. - */ +/// Returns a predefined task that consists of a Tower of Hanoi puzzle. +/// +/// In a Tower of Hanoi task, the participant is asked to solve the classic puzzle in as few moves as possible. +/// You can use this task to assess the participant's problem-solving skills. +/// +/// A Tower of Hanoi task finishes when the participant has completed the puzzle correctly or concedes that he or she cannot solve it. +/// +/// Data collected by the task is in the form of an `ORKTowerOfHanoiResult` object. Data collected in this task consists of how many moves were taken and whether the puzzle was successfully completed or not. +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, the default localized text +/// is displayed. +/// - numberOfDisks: The number of disks in the puzzle; the default value for this property +/// is 3. +/// - options: Options that affect the features of the predefined task. +/// - outputDirectory: The url to the directory in which all output file data should be written. +/// +/// - Returns: An active device motion reaction time task that can be presented. +/// +/// - Important: If you choose to present this task using an `ORKTaskViewController` object, the value of the +/// controller's `outputDirectory` property - if it is set - will override the `outputDirectory` value passed +/// here. ++ (ORKOrderedTask *)towerOfHanoiTaskWithIdentifier:(NSString *)identifier + intendedUseDescription:(nullable NSString *)intendedUseDescription + numberOfDisks:(NSUInteger)numberOfDisks + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL*)outputDirectory; + +/// Returns a predefined task that consists of a timed walk, with a distinct turn around step. +/// +/// In a timed walk task, the participant is asked to walk for a specific distance as quickly as +/// possible, but safely. Then the participant is asked to turn around. The task is immediately +/// administered again by having the patient walk back the same distance. +/// A timed walk task can be used to measure lower extremity function. +/// +/// The presentation of the timed walk task differs from both the fitness check task and the short +/// walk task in that the distance is fixed. After a first walk, the user is asked to turn, then reverse +/// direction. +/// +/// The data collected by this task can include accelerometer, device motion, pedometer data, +/// and location where available. +/// +/// Data collected by the task is in the form of an `ORKTimedWalkResult` object. +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, the default localized text +/// is displayed. +/// - distanceInMeters: The timed walk distance in meters. +/// - timeLimit: The time limit to complete the trials. +/// - turnAroundTimeLimit: The time limit to complete the turn around step, passing zero or negative +/// value to this parameter will bypass the turnAroundTime step. +/// - includeAssistiveDeviceForm: A Boolean value that indicates whether to inlude the form step about the +/// usage of an assistive device. +/// - options: Options that affect the features of the predefined task. +/// +/// - Returns: An active timed walk task that must be presented using an `ORKTaskViewController` object. +/// +/// - Important: This active task must be presented using an `ORKTaskViewController` object. Make sure to +/// set the `ORKTaskViewController`'s `outputDirectory` property to indicate the directory in which to +/// temporarily store the result files the task may generate. Otherwise, no data will be logged. + (ORKOrderedTask *)timedWalkTaskWithIdentifier:(NSString *)identifier intendedUseDescription:(nullable NSString *)intendedUseDescription distanceInMeters:(double)distanceInMeters @@ -697,32 +1477,77 @@ NS_ASSUME_NONNULL_BEGIN includeAssistiveDeviceForm:(BOOL)includeAssistiveDeviceForm options:(ORKPredefinedTaskOption)options; - -/** - Returns a predefined task that consists of the paced serial addition test (PSAT). - - In a PSAT task, the participant is asked to add a new digit to the one immediately before it - every 2 or 3 seconds. - - A PSAT task can be used to measure the cognitive function that assesses auditory and/or - visual information processing speed and flexibility, as well as calculation ability. - - Data collected by the task is in the form of an `ORKPSATResult` object. - - @param identifier The task identifier to use for this task, appropriate to the study. - @param intendedUseDescription A localized string describing the intended use of the data - collected. If the value of this parameter is `nil`, the default - localized text is displayed. - @param presentationMode The presentation mode of the PSAT test (auditory or visual or both). - @param interStimulusInterval The time interval between two digits presented. - @param stimulusDuration The time duration the digit is shown on screen (only for - visual PSAT, that is PVSAT and PAVSAT). - @param seriesLength The number of digits that will be presented during the task. - @param options Options that affect the features of the predefined task. - - @return An active PSAT task that can be presented with an `ORKTaskViewController` object. - - */ +/// Returns a predefined task that consists of a timed walk, with a distinct turn around step. +/// +/// In a timed walk task, the participant is asked to walk for a specific distance as quickly as +/// possible, but safely. Then the participant is asked to turn around. The task is immediately +/// administered again by having the patient walk back the same distance. +/// A timed walk task can be used to measure lower extremity function. +/// +/// The presentation of the timed walk task differs from both the fitness check task and the short +/// walk task in that the distance is fixed. After a first walk, the user is asked to turn, then reverse +/// direction. +/// +/// The data collected by this task can include accelerometer, device motion, pedometer data, +/// and location where available. +/// +/// Data collected by the task is in the form of an `ORKTimedWalkResult` object. +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, the default localized text +/// is displayed. +/// - distanceInMeters: The timed walk distance in meters. +/// - timeLimit: The time limit to complete the trials. +/// - turnAroundTimeLimit: The time limit to complete the turn around step, passing zero or negative +/// value to this parameter will bypass the turnAroundTime step. +/// - includeAssistiveDeviceForm: A Boolean value that indicates whether to inlude the form step about the +/// usage of an assistive device. +/// - options: Options that affect the features of the predefined task. +/// - outputDirectory: The url to the directory in which all output file data should be written. +/// +/// - Returns: An active timed walk task that can be presented. +/// +/// - Important: If you choose to present this task using an `ORKTaskViewController` object, the value of the +/// controller's `outputDirectory` property - if it is set - will override the `outputDirectory` value passed +/// here. ++ (ORKOrderedTask *)timedWalkTaskWithIdentifier:(NSString *)identifier + intendedUseDescription:(nullable NSString *)intendedUseDescription + distanceInMeters:(double)distanceInMeters + timeLimit:(NSTimeInterval)timeLimit + turnAroundTimeLimit:(NSTimeInterval)turnAroundTimeLimit + includeAssistiveDeviceForm:(BOOL)includeAssistiveDeviceForm + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL*)outputDirectory; + +/// Returns a predefined task that consists of the paced serial addition test (PSAT). +/// +/// In a PSAT task, the participant is asked to add a new digit to the one immediately before it +/// every 2 or 3 seconds. +/// +/// A PSAT task can be used to measure the cognitive function that assesses auditory and/or +/// visual information processing speed and flexibility, as well as calculation ability. +/// +/// Data collected by the task is in the form of an `ORKPSATResult` object. +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, the default localized text +/// is displayed. +/// - presentationMode: The presentation mode of the PSAT test (auditory or visual or both). +/// - interStimulusInterval: The time interval between two digits presented. +/// - stimulusDuration: The time duration the digit is shown on screen (only for visual PSAT, +/// that is PVSAT and PAVSAT). +/// - seriesLength: The number of digits that will be presented during the task. +/// - options: Options that affect the features of the predefined task. +/// +/// - Returns: An active PSAT task that must be presented using an `ORKTaskViewController` object. +/// +/// - Important: This active task must be presented using an `ORKTaskViewController` object. Make sure to +/// set the `ORKTaskViewController`'s `outputDirectory` property to indicate the directory in which to +/// temporarily store the result files the task may generate. Otherwise, no data will be logged. + (ORKOrderedTask *)PSATTaskWithIdentifier:(NSString *)identifier intendedUseDescription:(nullable NSString *)intendedUseDescription presentationMode:(ORKPSATPresentationMode)presentationMode @@ -731,24 +1556,63 @@ NS_ASSUME_NONNULL_BEGIN seriesLength:(NSInteger)seriesLength options:(ORKPredefinedTaskOption)options; - -/** - Returns a predefined task that measures hand tremor. - - In a tremor assessment task, the participant is asked to hold the device with their most affected - hand in various positions while accelerometer and motion data are captured. - - @param identifier The task identifier to use for this task, appropriate to the study. - @param intendedUseDescription A localized string describing the intended use of the data - collected. If the value of this parameter is `nil`, the default - localized text is displayed. - @param activeStepDuration The duration for each active step in the task. - @param activeTaskOptions Options that affect which active steps are presented for this task. - @param handOptions Options for determining which hand(s) to test. - @param options Options that affect the features of the predefined task. - - @return An active tremor test task that can be presented with an `ORKTaskViewController` object. - */ +/// Returns a predefined task that consists of the paced serial addition test (PSAT). +/// +/// In a PSAT task, the participant is asked to add a new digit to the one immediately before it +/// every 2 or 3 seconds. +/// +/// A PSAT task can be used to measure the cognitive function that assesses auditory and/or +/// visual information processing speed and flexibility, as well as calculation ability. +/// +/// Data collected by the task is in the form of an `ORKPSATResult` object. +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, the default localized text +/// is displayed. +/// - presentationMode: The presentation mode of the PSAT test (auditory or visual or both). +/// - interStimulusInterval: The time interval between two digits presented. +/// - stimulusDuration: The time duration the digit is shown on screen (only for visual PSAT, +/// that is PVSAT and PAVSAT). +/// - seriesLength: The number of digits that will be presented during the task. +/// - options: Options that affect the features of the predefined task. +/// - outputDirectory: The url to the directory in which all output file data should be written. +/// +/// - Returns: An active PSAT task that can be presented. +/// +/// - Important: If you choose to present this task using an `ORKTaskViewController` object, the value of the +/// controller's `outputDirectory` property - if it is set - will override the `outputDirectory` value passed +/// here. ++ (ORKOrderedTask *)PSATTaskWithIdentifier:(NSString *)identifier + intendedUseDescription:(nullable NSString *)intendedUseDescription + presentationMode:(ORKPSATPresentationMode)presentationMode + interStimulusInterval:(NSTimeInterval)interStimulusInterval + stimulusDuration:(NSTimeInterval)stimulusDuration + seriesLength:(NSInteger)seriesLength + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL*)outputDirectory; + +/// Returns a predefined task that measures hand tremor. +/// +/// In a tremor assessment task, the participant is asked to hold the device with their most affected +/// hand in various positions while accelerometer and motion data are captured. +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, the default localized text is +/// displayed. +/// - activeStepDuration: The duration for each active step in the task. +/// - activeTaskOptions: Options that affect which active steps are presented for this task. +/// - handOptions: Options for determining which hand(s) to test. +/// - options: Options that affect the features of the predefined task. +/// +/// - Returns: An active tremor test task that must be presented using an `ORKTaskViewController` object. +/// +/// - Important: This active task must be presented using an `ORKTaskViewController` object. Make sure to +/// set the `ORKTaskViewController`'s `outputDirectory` property to indicate the directory in which to +/// temporarily store the result files the task may generate. Otherwise, no data will be logged. + (ORKNavigableOrderedTask *)tremorTestTaskWithIdentifier:(NSString *)identifier intendedUseDescription:(nullable NSString *)intendedUseDescription activeStepDuration:(NSTimeInterval)activeStepDuration @@ -756,33 +1620,97 @@ NS_ASSUME_NONNULL_BEGIN handOptions:(ORKPredefinedTaskHandOption)handOptions options:(ORKPredefinedTaskOption)options; - -/** - Returns a predefined task that measures visual attention and task switching. - - In a trail making test, the participant is asked to connect a series of cicles labeled 1,2,3... or - 1,A,2,B,3,C... and time to complete the test is recorded. - - `ORKTrailMakingTypeIdentifierA` uses the pattern: 1-2-3-4-5-6-7. - `ORKTrailMakingTypeIdentifierB` uses the pattern: 1-A-2-B-3-C-4-D-5-E-6-F-7 - - @param identifier The task identifier to use for this task, appropriate to the study. - @param intendedUseDescription A localized string describing the intended use of the data - collected. If the value of this parameter is `nil`, the default - localized text is displayed. - @param trailmakingInstruction Instructional content describing what the user needs to do when - the task begins. If the value of this parameter is `nil`, - @param trailType Type of trail to display. Either `ORKTrailMakingTypeIdentifierA` or `ORKTrailMakingTypeIdentifierB` - @param options Options that affect the features of the predefined task. - - @return An active trail making test task that can be presented with an `ORKTaskViewController` object. - */ +/// Returns a predefined task that measures hand tremor. +/// +/// In a tremor assessment task, the participant is asked to hold the device with their most affected +/// hand in various positions while accelerometer and motion data are captured. +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, the default localized text is +/// displayed. +/// - activeStepDuration: The duration for each active step in the task. +/// - activeTaskOptions: Options that affect which active steps are presented for this task. +/// - handOptions: Options for determining which hand(s) to test. +/// - options: Options that affect the features of the predefined task. +/// - outputDirectory: The url to the directory in which all output file data should be written. +/// +/// - Returns: An active tremor test task that can be presented. +/// +/// - Important: If you choose to present this task using an `ORKTaskViewController` object, the value of the +/// controller's `outputDirectory` property - if it is set - will override the `outputDirectory` value passed +/// here. ++ (ORKNavigableOrderedTask *)tremorTestTaskWithIdentifier:(NSString *)identifier + intendedUseDescription:(nullable NSString *)intendedUseDescription + activeStepDuration:(NSTimeInterval)activeStepDuration + activeTaskOptions:(ORKTremorActiveTaskOption)activeTaskOptions + handOptions:(ORKPredefinedTaskHandOption)handOptions + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL*)outputDirectory; + +/// Returns a predefined task that measures visual attention and task switching. +/// +/// In a trail making test, the participant is asked to connect a series of cicles labeled 1,2,3... or +/// 1,A,2,B,3,C... and time to complete the test is recorded. +/// +/// `ORKTrailMakingTypeIdentifierA` uses the pattern: 1-2-3-4-5-6-7. +/// `ORKTrailMakingTypeIdentifierB` uses the pattern: 1-A-2-B-3-C-4-D-5-E-6-F-7 +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, the default localized text is +/// displayed. +/// - trailmakingInstruction: Instructional content describing what the user needs to do when the task +/// begins. If the value of this parameter is `nil`, +/// - trailType: Type of trail to display. +/// Either `ORKTrailMakingTypeIdentifierA` or `ORKTrailMakingTypeIdentifierB`. +/// - options: Options that affect the features of the predefined task. +/// +/// - Returns: An active trail making test task that must be presented using an `ORKTaskViewController` object. +/// +/// - Important: This active task must be presented using an `ORKTaskViewController` object. Make sure to +/// set the `ORKTaskViewController`'s `outputDirectory` property to indicate the directory in which to +/// temporarily store the result files the task may generate. Otherwise, no data will be logged. + (ORKOrderedTask *)trailmakingTaskWithIdentifier:(NSString *)identifier intendedUseDescription:(nullable NSString *)intendedUseDescription trailmakingInstruction:(nullable NSString *)trailmakingInstruction trailType:(ORKTrailMakingTypeIdentifier)trailType options:(ORKPredefinedTaskOption)options; +/// Returns a predefined task that measures visual attention and task switching. +/// +/// In a trail making test, the participant is asked to connect a series of cicles labeled 1,2,3... or +/// 1,A,2,B,3,C... and time to complete the test is recorded. +/// +/// `ORKTrailMakingTypeIdentifierA` uses the pattern: 1-2-3-4-5-6-7. +/// `ORKTrailMakingTypeIdentifierB` uses the pattern: 1-A-2-B-3-C-4-D-5-E-6-F-7 +/// +/// - Parameters: +/// - identifier: The task identifier to use for this task, appropriate to the study. +/// - intendedUseDescription: A localized string describing the intended use of the data collected. +/// If the value of this parameter is `nil`, the default localized text is +/// displayed. +/// - trailmakingInstruction: Instructional content describing what the user needs to do when the task +/// begins. If the value of this parameter is `nil`, +/// - trailType: Type of trail to display. +/// Either `ORKTrailMakingTypeIdentifierA` or `ORKTrailMakingTypeIdentifierB`. +/// - options: Options that affect the features of the predefined task. +/// - outputDirectory: The url to the directory in which all output file data should be written. +/// +/// - Returns: An active trail making test task that can be presented. +/// +/// - Important: If you choose to present this task using an `ORKTaskViewController` object, the value of the +/// controller's `outputDirectory` property - if it is set - will override the `outputDirectory` value passed +/// here. ++ (ORKOrderedTask *)trailmakingTaskWithIdentifier:(NSString *)identifier + intendedUseDescription:(nullable NSString *)intendedUseDescription + trailmakingInstruction:(nullable NSString *)trailmakingInstruction + trailType:(ORKTrailMakingTypeIdentifier)trailType + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL*)outputDirectory; + @end NS_ASSUME_NONNULL_END diff --git a/ResearchKitActiveTask/Common/ORKOrderedTask+ORKPredefinedActiveTask.m b/ResearchKitActiveTask/Common/ORKOrderedTask+ORKPredefinedActiveTask.m index 42df4d34cf..d8da20582b 100644 --- a/ResearchKitActiveTask/Common/ORKOrderedTask+ORKPredefinedActiveTask.m +++ b/ResearchKitActiveTask/Common/ORKOrderedTask+ORKPredefinedActiveTask.m @@ -125,7 +125,8 @@ void ORKStepArrayAddStep(NSMutableArray *array, ORKStep *step) { @implementation ORKOrderedTask (ORKMakeTaskUtilities) -+ (NSArray*)makeRecorderConfigurationsWithOptions:(ORKPredefinedTaskOption)options { ++ (NSArray*)makeRecorderConfigurationsWithOptions:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL *)outputDirectory { #if ORK_FEATURE_HEALTHKIT_AUTHORIZATION HKUnit *bpmUnit = [[HKUnit countUnit] unitDividedByUnit:[HKUnit minuteUnit]]; @@ -135,25 +136,31 @@ @implementation ORKOrderedTask (ORKMakeTaskUtilities) NSMutableArray *recorderConfigurations = [NSMutableArray arrayWithCapacity:5]; if (!(ORKPredefinedTaskOptionExcludePedometer & options)) { - [recorderConfigurations addObject:[[ORKPedometerRecorderConfiguration alloc] initWithIdentifier:ORKPedometerRecorderIdentifier]]; + [recorderConfigurations addObject:[[ORKPedometerRecorderConfiguration alloc] initWithIdentifier:ORKPedometerRecorderIdentifier + outputDirectory:outputDirectory]]; } if (!(ORKPredefinedTaskOptionExcludeAccelerometer & options)) { [recorderConfigurations addObject:[[ORKAccelerometerRecorderConfiguration alloc] initWithIdentifier:ORKAccelerometerRecorderIdentifier - frequency:100]]; + frequency:100 + outputDirectory:outputDirectory]]; } if (!(ORKPredefinedTaskOptionExcludeDeviceMotion & options)) { [recorderConfigurations addObject:[[ORKDeviceMotionRecorderConfiguration alloc] initWithIdentifier:ORKDeviceMotionRecorderIdentifier - frequency:100]]; + frequency:100 + outputDirectory:outputDirectory]]; } #if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION if (!(ORKPredefinedTaskOptionExcludeLocation & options)) { - [recorderConfigurations addObject:[[ORKLocationRecorderConfiguration alloc] initWithIdentifier:ORKLocationRecorderIdentifier]]; + [recorderConfigurations addObject:[[ORKLocationRecorderConfiguration alloc] initWithIdentifier:ORKLocationRecorderIdentifier + outputDirectory:outputDirectory]]; } #endif #if ORK_FEATURE_HEALTHKIT_AUTHORIZATION if (!(ORKPredefinedTaskOptionExcludeHeartRate & options)) { [recorderConfigurations addObject:[[ORKHealthQuantityTypeRecorderConfiguration alloc] initWithIdentifier:ORKHeartRateRecorderIdentifier - healthQuantityType:heartRateType unit:bpmUnit]]; + healthQuantityType:heartRateType + unit:bpmUnit + outputDirectory:outputDirectory]]; } #endif @@ -200,7 +207,17 @@ @implementation ORKOrderedTask (ORKPredefinedActiveTask) + (ORKOrderedTask *)amslerGridTaskWithIdentifier:(NSString *)identifier intendedUseDescription:(NSString *)intendedUseDescription - options:(ORKPredefinedTaskOption)options { + options:(ORKPredefinedTaskOption)options { + return [ORKOrderedTask amslerGridTaskWithIdentifier:identifier + intendedUseDescription:intendedUseDescription + options:options + outputDirectory:nil]; +} + ++ (ORKOrderedTask *)amslerGridTaskWithIdentifier:(NSString *)identifier + intendedUseDescription:(NSString *)intendedUseDescription + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL *)outputDirectory { NSMutableArray *steps = [NSMutableArray array]; if (!(options & ORKPredefinedTaskOptionExcludeInstructions)) { @@ -284,7 +301,26 @@ + (ORKNavigableOrderedTask *)holePegTestTaskWithIdentifier:(NSString *)identifie rotated:(BOOL)rotated timeLimit:(NSTimeInterval)timeLimit options:(ORKPredefinedTaskOption)options { - + return [ORKOrderedTask holePegTestTaskWithIdentifier:identifier + intendedUseDescription:intendedUseDescription + dominantHand:dominantHand + numberOfPegs:numberOfPegs + threshold:threshold + rotated:rotated + timeLimit:timeLimit + options:options + outputDirectory:nil]; +} + ++ (ORKNavigableOrderedTask *)holePegTestTaskWithIdentifier:(NSString *)identifier + intendedUseDescription:(nullable NSString *)intendedUseDescription + dominantHand:(ORKBodySagittal)dominantHand + numberOfPegs:(int)numberOfPegs + threshold:(double)threshold + rotated:(BOOL)rotated + timeLimit:(NSTimeInterval)timeLimit + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL *)outputDirectory { NSMutableArray *steps = [NSMutableArray array]; BOOL dominantHandLeft = (dominantHand == ORKBodySagittalLeft); NSTimeInterval stepDuration = (timeLimit == 0) ? CGFLOAT_MAX : timeLimit; @@ -417,6 +453,20 @@ + (ORKOrderedTask *)twoFingerTappingIntervalTaskWithIdentifier:(NSString *)ident duration:(NSTimeInterval)duration handOptions:(ORKPredefinedTaskHandOption)handOptions options:(ORKPredefinedTaskOption)options { + return [ORKOrderedTask twoFingerTappingIntervalTaskWithIdentifier:identifier + intendedUseDescription:intendedUseDescription + duration:duration + handOptions:handOptions + options:options + outputDirectory:nil]; +} + ++ (ORKOrderedTask *)twoFingerTappingIntervalTaskWithIdentifier:(NSString *)identifier + intendedUseDescription:(NSString *)intendedUseDescription + duration:(NSTimeInterval)duration + handOptions:(ORKPredefinedTaskHandOption)handOptions + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL *)outputDirectory { NSString *durationString = [ORKDurationStringFormatter() stringFromTimeInterval:duration]; @@ -532,7 +582,8 @@ + (ORKOrderedTask *)twoFingerTappingIntervalTaskWithIdentifier:(NSString *)ident NSMutableArray *recorderConfigurations = [NSMutableArray arrayWithCapacity:5]; if (!(ORKPredefinedTaskOptionExcludeAccelerometer & options)) { [recorderConfigurations addObject:[[ORKAccelerometerRecorderConfiguration alloc] initWithIdentifier:ORKAccelerometerRecorderIdentifier - frequency:100]]; + frequency:100 + outputDirectory:outputDirectory]]; } ORKTappingIntervalStep *step = [[ORKTappingIntervalStep alloc] initWithIdentifier:appendIdentifier(ORKTappingStepIdentifier)]; @@ -585,6 +636,26 @@ + (ORKNavigableOrderedTask *)audioTaskWithIdentifier:(NSString *)identifier recordingSettings:(nullable NSDictionary *)recordingSettings checkAudioLevel:(BOOL)checkAudioLevel options:(ORKPredefinedTaskOption)options { + return [ORKOrderedTask audioTaskWithIdentifier:identifier + intendedUseDescription:intendedUseDescription + speechInstruction:speechInstruction + shortSpeechInstruction:shortSpeechInstruction + duration:duration + recordingSettings:recordingSettings + checkAudioLevel:checkAudioLevel + options:options + outputDirectory:nil]; +} + ++ (ORKNavigableOrderedTask *)audioTaskWithIdentifier:(NSString *)identifier + intendedUseDescription:(nullable NSString *)intendedUseDescription + speechInstruction:(nullable NSString *)speechInstruction + shortSpeechInstruction:(nullable NSString *)shortSpeechInstruction + duration:(NSTimeInterval)duration + recordingSettings:(nullable NSDictionary *)recordingSettings + checkAudioLevel:(BOOL)checkAudioLevel + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL *)outputDirectory { recordingSettings = recordingSettings ? : @{ AVFormatIDKey : @(kAudioFormatAppleLossless), AVNumberOfChannelsKey : @(2), @@ -628,7 +699,8 @@ + (ORKNavigableOrderedTask *)audioTaskWithIdentifier:(NSString *)identifier // Collect audio during the countdown step too, to provide a baseline. step.recorderConfigurations = @[[[ORKAudioRecorderConfiguration alloc] initWithIdentifier:ORKAudioRecorderIdentifier - recorderSettings:recordingSettings]]; + recorderSettings:recordingSettings + outputDirectory:outputDirectory]]; // If checking the sound level then add text indicating that's what is happening if (checkAudioLevel) { @@ -653,7 +725,8 @@ + (ORKNavigableOrderedTask *)audioTaskWithIdentifier:(NSString *)identifier step.title = ORKLocalizedString(@"AUDIO_TASK_TITLE", nil); step.text = shortSpeechInstruction ? : ORKLocalizedString(@"AUDIO_INSTRUCTION", nil); step.recorderConfigurations = @[[[ORKAudioRecorderConfiguration alloc] initWithIdentifier:ORKAudioRecorderIdentifier - recorderSettings:recordingSettings]]; + recorderSettings:recordingSettings + outputDirectory:outputDirectory]]; step.stepDuration = duration; step.shouldContinueOnFinish = YES; @@ -691,7 +764,21 @@ + (ORKOrderedTask *)fitnessCheckTaskWithIdentifier:(NSString *)identifier intendedUseDescription:(NSString *)intendedUseDescription walkDuration:(NSTimeInterval)walkDuration restDuration:(NSTimeInterval)restDuration - options:(ORKPredefinedTaskOption)options { + options:(ORKPredefinedTaskOption)options { + return [ORKOrderedTask fitnessCheckTaskWithIdentifier:identifier + intendedUseDescription:intendedUseDescription + walkDuration:walkDuration + restDuration:restDuration + options:options + outputDirectory:nil]; +} + ++ (ORKOrderedTask *)fitnessCheckTaskWithIdentifier:(NSString *)identifier + intendedUseDescription:(NSString *)intendedUseDescription + walkDuration:(NSTimeInterval)walkDuration + restDuration:(NSTimeInterval)restDuration + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL *)outputDirectory { NSDateComponentsFormatter *formatter = [self textTimeFormatter]; @@ -735,7 +822,8 @@ + (ORKOrderedTask *)fitnessCheckTaskWithIdentifier:(NSString *)identifier fitnessStep.title = ORKLocalizedString(@"FITNESS_TASK_TITLE", nil); fitnessStep.text = [NSString localizedStringWithFormat:ORKLocalizedString(@"FITNESS_WALK_INSTRUCTION_FORMAT", nil), [formatter stringFromTimeInterval:walkDuration]]; fitnessStep.spokenInstruction = fitnessStep.text; - fitnessStep.recorderConfigurations = [self makeRecorderConfigurationsWithOptions:options]; + fitnessStep.recorderConfigurations = [self makeRecorderConfigurationsWithOptions:options + outputDirectory:outputDirectory]; fitnessStep.shouldContinueOnFinish = YES; fitnessStep.optional = NO; fitnessStep.shouldStartTimerAutomatically = YES; @@ -754,7 +842,8 @@ + (ORKOrderedTask *)fitnessCheckTaskWithIdentifier:(NSString *)identifier stillStep.title = ORKLocalizedString(@"FITNESS_TASK_TITLE", nil); stillStep.text = [NSString localizedStringWithFormat:ORKLocalizedString(@"FITNESS_SIT_INSTRUCTION_FORMAT", nil), [formatter stringFromTimeInterval:restDuration]]; stillStep.spokenInstruction = stillStep.text; - stillStep.recorderConfigurations = [self makeRecorderConfigurationsWithOptions:options]; + stillStep.recorderConfigurations = [self makeRecorderConfigurationsWithOptions:options + outputDirectory:outputDirectory]; stillStep.shouldContinueOnFinish = YES; stillStep.optional = NO; stillStep.shouldStartTimerAutomatically = YES; @@ -789,6 +878,16 @@ + (ORKOrderedTask *)fitnessCheckTaskWithIdentifier:(NSString *)identifier + (ORKOrderedTask *)sixMinuteWalkTaskWithIdentifier:(NSString *)identifier intendedUseDescription:(NSString *)intendedUseDescription options:(ORKPredefinedTaskOption)options { + return [ORKOrderedTask sixMinuteWalkTaskWithIdentifier:identifier + intendedUseDescription:intendedUseDescription + options:options + outputDirectory:nil]; +} + ++ (ORKOrderedTask *)sixMinuteWalkTaskWithIdentifier:(NSString *)identifier + intendedUseDescription:(NSString *)intendedUseDescription + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL *)outputDirectory { NSTimeInterval walkDuration = 360; // 6 minutes NSMutableArray *steps = [NSMutableArray array]; @@ -875,7 +974,8 @@ + (ORKOrderedTask *)sixMinuteWalkTaskWithIdentifier:(NSString *)identifier fitnessStep.title = ORKLocalizedString(@"6MWT_TEST_IN_PROGRESS", nil); fitnessStep.text = ORKLocalizedString(@"6MWT_TEST_IN_PROGRESS_DETAIL", nil); fitnessStep.spokenInstruction = fitnessStep.text; - fitnessStep.recorderConfigurations = [self makeRecorderConfigurationsWithOptions:options]; + fitnessStep.recorderConfigurations = [self makeRecorderConfigurationsWithOptions:options + outputDirectory:outputDirectory]; fitnessStep.shouldContinueOnFinish = YES; fitnessStep.optional = NO; fitnessStep.shouldStartTimerAutomatically = YES; @@ -948,6 +1048,22 @@ + (ORKOrderedTask *)tecumsehCubeTaskWithIdentifier:(NSString *)identifier audioResourceName:(NSString *)audioResourceName audioFileExtension:(nullable NSString*)audioFileExtension options:(ORKPredefinedTaskOption)options { + return [ORKOrderedTask tecumsehCubeTaskWithIdentifier:identifier + intendedUseDescription:intendedUseDescription + audioBundleIdentifier:audioResourceName + audioResourceName:audioResourceName + audioFileExtension:audioFileExtension + options:options + outputDirectory:nil]; +} + ++ (ORKOrderedTask *)tecumsehCubeTaskWithIdentifier:(NSString *)identifier + intendedUseDescription:(nullable NSString *)intendedUseDescription + audioBundleIdentifier:(NSString *)audioBundleIdentifier + audioResourceName:(NSString *)audioResourceName + audioFileExtension:(nullable NSString*)audioFileExtension + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL *)outputDirectory { NSTimeInterval stepDuration = 180; // 3 minutes NSTimeInterval restDuration = 180; // 3 minutes @@ -1032,7 +1148,8 @@ + (ORKOrderedTask *)tecumsehCubeTaskWithIdentifier:(NSString *)identifier cubeStep.title = ORKLocalizedString(@"TC_TEST_IN_PROGRESS", nil); cubeStep.text = ORKLocalizedString(@"TC_TEST_IN_PROGRESS_DETAIL", nil); cubeStep.spokenInstruction = cubeStep.text; - cubeStep.recorderConfigurations = [self makeRecorderConfigurationsWithOptions:options]; + cubeStep.recorderConfigurations = [self makeRecorderConfigurationsWithOptions:options + outputDirectory:outputDirectory]; cubeStep.shouldContinueOnFinish = YES; cubeStep.optional = NO; cubeStep.shouldStartTimerAutomatically = YES; @@ -1050,7 +1167,8 @@ + (ORKOrderedTask *)tecumsehCubeTaskWithIdentifier:(NSString *)identifier stillStep.title = ORKLocalizedString(@"TC_REST_IN_PROGRESS", nil); stillStep.text = ORKLocalizedString(@"TC_REST_IN_PROGRESS_DETAIL", nil); stillStep.spokenInstruction = stillStep.text; - stillStep.recorderConfigurations = [self makeRecorderConfigurationsWithOptions:options]; + stillStep.recorderConfigurations = [self makeRecorderConfigurationsWithOptions:options + outputDirectory:outputDirectory]; stillStep.shouldContinueOnFinish = YES; stillStep.optional = NO; stillStep.shouldStartTimerAutomatically = YES; @@ -1082,6 +1200,20 @@ + (ORKOrderedTask *)shortWalkTaskWithIdentifier:(NSString *)identifier numberOfStepsPerLeg:(NSInteger)numberOfStepsPerLeg restDuration:(NSTimeInterval)restDuration options:(ORKPredefinedTaskOption)options { + return [ORKOrderedTask shortWalkTaskWithIdentifier:identifier + intendedUseDescription:intendedUseDescription + numberOfStepsPerLeg:numberOfStepsPerLeg + restDuration:restDuration + options:options + outputDirectory:nil]; +} + ++ (ORKOrderedTask *)shortWalkTaskWithIdentifier:(NSString *)identifier + intendedUseDescription:(NSString *)intendedUseDescription + numberOfStepsPerLeg:(NSInteger)numberOfStepsPerLeg + restDuration:(NSTimeInterval)restDuration + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL *)outputDirectory { NSDateComponentsFormatter *formatter = [self textTimeFormatter]; @@ -1123,15 +1255,18 @@ + (ORKOrderedTask *)shortWalkTaskWithIdentifier:(NSString *)identifier { NSMutableArray *recorderConfigurations = [NSMutableArray array]; if (!(ORKPredefinedTaskOptionExcludePedometer & options)) { - [recorderConfigurations addObject:[[ORKPedometerRecorderConfiguration alloc] initWithIdentifier:ORKPedometerRecorderIdentifier]]; + [recorderConfigurations addObject:[[ORKPedometerRecorderConfiguration alloc] initWithIdentifier:ORKPedometerRecorderIdentifier + outputDirectory:outputDirectory]]; } if (!(ORKPredefinedTaskOptionExcludeAccelerometer & options)) { [recorderConfigurations addObject:[[ORKAccelerometerRecorderConfiguration alloc] initWithIdentifier:ORKAccelerometerRecorderIdentifier - frequency:100]]; + frequency:100 + outputDirectory:outputDirectory]]; } if (!(ORKPredefinedTaskOptionExcludeDeviceMotion & options)) { [recorderConfigurations addObject:[[ORKDeviceMotionRecorderConfiguration alloc] initWithIdentifier:ORKDeviceMotionRecorderIdentifier - frequency:100]]; + frequency:100 + outputDirectory:outputDirectory]]; } ORKWalkingTaskStep *walkingStep = [[ORKWalkingTaskStep alloc] initWithIdentifier:ORKShortWalkOutboundStepIdentifier]; @@ -1153,15 +1288,18 @@ + (ORKOrderedTask *)shortWalkTaskWithIdentifier:(NSString *)identifier { NSMutableArray *recorderConfigurations = [NSMutableArray array]; if (!(ORKPredefinedTaskOptionExcludePedometer & options)) { - [recorderConfigurations addObject:[[ORKPedometerRecorderConfiguration alloc] initWithIdentifier:ORKPedometerRecorderIdentifier]]; + [recorderConfigurations addObject:[[ORKPedometerRecorderConfiguration alloc] initWithIdentifier:ORKPedometerRecorderIdentifier + outputDirectory:outputDirectory]]; } if (!(ORKPredefinedTaskOptionExcludeAccelerometer & options)) { [recorderConfigurations addObject:[[ORKAccelerometerRecorderConfiguration alloc] initWithIdentifier:ORKAccelerometerRecorderIdentifier - frequency:100]]; + frequency:100 + outputDirectory:outputDirectory]]; } if (!(ORKPredefinedTaskOptionExcludeDeviceMotion & options)) { [recorderConfigurations addObject:[[ORKDeviceMotionRecorderConfiguration alloc] initWithIdentifier:ORKDeviceMotionRecorderIdentifier - frequency:100]]; + frequency:100 + outputDirectory:outputDirectory]]; } ORKWalkingTaskStep *walkingStep = [[ORKWalkingTaskStep alloc] initWithIdentifier:ORKShortWalkReturnStepIdentifier]; @@ -1184,11 +1322,13 @@ + (ORKOrderedTask *)shortWalkTaskWithIdentifier:(NSString *)identifier NSMutableArray *recorderConfigurations = [NSMutableArray array]; if (!(ORKPredefinedTaskOptionExcludeAccelerometer & options)) { [recorderConfigurations addObject:[[ORKAccelerometerRecorderConfiguration alloc] initWithIdentifier:ORKAccelerometerRecorderIdentifier - frequency:100]]; + frequency:100 + outputDirectory:outputDirectory]]; } if (!(ORKPredefinedTaskOptionExcludeDeviceMotion & options)) { [recorderConfigurations addObject:[[ORKDeviceMotionRecorderConfiguration alloc] initWithIdentifier:ORKDeviceMotionRecorderIdentifier - frequency:100]]; + frequency:100 + outputDirectory:outputDirectory]]; } ORKFitnessStep *activeStep = [[ORKFitnessStep alloc] initWithIdentifier:ORKShortWalkRestStepIdentifier]; @@ -1228,6 +1368,20 @@ + (ORKOrderedTask *)walkBackAndForthTaskWithIdentifier:(NSString *)identifier walkDuration:(NSTimeInterval)walkDuration restDuration:(NSTimeInterval)restDuration options:(ORKPredefinedTaskOption)options { + return [ORKOrderedTask walkBackAndForthTaskWithIdentifier:identifier + intendedUseDescription:intendedUseDescription + walkDuration:walkDuration + restDuration:restDuration + options:options + outputDirectory:nil]; +} + ++ (ORKOrderedTask *)walkBackAndForthTaskWithIdentifier:(NSString *)identifier + intendedUseDescription:(NSString *)intendedUseDescription + walkDuration:(NSTimeInterval)walkDuration + restDuration:(NSTimeInterval)restDuration + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL *)outputDirectory { NSDateComponentsFormatter *formatter = [self textTimeFormatter]; formatter.unitsStyle = NSDateComponentsFormatterUnitsStyleFull; @@ -1270,15 +1424,18 @@ + (ORKOrderedTask *)walkBackAndForthTaskWithIdentifier:(NSString *)identifier { NSMutableArray *recorderConfigurations = [NSMutableArray array]; if (!(ORKPredefinedTaskOptionExcludePedometer & options)) { - [recorderConfigurations addObject:[[ORKPedometerRecorderConfiguration alloc] initWithIdentifier:ORKPedometerRecorderIdentifier]]; + [recorderConfigurations addObject:[[ORKPedometerRecorderConfiguration alloc] initWithIdentifier:ORKPedometerRecorderIdentifier + outputDirectory:outputDirectory]]; } if (!(ORKPredefinedTaskOptionExcludeAccelerometer & options)) { [recorderConfigurations addObject:[[ORKAccelerometerRecorderConfiguration alloc] initWithIdentifier:ORKAccelerometerRecorderIdentifier - frequency:100]]; + frequency:100 + outputDirectory:outputDirectory]]; } if (!(ORKPredefinedTaskOptionExcludeDeviceMotion & options)) { [recorderConfigurations addObject:[[ORKDeviceMotionRecorderConfiguration alloc] initWithIdentifier:ORKDeviceMotionRecorderIdentifier - frequency:100]]; + frequency:100 + outputDirectory:outputDirectory]]; } ORKWalkingTaskStep *walkingStep = [[ORKWalkingTaskStep alloc] initWithIdentifier:ORKShortWalkOutboundStepIdentifier]; @@ -1304,11 +1461,13 @@ + (ORKOrderedTask *)walkBackAndForthTaskWithIdentifier:(NSString *)identifier NSMutableArray *recorderConfigurations = [NSMutableArray array]; if (!(ORKPredefinedTaskOptionExcludeAccelerometer & options)) { [recorderConfigurations addObject:[[ORKAccelerometerRecorderConfiguration alloc] initWithIdentifier:ORKAccelerometerRecorderIdentifier - frequency:100]]; + frequency:100 + outputDirectory:outputDirectory]]; } if (!(ORKPredefinedTaskOptionExcludeDeviceMotion & options)) { [recorderConfigurations addObject:[[ORKDeviceMotionRecorderConfiguration alloc] initWithIdentifier:ORKDeviceMotionRecorderIdentifier - frequency:100]]; + frequency:100 + outputDirectory:outputDirectory]]; } ORKFitnessStep *activeStep = [[ORKFitnessStep alloc] initWithIdentifier:ORKShortWalkRestStepIdentifier]; @@ -1351,6 +1510,18 @@ + (ORKOrderedTask *)kneeRangeOfMotionTaskWithIdentifier:(NSString *)identifier limbOption:(ORKPredefinedTaskLimbOption)limbOption intendedUseDescription:(NSString *)intendedUseDescription options:(ORKPredefinedTaskOption)options { + return [ORKOrderedTask kneeRangeOfMotionTaskWithIdentifier:identifier + limbOption:limbOption + intendedUseDescription:intendedUseDescription + options:options + outputDirectory:nil]; +} + ++ (ORKOrderedTask *)kneeRangeOfMotionTaskWithIdentifier:(NSString *)identifier + limbOption:(ORKPredefinedTaskLimbOption)limbOption + intendedUseDescription:(NSString *)intendedUseDescription + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL *)outputDirectory { NSMutableArray *steps = [NSMutableArray array]; NSString *limbType = ORKLocalizedString(@"LIMB_RIGHT", nil); UIImage *kneeStartImage = [UIImage imageNamed:@"knee_start_right" inBundle:[NSBundle bundleForClass:[self class]] compatibleWithTraitCollection:nil]; @@ -1423,7 +1594,9 @@ + (ORKOrderedTask *)kneeRangeOfMotionTaskWithIdentifier:(NSString *)identifier touchAnywhereStep.spokenInstruction = touchAnywhereStep.text; - ORKDeviceMotionRecorderConfiguration *deviceMotionRecorderConfig = [[ORKDeviceMotionRecorderConfiguration alloc] initWithIdentifier:ORKDeviceMotionRecorderIdentifier frequency:100]; + ORKDeviceMotionRecorderConfiguration *deviceMotionRecorderConfig = [[ORKDeviceMotionRecorderConfiguration alloc] initWithIdentifier:ORKDeviceMotionRecorderIdentifier + frequency:100 + outputDirectory:outputDirectory]; ORKRangeOfMotionStep *kneeRangeOfMotionStep = [[ORKRangeOfMotionStep alloc] initWithIdentifier:ORKKneeRangeOfMotionStepIdentifier limbOption:limbOption]; kneeRangeOfMotionStep.image = kneeStartImage; @@ -1456,6 +1629,18 @@ + (ORKOrderedTask *)shoulderRangeOfMotionTaskWithIdentifier:(NSString *)identifi limbOption:(ORKPredefinedTaskLimbOption)limbOption intendedUseDescription:(NSString *)intendedUseDescription options:(ORKPredefinedTaskOption)options { + return [ORKOrderedTask shoulderRangeOfMotionTaskWithIdentifier:identifier + limbOption:limbOption + intendedUseDescription:intendedUseDescription + options:options + outputDirectory:nil]; +} + ++ (ORKOrderedTask *)shoulderRangeOfMotionTaskWithIdentifier:(NSString *)identifier + limbOption:(ORKPredefinedTaskLimbOption)limbOption + intendedUseDescription:(NSString *)intendedUseDescription + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL *)outputDirectory { NSMutableArray *steps = [NSMutableArray array]; NSString *limbType = ORKLocalizedString(@"LIMB_RIGHT", nil); UIImage *shoulderStartImage = [UIImage imageNamed:@"shoulder_start_right" inBundle:[NSBundle bundleForClass:[self class]] compatibleWithTraitCollection:nil]; @@ -1507,16 +1692,18 @@ + (ORKOrderedTask *)shoulderRangeOfMotionTaskWithIdentifier:(NSString *)identifi touchAnywhereStep.title = ORKLocalizedString(@"RANGE_OF_MOTION_TITLE", nil); ORKStepArrayAddStep(steps, touchAnywhereStep); - touchAnywhereStep.spokenInstruction = touchAnywhereStep.title; + touchAnywhereStep.spokenInstruction = [touchAnywhereStep.title stringByAppendingString:[@":" stringByAppendingString:touchAnywhereStep.text]]; - ORKDeviceMotionRecorderConfiguration *deviceMotionRecorderConfig = [[ORKDeviceMotionRecorderConfiguration alloc] initWithIdentifier:ORKDeviceMotionRecorderIdentifier frequency:100]; + ORKDeviceMotionRecorderConfiguration *deviceMotionRecorderConfig = [[ORKDeviceMotionRecorderConfiguration alloc] initWithIdentifier:ORKDeviceMotionRecorderIdentifier + frequency:100 + outputDirectory:outputDirectory]; ORKShoulderRangeOfMotionStep *shoulderRangeOfMotionStep = [[ORKShoulderRangeOfMotionStep alloc] initWithIdentifier:ORKShoulderRangeOfMotionStepIdentifier limbOption:limbOption]; shoulderRangeOfMotionStep.title = ORKLocalizedString(@"RANGE_OF_MOTION_TITLE", nil); shoulderRangeOfMotionStep.text = ([limbType isEqualToString: ORKLocalizedString(@"LIMB_LEFT", nil)])? ORKLocalizedString(@"SHOULDER_RANGE_OF_MOTION_SPOKEN_INSTRUCTION_LEFT", nil) : ORKLocalizedString(@"SHOULDER_RANGE_OF_MOTION_SPOKEN_INSTRUCTION_RIGHT", nil); - shoulderRangeOfMotionStep.spokenInstruction = shoulderRangeOfMotionStep.text; + shoulderRangeOfMotionStep.spokenInstruction = [shoulderRangeOfMotionStep.title stringByAppendingString:[@":" stringByAppendingString:shoulderRangeOfMotionStep.text]]; shoulderRangeOfMotionStep.recorderConfigurations = @[deviceMotionRecorderConfig]; shoulderRangeOfMotionStep.optional = NO; @@ -1550,6 +1737,34 @@ + (ORKOrderedTask *)spatialSpanMemoryTaskWithIdentifier:(NSString *)identifier customTargetPluralName:(NSString *)customTargetPluralName requireReversal:(BOOL)requireReversal options:(ORKPredefinedTaskOption)options { + return [ORKOrderedTask spatialSpanMemoryTaskWithIdentifier:identifier + intendedUseDescription:intendedUseDescription + initialSpan:initialSpan + minimumSpan:minimumSpan + maximumSpan:maximumSpan + playSpeed:playSpeed + maximumTests:maximumTests + maximumConsecutiveFailures:maximumConsecutiveFailures + customTargetImage:customTargetImage + customTargetPluralName:customTargetPluralName + requireReversal:requireReversal + options:options + outputDirectory:nil]; +} + ++ (ORKOrderedTask *)spatialSpanMemoryTaskWithIdentifier:(NSString *)identifier + intendedUseDescription:(NSString *)intendedUseDescription + initialSpan:(NSInteger)initialSpan + minimumSpan:(NSInteger)minimumSpan + maximumSpan:(NSInteger)maximumSpan + playSpeed:(NSTimeInterval)playSpeed + maximumTests:(NSInteger)maximumTests + maximumConsecutiveFailures:(NSInteger)maximumConsecutiveFailures + customTargetImage:(UIImage *)customTargetImage + customTargetPluralName:(NSString *)customTargetPluralName + requireReversal:(BOOL)requireReversal + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL *)outputDirectory { NSString *targetPluralName = customTargetPluralName ? : ORKLocalizedString(@"SPATIAL_SPAN_MEMORY_TARGET_PLURAL", nil); @@ -1628,6 +1843,26 @@ + (ORKOrderedTask *)speechRecognitionTaskWithIdentifier:(NSString *)identifier shouldHideTranscript:(BOOL)shouldHideTranscript allowsEdittingTranscript:(BOOL)allowsEdittingTranscript options:(ORKPredefinedTaskOption)options { + return [ORKOrderedTask speechRecognitionTaskWithIdentifier:identifier + intendedUseDescription:intendedUseDescription + speechRecognizerLocale:speechRecognizerLocale + speechRecognitionImage:speechRecognitionImage + speechRecognitionText:speechRecognitionText + shouldHideTranscript:shouldHideTranscript + allowsEdittingTranscript:allowsEdittingTranscript + options:options + outputDirectory:nil]; +} + ++ (ORKOrderedTask *)speechRecognitionTaskWithIdentifier:(NSString *)identifier + intendedUseDescription:(nullable NSString *)intendedUseDescription + speechRecognizerLocale:(ORKSpeechRecognizerLocale)speechRecognizerLocale + speechRecognitionImage:(nullable UIImage *)speechRecognitionImage + speechRecognitionText:(nullable NSString *)speechRecognitionText + shouldHideTranscript:(BOOL)shouldHideTranscript + allowsEdittingTranscript:(BOOL)allowsEdittingTranscript + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL *)outputDirectory { NSMutableArray *steps = [NSMutableArray array]; if (!(options & ORKPredefinedTaskOptionExcludeInstructions)) { @@ -1659,7 +1894,8 @@ + (ORKOrderedTask *)speechRecognitionTaskWithIdentifier:(NSString *)identifier } ORKSpeechRecognitionStep *step = [[ORKSpeechRecognitionStep alloc] initWithIdentifier: ORKSpeechRecognitionStepIdentifier image:speechRecognitionImage text:speechRecognitionText]; - ORKStreamingAudioRecorderConfiguration *config = [[ORKStreamingAudioRecorderConfiguration alloc] initWithIdentifier: ORKStreamingAudioRecorderIdentifier]; + ORKStreamingAudioRecorderConfiguration *config = [[ORKStreamingAudioRecorderConfiguration alloc] initWithIdentifier: ORKStreamingAudioRecorderIdentifier + outputDirectory:outputDirectory]; step.title = ORKLocalizedString(@"SPEECH_TASK_TITLE", nil); step.shouldHideTranscript = shouldHideTranscript; step.recorderConfigurations = @[config]; @@ -1700,6 +1936,16 @@ + (ORKOrderedTask *)speechRecognitionTaskWithIdentifier:(NSString *)identifier + (ORKOrderedTask *)speechInNoiseTaskWithIdentifier:(NSString *)identifier intendedUseDescription:(nullable NSString *)intendedUseDescription options:(ORKPredefinedTaskOption)options { + return [ORKOrderedTask speechInNoiseTaskWithIdentifier:identifier + intendedUseDescription:intendedUseDescription + options:options + outputDirectory:nil]; +} + ++ (ORKOrderedTask *)speechInNoiseTaskWithIdentifier:(NSString *)identifier + intendedUseDescription:(nullable NSString *)intendedUseDescription + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL *)outputDirectory { NSMutableArray *steps = [NSMutableArray array]; { @@ -1748,7 +1994,8 @@ + (ORKOrderedTask *)speechInNoiseTaskWithIdentifier:(NSString *)identifier { ORKSpeechRecognitionStep *step = [[ORKSpeechRecognitionStep alloc] initWithIdentifier: ORKSpeechInNoiseStep2Identifier image:nil text:nil]; - ORKStreamingAudioRecorderConfiguration *config = [[ORKStreamingAudioRecorderConfiguration alloc] initWithIdentifier: ORKStreamingAudioRecorderIdentifier]; + ORKStreamingAudioRecorderConfiguration *config = [[ORKStreamingAudioRecorderConfiguration alloc] initWithIdentifier: ORKStreamingAudioRecorderIdentifier + outputDirectory:outputDirectory]; step.title = ORKLocalizedString(@"SPEECH_IN_NOISE_SPEAK_TITLE", nil); step.text = ORKLocalizedString(@"SPEECH_IN_NOISE_SPEAK_TEXT", nil); step.shouldHideTranscript = YES; @@ -1791,6 +2038,18 @@ + (ORKOrderedTask *)stroopTaskWithIdentifier:(NSString *)identifier intendedUseDescription:(nullable NSString *)intendedUseDescription numberOfAttempts:(NSInteger)numberOfAttempts options:(ORKPredefinedTaskOption)options { + return [ORKOrderedTask stroopTaskWithIdentifier:identifier + intendedUseDescription:intendedUseDescription + numberOfAttempts:numberOfAttempts + options:options + outputDirectory:nil]; +} + ++ (ORKOrderedTask *)stroopTaskWithIdentifier:(NSString *)identifier + intendedUseDescription:(nullable NSString *)intendedUseDescription + numberOfAttempts:(NSInteger)numberOfAttempts + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL *)outputDirectory { NSMutableArray *steps = [NSMutableArray array]; if (!(options & ORKPredefinedTaskOptionExcludeInstructions)) { { @@ -1856,6 +2115,22 @@ + (ORKOrderedTask *)toneAudiometryTaskWithIdentifier:(NSString *)identifier shortSpeechInstruction:(nullable NSString *)shortSpeechInstruction toneDuration:(NSTimeInterval)toneDuration options:(ORKPredefinedTaskOption)options { + return [ORKOrderedTask toneAudiometryTaskWithIdentifier:identifier + intendedUseDescription:intendedUseDescription + speechInstruction:speechInstruction + shortSpeechInstruction:shortSpeechInstruction + toneDuration:toneDuration + options:options + outputDirectory:nil]; +} + ++ (ORKOrderedTask *)toneAudiometryTaskWithIdentifier:(NSString *)identifier + intendedUseDescription:(nullable NSString *)intendedUseDescription + speechInstruction:(nullable NSString *)speechInstruction + shortSpeechInstruction:(nullable NSString *)shortSpeechInstruction + toneDuration:(NSTimeInterval)toneDuration + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL *)outputDirectory { if (options & ORKPredefinedTaskOptionExcludeAudio) { @throw [NSException exceptionWithName:NSGenericException reason:@"Audio collection cannot be excluded from audio task" userInfo:nil]; @@ -1940,7 +2215,17 @@ + (ORKOrderedTask *)toneAudiometryTaskWithIdentifier:(NSString *)identifier + (ORKNavigableOrderedTask *)dBHLToneAudiometryTaskWithIdentifier:(NSString *)identifier intendedUseDescription:(nullable NSString *)intendedUseDescription - options:(ORKPredefinedTaskOption)options { + options:(ORKPredefinedTaskOption)options { + return [ORKOrderedTask dBHLToneAudiometryTaskWithIdentifier:identifier + intendedUseDescription:intendedUseDescription + options:options + outputDirectory:nil]; +} + ++ (ORKNavigableOrderedTask *)dBHLToneAudiometryTaskWithIdentifier:(NSString *)identifier + intendedUseDescription:(nullable NSString *)intendedUseDescription + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL *)outputDirectory { if (options & ORKPredefinedTaskOptionExcludeAudio) { @throw [NSException exceptionWithName:NSGenericException reason:@"Audio collection cannot be excluded from audio task" userInfo:nil]; @@ -2055,6 +2340,18 @@ + (ORKOrderedTask *)towerOfHanoiTaskWithIdentifier:(NSString *)identifier intendedUseDescription:(nullable NSString *)intendedUseDescription numberOfDisks:(NSUInteger)numberOfDisks options:(ORKPredefinedTaskOption)options { + return [ORKOrderedTask towerOfHanoiTaskWithIdentifier:identifier + intendedUseDescription:intendedUseDescription + numberOfDisks:numberOfDisks + options:options + outputDirectory:nil]; +} + ++ (ORKOrderedTask *)towerOfHanoiTaskWithIdentifier:(NSString *)identifier + intendedUseDescription:(nullable NSString *)intendedUseDescription + numberOfDisks:(NSUInteger)numberOfDisks + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL *)outputDirectory { NSMutableArray *steps = [NSMutableArray array]; @@ -2118,7 +2415,32 @@ + (ORKOrderedTask *)reactionTimeTaskWithIdentifier:(NSString *)identifier timeoutSound:(UInt32)timeoutSoundID failureSound:(UInt32)failureSoundID options:(ORKPredefinedTaskOption)options { - + return [ORKOrderedTask reactionTimeTaskWithIdentifier:identifier + intendedUseDescription:intendedUseDescription + maximumStimulusInterval:maximumStimulusInterval + minimumStimulusInterval:minimumStimulusInterval + thresholdAcceleration:thresholdAcceleration + numberOfAttempts:numberOfAttempts + timeout:timeout + successSound:successSoundID + timeoutSound:timeoutSoundID + failureSound:failureSoundID + options:options + outputDirectory:nil]; +} + ++ (ORKOrderedTask *)reactionTimeTaskWithIdentifier:(NSString *)identifier + intendedUseDescription:(nullable NSString *)intendedUseDescription + maximumStimulusInterval:(NSTimeInterval)maximumStimulusInterval + minimumStimulusInterval:(NSTimeInterval)minimumStimulusInterval + thresholdAcceleration:(double)thresholdAcceleration + numberOfAttempts:(int)numberOfAttempts + timeout:(NSTimeInterval)timeout + successSound:(UInt32)successSoundID + timeoutSound:(UInt32)timeoutSoundID + failureSound:(UInt32)failureSoundID + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL *)outputDirectory { NSMutableArray *steps = [NSMutableArray array]; if (!(options & ORKPredefinedTaskOptionExcludeInstructions)) { @@ -2159,7 +2481,9 @@ + (ORKOrderedTask *)reactionTimeTaskWithIdentifier:(NSString *)identifier step.successSound = successSoundID; step.timeoutSound = timeoutSoundID; step.failureSound = failureSoundID; - step.recorderConfigurations = @[ [[ORKDeviceMotionRecorderConfiguration alloc] initWithIdentifier:ORKDeviceMotionRecorderIdentifier frequency: 100]]; + step.recorderConfigurations = @[ [[ORKDeviceMotionRecorderConfiguration alloc] initWithIdentifier:ORKDeviceMotionRecorderIdentifier + frequency: 100 + outputDirectory:outputDirectory]]; ORKStepArrayAddStep(steps, step); @@ -2187,8 +2511,33 @@ + (ORKOrderedTask *)normalizedReactionTimeTaskWithIdentifier:(NSString *)identif successSound:(UInt32)successSoundID timeoutSound:(UInt32)timeoutSoundID failureSound:(UInt32)failureSoundID - options:(ORKPredefinedTaskOption)options { - + options:(ORKPredefinedTaskOption)options { + return [ORKOrderedTask normalizedReactionTimeTaskWithIdentifier:identifier + intendedUseDescription:intendedUseDescription + maximumStimulusInterval:maximumStimulusInterval + minimumStimulusInterval:minimumStimulusInterval + thresholdAcceleration:thresholdAcceleration + numberOfAttempts:numberOfAttempts + timeout:timeout + successSound:successSoundID + timeoutSound:timeoutSoundID + failureSound:failureSoundID + options:options + outputDirectory:nil]; +} + ++ (ORKOrderedTask *)normalizedReactionTimeTaskWithIdentifier:(NSString *)identifier + intendedUseDescription:(nullable NSString *)intendedUseDescription + maximumStimulusInterval:(NSTimeInterval)maximumStimulusInterval + minimumStimulusInterval:(NSTimeInterval)minimumStimulusInterval + thresholdAcceleration:(double)thresholdAcceleration + numberOfAttempts:(int)numberOfAttempts + timeout:(NSTimeInterval)timeout + successSound:(UInt32)successSoundID + timeoutSound:(UInt32)timeoutSoundID + failureSound:(UInt32)failureSoundID + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL *)outputDirectory { NSMutableArray *steps = [NSMutableArray array]; if (!(options & ORKPredefinedTaskOptionExcludeInstructions)) { @@ -2229,7 +2578,9 @@ + (ORKOrderedTask *)normalizedReactionTimeTaskWithIdentifier:(NSString *)identif step.successSound = successSoundID; step.timeoutSound = timeoutSoundID; step.failureSound = failureSoundID; - step.recorderConfigurations = @[ [[ORKDeviceMotionRecorderConfiguration alloc] initWithIdentifier:ORKDeviceMotionRecorderIdentifier frequency: 100]]; + step.recorderConfigurations = @[ [[ORKDeviceMotionRecorderConfiguration alloc] initWithIdentifier:ORKDeviceMotionRecorderIdentifier + frequency: 100 + outputDirectory:outputDirectory]]; ORKStepArrayAddStep(steps, step); @@ -2259,7 +2610,24 @@ + (ORKOrderedTask *)timedWalkTaskWithIdentifier:(NSString *)identifier turnAroundTimeLimit:(NSTimeInterval)turnAroundTimeLimit includeAssistiveDeviceForm:(BOOL)includeAssistiveDeviceForm options:(ORKPredefinedTaskOption)options { + return [ORKOrderedTask timedWalkTaskWithIdentifier:identifier + intendedUseDescription:intendedUseDescription + distanceInMeters:distanceInMeters + timeLimit:timeLimit + turnAroundTimeLimit:turnAroundTimeLimit + includeAssistiveDeviceForm:includeAssistiveDeviceForm + options:options + outputDirectory:nil]; +} ++ (ORKOrderedTask *)timedWalkTaskWithIdentifier:(NSString *)identifier + intendedUseDescription:(nullable NSString *)intendedUseDescription + distanceInMeters:(double)distanceInMeters + timeLimit:(NSTimeInterval)timeLimit + turnAroundTimeLimit:(NSTimeInterval)turnAroundTimeLimit + includeAssistiveDeviceForm:(BOOL)includeAssistiveDeviceForm + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL *)outputDirectory { NSMutableArray *steps = [NSMutableArray array]; NSLengthFormatter *lengthFormatter = [NSLengthFormatter new]; @@ -2339,20 +2707,24 @@ + (ORKOrderedTask *)timedWalkTaskWithIdentifier:(NSString *)identifier { NSMutableArray *recorderConfigurations = [NSMutableArray array]; if (!(options & ORKPredefinedTaskOptionExcludePedometer)) { - [recorderConfigurations addObject:[[ORKPedometerRecorderConfiguration alloc] initWithIdentifier:ORKPedometerRecorderIdentifier]]; + [recorderConfigurations addObject:[[ORKPedometerRecorderConfiguration alloc] initWithIdentifier:ORKPedometerRecorderIdentifier + outputDirectory:outputDirectory]]; } if (!(options & ORKPredefinedTaskOptionExcludeAccelerometer)) { [recorderConfigurations addObject:[[ORKAccelerometerRecorderConfiguration alloc] initWithIdentifier:ORKAccelerometerRecorderIdentifier - frequency:100]]; + frequency:100 + outputDirectory:outputDirectory]]; } if (!(options & ORKPredefinedTaskOptionExcludeDeviceMotion)) { [recorderConfigurations addObject:[[ORKDeviceMotionRecorderConfiguration alloc] initWithIdentifier:ORKDeviceMotionRecorderIdentifier - frequency:100]]; + frequency:100 + outputDirectory:outputDirectory]]; } #if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION if (! (options & ORKPredefinedTaskOptionExcludeLocation)) { - [recorderConfigurations addObject:[[ORKLocationRecorderConfiguration alloc] initWithIdentifier:ORKLocationRecorderIdentifier]]; + [recorderConfigurations addObject:[[ORKLocationRecorderConfiguration alloc] initWithIdentifier:ORKLocationRecorderIdentifier + outputDirectory:outputDirectory]]; } #endif { @@ -2425,7 +2797,24 @@ + (ORKOrderedTask *)PSATTaskWithIdentifier:(NSString *)identifier stimulusDuration:(NSTimeInterval)stimulusDuration seriesLength:(NSInteger)seriesLength options:(ORKPredefinedTaskOption)options { - + return [ORKOrderedTask PSATTaskWithIdentifier:identifier + intendedUseDescription:intendedUseDescription + presentationMode:presentationMode + interStimulusInterval:interStimulusInterval + stimulusDuration:stimulusDuration + seriesLength:seriesLength + options:options + outputDirectory:nil]; +} + ++ (ORKOrderedTask *)PSATTaskWithIdentifier:(NSString *)identifier + intendedUseDescription:(nullable NSString *)intendedUseDescription + presentationMode:(ORKPSATPresentationMode)presentationMode + interStimulusInterval:(NSTimeInterval)interStimulusInterval + stimulusDuration:(NSTimeInterval)stimulusDuration + seriesLength:(NSInteger)seriesLength + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL *)outputDirectory { NSMutableArray *steps = [NSMutableArray array]; NSString *versionTitle = @""; NSString *versionDetailText = @""; @@ -2525,7 +2914,8 @@ + (NSMutableArray *)stepsForOneHandTremorTestTaskWithIdentifier:(NSString *)iden leftHand:(BOOL)leftHand handIdentifier:(NSString *)handIdentifier introDetailText:(NSString *)detailText - options:(ORKPredefinedTaskOption)options { + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL *)outputDirectory { NSMutableArray *steps = [NSMutableArray array]; NSString *stepFinishedInstruction = ORKLocalizedString(@"TREMOR_TEST_ACTIVE_STEP_FINISHED_INSTRUCTION", nil); BOOL rightHand = !leftHand && ![handIdentifier isEqualToString:ORKActiveTaskMostAffectedHandIdentifier]; @@ -2591,7 +2981,12 @@ + (NSMutableArray *)stepsForOneHandTremorTestTaskWithIdentifier:(NSString *)iden NSString *titleFormat = ORKLocalizedString(@"TREMOR_TEST_ACTIVE_STEP_IN_LAP_INSTRUCTION_%ld", nil); NSString *stepIdentifier = [self stepIdentifier:ORKTremorTestInLapStepIdentifier withHandIdentifier:handIdentifier]; ORKActiveStep *step = [[ORKActiveStep alloc] initWithIdentifier:stepIdentifier]; - step.recorderConfigurations = @[[[ORKAccelerometerRecorderConfiguration alloc] initWithIdentifier:@"ac1_acc" frequency:100.0], [[ORKDeviceMotionRecorderConfiguration alloc] initWithIdentifier:@"ac1_motion" frequency:100.0]]; + step.recorderConfigurations = @[[[ORKAccelerometerRecorderConfiguration alloc] initWithIdentifier:@"ac1_acc" + frequency:100.0 + outputDirectory:outputDirectory], + [[ORKDeviceMotionRecorderConfiguration alloc] initWithIdentifier:@"ac1_motion" + frequency:100.0 + outputDirectory:outputDirectory]]; step.title = ORKLocalizedString(@"TREMOR_TEST_TITLE", nil); step.text = [NSString localizedStringWithFormat:titleFormat, (long)activeStepDuration]; step.spokenInstruction = step.text; @@ -2643,7 +3038,12 @@ + (NSMutableArray *)stepsForOneHandTremorTestTaskWithIdentifier:(NSString *)iden NSString *titleFormat = ORKLocalizedString(@"TREMOR_TEST_ACTIVE_STEP_EXTEND_ARM_INSTRUCTION_%ld", nil); NSString *stepIdentifier = [self stepIdentifier:ORKTremorTestExtendArmStepIdentifier withHandIdentifier:handIdentifier]; ORKActiveStep *step = [[ORKActiveStep alloc] initWithIdentifier:stepIdentifier]; - step.recorderConfigurations = @[[[ORKAccelerometerRecorderConfiguration alloc] initWithIdentifier:@"ac2_acc" frequency:100.0], [[ORKDeviceMotionRecorderConfiguration alloc] initWithIdentifier:@"ac2_motion" frequency:100.0]]; + step.recorderConfigurations = @[[[ORKAccelerometerRecorderConfiguration alloc] initWithIdentifier:@"ac2_acc" + frequency:100.0 + outputDirectory:outputDirectory], + [[ORKDeviceMotionRecorderConfiguration alloc] initWithIdentifier:@"ac2_motion" + frequency:100.0 + outputDirectory:outputDirectory]]; step.title = ORKLocalizedString(@"TREMOR_TEST_TITLE", nil); step.text = [NSString localizedStringWithFormat:titleFormat, (long)activeStepDuration]; step.spokenInstruction = step.text; @@ -2700,7 +3100,12 @@ + (NSMutableArray *)stepsForOneHandTremorTestTaskWithIdentifier:(NSString *)iden NSString *titleFormat = ORKLocalizedString(@"TREMOR_TEST_ACTIVE_STEP_BEND_ARM_INSTRUCTION_%ld", nil); NSString *stepIdentifier = [self stepIdentifier:ORKTremorTestBendArmStepIdentifier withHandIdentifier:handIdentifier]; ORKActiveStep *step = [[ORKActiveStep alloc] initWithIdentifier:stepIdentifier]; - step.recorderConfigurations = @[[[ORKAccelerometerRecorderConfiguration alloc] initWithIdentifier:@"ac3_acc" frequency:100.0], [[ORKDeviceMotionRecorderConfiguration alloc] initWithIdentifier:@"ac3_motion" frequency:100.0]]; + step.recorderConfigurations = @[[[ORKAccelerometerRecorderConfiguration alloc] initWithIdentifier:@"ac3_acc" + frequency:100.0 + outputDirectory:outputDirectory], + [[ORKDeviceMotionRecorderConfiguration alloc] initWithIdentifier:@"ac3_motion" + frequency:100.0 + outputDirectory:outputDirectory]]; step.title = ORKLocalizedString(@"TREMOR_TEST_TITLE", nil); step.text = [NSString localizedStringWithFormat:titleFormat, (long)activeStepDuration]; step.spokenInstruction = step.text; @@ -2751,7 +3156,12 @@ + (NSMutableArray *)stepsForOneHandTremorTestTaskWithIdentifier:(NSString *)iden NSString *titleFormat = ORKLocalizedString(@"TREMOR_TEST_ACTIVE_STEP_TOUCH_NOSE_INSTRUCTION_%ld", nil); NSString *stepIdentifier = [self stepIdentifier:ORKTremorTestTouchNoseStepIdentifier withHandIdentifier:handIdentifier]; ORKActiveStep *step = [[ORKActiveStep alloc] initWithIdentifier:stepIdentifier]; - step.recorderConfigurations = @[[[ORKAccelerometerRecorderConfiguration alloc] initWithIdentifier:@"ac4_acc" frequency:100.0], [[ORKDeviceMotionRecorderConfiguration alloc] initWithIdentifier:@"ac4_motion" frequency:100.0]]; + step.recorderConfigurations = @[[[ORKAccelerometerRecorderConfiguration alloc] initWithIdentifier:@"ac4_acc" + frequency:100.0 + outputDirectory:outputDirectory], + [[ORKDeviceMotionRecorderConfiguration alloc] initWithIdentifier:@"ac4_motion" + frequency:100.0 + outputDirectory:outputDirectory]]; step.title = ORKLocalizedString(@"TREMOR_TEST_TITLE", nil); step.text = [NSString localizedStringWithFormat:titleFormat, (long)activeStepDuration]; step.spokenInstruction = step.text; @@ -2800,7 +3210,12 @@ + (NSMutableArray *)stepsForOneHandTremorTestTaskWithIdentifier:(NSString *)iden NSString *titleFormat = ORKLocalizedString(@"TREMOR_TEST_ACTIVE_STEP_TURN_WRIST_INSTRUCTION_%ld", nil); NSString *stepIdentifier = [self stepIdentifier:ORKTremorTestTurnWristStepIdentifier withHandIdentifier:handIdentifier]; ORKActiveStep *step = [[ORKActiveStep alloc] initWithIdentifier:stepIdentifier]; - step.recorderConfigurations = @[[[ORKAccelerometerRecorderConfiguration alloc] initWithIdentifier:@"ac5_acc" frequency:100.0], [[ORKDeviceMotionRecorderConfiguration alloc] initWithIdentifier:@"ac5_motion" frequency:100.0]]; + step.recorderConfigurations = @[[[ORKAccelerometerRecorderConfiguration alloc] initWithIdentifier:@"ac5_acc" + frequency:100.0 + outputDirectory:outputDirectory], + [[ORKDeviceMotionRecorderConfiguration alloc] initWithIdentifier:@"ac5_motion" + frequency:100.0 + outputDirectory:outputDirectory]]; step.title = ORKLocalizedString(@"TREMOR_TEST_TITLE", nil); step.text = [NSString localizedStringWithFormat:titleFormat, (long)activeStepDuration]; step.spokenInstruction = step.text; @@ -2836,7 +3251,22 @@ + (ORKNavigableOrderedTask *)tremorTestTaskWithIdentifier:(NSString *)identifier activeTaskOptions:(ORKTremorActiveTaskOption)activeTaskOptions handOptions:(ORKPredefinedTaskHandOption)handOptions options:(ORKPredefinedTaskOption)options { - + return [ORKOrderedTask tremorTestTaskWithIdentifier:identifier + intendedUseDescription:intendedUseDescription + activeStepDuration:activeTaskOptions + activeTaskOptions:activeTaskOptions + handOptions:handOptions + options:options + outputDirectory:nil]; +} + ++ (ORKNavigableOrderedTask *)tremorTestTaskWithIdentifier:(NSString *)identifier + intendedUseDescription:(nullable NSString *)intendedUseDescription + activeStepDuration:(NSTimeInterval)activeStepDuration + activeTaskOptions:(ORKTremorActiveTaskOption)activeTaskOptions + handOptions:(ORKPredefinedTaskHandOption)handOptions + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL *)outputDirectory { NSMutableArray<__kindof ORKStep *> *steps = [NSMutableArray array]; // coin toss for which hand first (in case we're doing both) BOOL leftFirstIfDoingBoth = arc4random_uniform(2) == 1; @@ -2912,7 +3342,8 @@ + (ORKNavigableOrderedTask *)tremorTestTaskWithIdentifier:(NSString *)identifier leftHand:NO handIdentifier:ORKActiveTaskMostAffectedHandIdentifier introDetailText:detailText - options:options]; + options:options + outputDirectory:outputDirectory]; } else if (handOptions & ORKPredefinedTaskHandOptionRight) { rightSteps = [self stepsForOneHandTremorTestTaskWithIdentifier:identifier activeStepDuration:activeStepDuration @@ -2921,7 +3352,8 @@ + (ORKNavigableOrderedTask *)tremorTestTaskWithIdentifier:(NSString *)identifier leftHand:NO handIdentifier:ORKActiveTaskRightHandIdentifier introDetailText:nil - options:options]; + options:options + outputDirectory:outputDirectory]; } // left hand @@ -2934,7 +3366,8 @@ + (ORKNavigableOrderedTask *)tremorTestTaskWithIdentifier:(NSString *)identifier leftHand:YES handIdentifier:ORKActiveTaskLeftHandIdentifier introDetailText:nil - options:options]; + options:options + outputDirectory:outputDirectory]; } if (firstIsLeft && leftSteps != nil) { @@ -3002,6 +3435,20 @@ + (ORKOrderedTask *)trailmakingTaskWithIdentifier:(NSString *)identifier trailmakingInstruction:(nullable NSString *)trailmakingInstruction trailType:(ORKTrailMakingTypeIdentifier)trailType options:(ORKPredefinedTaskOption)options { + return [ORKOrderedTask trailmakingTaskWithIdentifier:identifier + intendedUseDescription:intendedUseDescription + trailmakingInstruction:trailmakingInstruction + trailType:trailType + options:options + outputDirectory:nil]; +} + ++ (ORKOrderedTask *)trailmakingTaskWithIdentifier:(NSString *)identifier + intendedUseDescription:(nullable NSString *)intendedUseDescription + trailmakingInstruction:(nullable NSString *)trailmakingInstruction + trailType:(ORKTrailMakingTypeIdentifier)trailType + options:(ORKPredefinedTaskOption)options + outputDirectory:(nullable NSURL *)outputDirectory { NSMutableArray<__kindof ORKStep *> *steps = [NSMutableArray array]; if (!(options & ORKPredefinedTaskOptionExcludeInstructions)) { diff --git a/ResearchKitActiveTask/Common/Recorders/Accelerometer/CMAccelerometerData+ORKJSONDictionary.m b/ResearchKitActiveTask/Common/Recorders/Accelerometer/CMAccelerometerData+ORKJSONDictionary.m index 210dee2e28..14998b99f0 100644 --- a/ResearchKitActiveTask/Common/Recorders/Accelerometer/CMAccelerometerData+ORKJSONDictionary.m +++ b/ResearchKitActiveTask/Common/Recorders/Accelerometer/CMAccelerometerData+ORKJSONDictionary.m @@ -30,12 +30,14 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE #import "CMAccelerometerData+ORKJSONDictionary.h" - +#import "ResearchKitActiveTask/ResearchKitActiveTask-Swift.h" @implementation CMAccelerometerData (ORKJSONDictionary) - (NSDictionary *)ork_JSONDictionary { - NSDictionary *dictionary = @{ @"timestamp": [NSDecimalNumber numberWithDouble:self.timestamp], + NSDictionary *dictionary = @{ + @"timestamp": [NSDecimalNumber numberWithDouble:self.timestamp], + @"timestampSince1970": [NSDecimalNumber numberWithDouble:self.timestampSince1970], @"x": [NSDecimalNumber numberWithDouble:self.acceleration.x], @"y": [NSDecimalNumber numberWithDouble:self.acceleration.y], @"z": [NSDecimalNumber numberWithDouble:self.acceleration.z] diff --git a/ResearchKitActiveTask/Common/Recorders/Accelerometer/ORKAccelerometerRecorder.h b/ResearchKitActiveTask/Common/Recorders/Accelerometer/ORKAccelerometerRecorder.h index 296dfff5a9..3b84c449fc 100644 --- a/ResearchKitActiveTask/Common/Recorders/Accelerometer/ORKAccelerometerRecorder.h +++ b/ResearchKitActiveTask/Common/Recorders/Accelerometer/ORKAccelerometerRecorder.h @@ -64,6 +64,23 @@ ORK_CLASS_AVAILABLE step:(nullable ORKStep *)step outputDirectory:(nullable NSURL *)outputDirectory; +/** + Returns an initialized accelerometer recorder using the specified frequency. + + @param identifier The unique identifier of the recorder (assigned by the recorder configuration). + @param frequency The frequency of accelerometer data collected from CoreMotion, in hertz (Hz). + @param step The step that requested this recorder. + @param outputDirectory The directory in which the accelerometer data should be stored. + @param rollingFileSizeThreshold The file-size threshold in bytes used to determine when data is rolled over to multiple files as data is being written. If the value is 0, data is written to only one file and not rolled over to multiple files. + + @return An initialized accelerometer recorder. + */ +- (instancetype)initWithIdentifier:(NSString *)identifier + frequency:(double)frequency + step:(nullable ORKStep *)step + outputDirectory:(nullable NSURL *)outputDirectory + rollingFileSizeThreshold:(size_t)rollingFileSizeThreshold NS_DESIGNATED_INITIALIZER; + @end NS_ASSUME_NONNULL_END diff --git a/ResearchKitActiveTask/Common/Recorders/Accelerometer/ORKAccelerometerRecorder.m b/ResearchKitActiveTask/Common/Recorders/Accelerometer/ORKAccelerometerRecorder.m index 9ef94482ab..ca78bce235 100644 --- a/ResearchKitActiveTask/Common/Recorders/Accelerometer/ORKAccelerometerRecorder.m +++ b/ResearchKitActiveTask/Common/Recorders/Accelerometer/ORKAccelerometerRecorder.m @@ -56,7 +56,15 @@ @interface ORKAccelerometerRecorder () { @implementation ORKAccelerometerRecorder - (instancetype)initWithIdentifier:(NSString *)identifier frequency:(double)frequency step:(ORKStep *)step outputDirectory:(NSURL *)outputDirectory { - self = [super initWithIdentifier:identifier step:step outputDirectory:outputDirectory]; + return [self initWithIdentifier:identifier frequency:frequency step:step outputDirectory:outputDirectory rollingFileSizeThreshold:0]; +} + +- (instancetype)initWithIdentifier:(NSString *)identifier + frequency:(double)frequency + step:(ORKStep *)step + outputDirectory:(NSURL *)outputDirectory + rollingFileSizeThreshold:(size_t)rollingFileSizeThreshold { + self = [super initWithIdentifier:identifier step:step outputDirectory:outputDirectory rollingFileSizeThreshold:rollingFileSizeThreshold]; if (self) { self.frequency = frequency; self.continuesInBackground = YES; @@ -136,12 +144,13 @@ - (void)stop { NSError *error = _recordingError; _recordingError = nil; - __block NSURL *fileUrl = nil; + __block NSMutableArray *fileUrls = [[NSMutableArray alloc] init]; [_logger enumerateLogs:^(NSURL *logFileUrl, BOOL *stop) { - fileUrl = logFileUrl; - } error:&error]; + [fileUrls addObject:logFileUrl]; + } + error:&error]; - [self reportFileResultWithFile:fileUrl error:error]; + [self reportFileResultsWithFiles:fileUrls error:error]; [super stop]; } @@ -179,12 +188,27 @@ @implementation ORKAccelerometerRecorderConfiguration #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wobjc-designated-initializers" -- (instancetype)initWithIdentifier:(NSString *)identifier { - @throw [NSException exceptionWithName:NSGenericException reason:@"Use subclass designated initializer" userInfo:nil]; +- (instancetype)initWithIdentifier:(NSString *)identifier frequency:(double)frequency { + return [self initWithIdentifier:identifier + frequency:frequency + outputDirectory:nil + rollingFileSizeThreshold:0]; } -- (instancetype)initWithIdentifier:(NSString *)identifier frequency:(double)frequency { - self = [super initWithIdentifier:identifier]; +- (instancetype)initWithIdentifier:(NSString *)identifier + frequency:(double)frequency + outputDirectory:(nullable NSURL *)outputDirectory { + return [self initWithIdentifier:identifier + frequency:frequency + outputDirectory:outputDirectory + rollingFileSizeThreshold:0]; +} + +- (instancetype)initWithIdentifier:(NSString *)identifier + frequency:(double)frequency + outputDirectory:(nullable NSURL *)outputDirectory + rollingFileSizeThreshold:(size_t)rollingFileSizeThreshold { + self = [super initWithIdentifier:identifier outputDirectory:outputDirectory rollingFileSizeThreshold:rollingFileSizeThreshold]; if (self) { _frequency = frequency; } @@ -192,11 +216,12 @@ - (instancetype)initWithIdentifier:(NSString *)identifier frequency:(double)freq } #pragma clang diagnostic pop -- (ORKRecorder *)recorderForStep:(ORKStep *)step outputDirectory:(NSURL *)outputDirectory { +- (ORKRecorder *)recorderForStep:(ORKStep *)step { return [[ORKAccelerometerRecorder alloc] initWithIdentifier:self.identifier frequency:self.frequency step:step - outputDirectory:outputDirectory]; + outputDirectory:self.outputDirectory + rollingFileSizeThreshold:self.rollingFileSizeThreshold]; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { diff --git a/ResearchKitActiveTask/Common/Recorders/Audio/ORKAudioRecorder.h b/ResearchKitActiveTask/Common/Recorders/Audio/ORKAudioRecorder.h index b98dd2abd0..ea260e13f8 100644 --- a/ResearchKitActiveTask/Common/Recorders/Audio/ORKAudioRecorder.h +++ b/ResearchKitActiveTask/Common/Recorders/Audio/ORKAudioRecorder.h @@ -76,7 +76,24 @@ ORK_CLASS_AVAILABLE - (instancetype)initWithIdentifier:(NSString *)identifier recorderSettings:(nullable NSDictionary *)recorderSettings step:(nullable ORKStep *)step - outputDirectory:(nullable NSURL *)outputDirectory NS_DESIGNATED_INITIALIZER; + outputDirectory:(nullable NSURL *)outputDirectory; + +/** + Returns an initialized audio recorder using the specified settings, step, and output directory. + + @param identifier The unique identifier of the recorder (assigned by the recorder configuration). + @param recorderSettings The settings for the recording session. + @param step The step that requested this recorder. + @param outputDirectory The directory in which the audio output should be stored. + @param rollingFileSizeThreshold The file-size threshold in bytes used to determine when data is rolled over to multiple files as data is being written. If the value is 0, data is written to only one file and not rolled over to multiple files. + + @return An initialized audio recorder. + */ +- (instancetype)initWithIdentifier:(NSString *)identifier + recorderSettings:(nullable NSDictionary *)recorderSettings + step:(nullable ORKStep *)step + outputDirectory:(nullable NSURL *)outputDirectory + rollingFileSizeThreshold:(size_t)rollingFileSizeThreshold NS_DESIGNATED_INITIALIZER; /** Reference to the audio recorder being used. diff --git a/ResearchKitActiveTask/Common/Recorders/Audio/ORKAudioRecorder.m b/ResearchKitActiveTask/Common/Recorders/Audio/ORKAudioRecorder.m index 23c5662767..d843f4d59d 100644 --- a/ResearchKitActiveTask/Common/Recorders/Audio/ORKAudioRecorder.m +++ b/ResearchKitActiveTask/Common/Recorders/Audio/ORKAudioRecorder.m @@ -35,6 +35,8 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE #import "ORKHelpers_Internal.h" +#import "ResearchKit/ResearchKit-Swift.h" + @interface ORKAudioRecorder () @@ -62,11 +64,28 @@ + (NSDictionary *)defaultRecorderSettings { AVSampleRateKey : @(44100.0)}; } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wobjc-designated-initializers" +- (instancetype)initWithIdentifier:(NSString *)identifier + step:(nullable ORKStep *)step + outputDirectory:(nullable NSURL *)outputDirectory + rollingFileSizeThreshold:(size_t)rollingFileSizeThreshold { + @throw [NSException exceptionWithName:NSGenericException reason:@"Use subclass designated initializer" userInfo:nil]; +} + - (instancetype)initWithIdentifier:(NSString *)identifier recorderSettings:(NSDictionary *)recorderSettings step:(ORKStep *)step outputDirectory:(NSURL *)outputDirectory { - self = [super initWithIdentifier:identifier step:step outputDirectory:outputDirectory]; + return [self initWithIdentifier:identifier recorderSettings:recorderSettings step:step outputDirectory:outputDirectory rollingFileSizeThreshold:0]; +} + +- (instancetype)initWithIdentifier:(NSString *)identifier + recorderSettings:(NSDictionary *)recorderSettings + step:(ORKStep *)step + outputDirectory:(NSURL *)outputDirectory + rollingFileSizeThreshold:(size_t)sizeThreshold { + self = [super initWithIdentifier:identifier step:step outputDirectory:outputDirectory rollingFileSizeThreshold:sizeThreshold]; if (self) { self.continuesInBackground = YES; @@ -80,6 +99,7 @@ - (instancetype)initWithIdentifier:(NSString *)identifier } return self; } +#pragma clang diagnostic pop - (void)restoreSavedAudioSessionCategory { if (_savedSessionCategory) { @@ -153,7 +173,7 @@ - (void)stop { fileUrl = nil; } - [self reportFileResultWithFile:fileUrl error:nil]; + [self reportFileResultsWithFiles:@[fileUrl] error:nil]; [super stop]; } @@ -284,13 +304,30 @@ @implementation ORKAudioRecorderConfiguration #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wobjc-designated-initializers" -- (instancetype)initWithIdentifier:(NSString *)identifier { - @throw [NSException exceptionWithName:NSGenericException reason:@"Use subclass designated initializer" userInfo:nil]; +- (instancetype)initWithIdentifier:(NSString *)identifier + outputDirectory:(nullable NSURL *)outputDirectory + rollingFileSizeThreshold:(size_t)rollingFileSizeThreshold { + return [super initWithIdentifier:identifier outputDirectory:outputDirectory rollingFileSizeThreshold:rollingFileSizeThreshold]; +} + +- (instancetype)initWithIdentifier:(NSString *)identifier recorderSettings:(NSDictionary *)recorderSettings { + return [self initWithIdentifier:identifier recorderSettings:recorderSettings outputDirectory:nil rollingFileSizeThreshold:0]; } - (instancetype)initWithIdentifier:(NSString *)identifier - recorderSettings:(NSDictionary *)recorderSettings { - self = [super initWithIdentifier:identifier]; + recorderSettings:(NSDictionary *)recorderSettings + outputDirectory:(nullable NSURL *)outputDirectory { + return [self initWithIdentifier:identifier + recorderSettings:recorderSettings + outputDirectory:outputDirectory + rollingFileSizeThreshold:0]; +} + +- (instancetype)initWithIdentifier:(NSString *)identifier + recorderSettings:(NSDictionary *)recorderSettings + outputDirectory:(nullable NSURL *)outputDirectory + rollingFileSizeThreshold:(size_t)rollingFileSizeThreshold { + self = [super initWithIdentifier:identifier outputDirectory:outputDirectory rollingFileSizeThreshold:rollingFileSizeThreshold]; if (self) { if (recorderSettings && ![recorderSettings isKindOfClass:[NSDictionary class]]) { @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"recorderSettings should be a dictionary" userInfo:recorderSettings]; @@ -301,12 +338,12 @@ - (instancetype)initWithIdentifier:(NSString *)identifier } #pragma clang diagnostic pop -- (ORKRecorder *)recorderForStep:(ORKStep *)step - outputDirectory:(NSURL *)outputDirectory { +- (ORKRecorder *)recorderForStep:(ORKStep *)step { return [[ORKAudioRecorder alloc] initWithIdentifier:self.identifier recorderSettings:self.recorderSettings step:step - outputDirectory:outputDirectory]; + outputDirectory:self.outputDirectory + rollingFileSizeThreshold:self.rollingFileSizeThreshold]; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { diff --git a/ResearchKitActiveTask/Common/Recorders/Audio/ORKAudioStreamer.m b/ResearchKitActiveTask/Common/Recorders/Audio/ORKAudioStreamer.m index 1028d2bbc5..1c2f82b100 100644 --- a/ResearchKitActiveTask/Common/Recorders/Audio/ORKAudioStreamer.m +++ b/ResearchKitActiveTask/Common/Recorders/Audio/ORKAudioStreamer.m @@ -37,8 +37,20 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE @implementation ORKAudioStreamerConfiguration -- (instancetype)initWithIdentifier:(NSString *)identifier { - self = [super initWithIdentifier:identifier]; +- (instancetype) initWithIdentifier:(NSString *)identifier { + return [self initWithIdentifier:identifier outputDirectory:nil rollingFileSizeThreshold:0]; +} + +- (instancetype) initWithIdentifier:(NSString *)identifier outputDirectory:(nullable NSURL *)outputDirectory { + return [self initWithIdentifier:identifier outputDirectory:outputDirectory rollingFileSizeThreshold:0]; +} + +- (instancetype)initWithIdentifier:(NSString *)identifier + outputDirectory:(nullable NSURL *)outputDirectory + rollingFileSizeThreshold:(size_t)rollingFileSizeThreshold { + self = [super initWithIdentifier:identifier + outputDirectory:outputDirectory + rollingFileSizeThreshold:rollingFileSizeThreshold]; if (self != nil) { _bypassAudioEngineStart = NO; @@ -47,12 +59,15 @@ - (instancetype)initWithIdentifier:(NSString *)identifier { return self; } -- (ORKRecorder *)recorderForStep:(ORKStep *)step outputDirectory:(NSURL *)outputDirectory { +- (ORKRecorder *)recorderForStep:(ORKStep *)step { if (_bypassAudioEngineStart) { return nil; } - ORKAudioStreamer *obj = [[ORKAudioStreamer alloc] initWithIdentifier:self.identifier step:step]; + ORKAudioStreamer *obj = [[ORKAudioStreamer alloc] initWithIdentifier:self.identifier + step:step + outputDirectory:self.outputDirectory + rollingFileSizeThreshold:self.rollingFileSizeThreshold]; return obj; } @@ -92,7 +107,7 @@ @implementation ORKAudioStreamer - (instancetype)initWithIdentifier:(NSString *)identifier step:(ORKStep *)step { - self = [super initWithIdentifier:identifier step:step outputDirectory:nil]; + self = [super initWithIdentifier:identifier step:step]; if (self) { self.continuesInBackground = YES; diff --git a/ResearchKitActiveTask/Common/Recorders/Audio/ORKStreamingAudioRecorder.h b/ResearchKitActiveTask/Common/Recorders/Audio/ORKStreamingAudioRecorder.h index abeb66a042..bad922ed35 100644 --- a/ResearchKitActiveTask/Common/Recorders/Audio/ORKStreamingAudioRecorder.h +++ b/ResearchKitActiveTask/Common/Recorders/Audio/ORKStreamingAudioRecorder.h @@ -62,7 +62,22 @@ ORK_CLASS_AVAILABLE */ - (instancetype)initWithIdentifier:(NSString *)identifier step:(nullable ORKStep *)step - outputDirectory:(nullable NSURL *)outputDirectory NS_DESIGNATED_INITIALIZER; + outputDirectory:(nullable NSURL *)outputDirectory; + +/** + Returns an initialized audio recorder using the specified step, and output directory. + + @param identifier The unique identifier of the recorder (assigned by the recorder configuration). + @param step The step that requested this recorder. + @param outputDirectory The directory in which the audio output should be stored. + @param rollingFileSizeThreshold The file-size threshold in bytes used to determine when data is rolled over to multiple files as data is being written. If the value is 0, data is written to only one file and not rolled over to multiple files. + + @return An initialized audio recorder. + */ +- (instancetype)initWithIdentifier:(NSString *)identifier + step:(nullable ORKStep *)step + outputDirectory:(nullable NSURL *)outputDirectory + rollingFileSizeThreshold:(size_t)rollingFileSizeThreshold NS_DESIGNATED_INITIALIZER; /** Reference to the audio recorder being used. diff --git a/ResearchKitActiveTask/Common/Recorders/Audio/ORKStreamingAudioRecorder.m b/ResearchKitActiveTask/Common/Recorders/Audio/ORKStreamingAudioRecorder.m index 53b046c31d..2bf7b7e444 100644 --- a/ResearchKitActiveTask/Common/Recorders/Audio/ORKStreamingAudioRecorder.m +++ b/ResearchKitActiveTask/Common/Recorders/Audio/ORKStreamingAudioRecorder.m @@ -35,6 +35,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE #import "ORKHelpers_Internal.h" +#import "ResearchKit/ResearchKit-Swift.h" @interface ORKStreamingAudioRecorder () @@ -57,7 +58,14 @@ - (void)dealloc { - (instancetype)initWithIdentifier:(NSString *)identifier step:(ORKStep *)step outputDirectory:(NSURL *)outputDirectory { - self = [super initWithIdentifier:identifier step:step outputDirectory:outputDirectory]; + return [self initWithIdentifier:identifier step:step outputDirectory:outputDirectory rollingFileSizeThreshold:0]; +} + +- (instancetype)initWithIdentifier:(NSString *)identifier + step:(ORKStep *)step + outputDirectory:(NSURL *)outputDirectory + rollingFileSizeThreshold:(size_t)rollingFileSizeThreshold { + self = [super initWithIdentifier:identifier step:step outputDirectory:outputDirectory rollingFileSizeThreshold:rollingFileSizeThreshold]; if (self) { self.continuesInBackground = YES; @@ -195,7 +203,7 @@ - (void)stop { if (![[NSFileManager defaultManager] fileExistsAtPath:[[self recordingFileURL] path]]) { fileUrl = nil; } - [self reportFileResultWithFile:fileUrl error:nil]; + [self reportFileResultsWithFiles:@[fileUrl] error:nil]; [super stop]; } @@ -244,19 +252,27 @@ @implementation ORKStreamingAudioRecorderConfiguration #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wobjc-designated-initializers" - - (instancetype)initWithIdentifier:(NSString *)identifier { - self = [super initWithIdentifier:identifier]; - - return self; + return [self initWithIdentifier:identifier outputDirectory:nil rollingFileSizeThreshold:0]; +} + +- (instancetype)initWithIdentifier:(NSString *)identifier + outputDirectory:(nullable NSURL *)outputDirectory { + return [super initWithIdentifier:identifier outputDirectory:outputDirectory rollingFileSizeThreshold:0]; +} + +- (instancetype)initWithIdentifier:(NSString *)identifier + outputDirectory:(nullable NSURL *)outputDirectory + rollingFileSizeThreshold:(size_t)rollingFileSizeThreshold { + return [super initWithIdentifier:identifier outputDirectory:outputDirectory rollingFileSizeThreshold:rollingFileSizeThreshold]; } #pragma clang diagnostic pop -- (ORKRecorder *)recorderForStep:(ORKStep *)step - outputDirectory:(NSURL *)outputDirectory { +- (ORKRecorder *)recorderForStep:(ORKStep *)step { ORKStreamingAudioRecorder *obj = [[ORKStreamingAudioRecorder alloc] initWithIdentifier:self.identifier step:step - outputDirectory:outputDirectory]; + outputDirectory:self.outputDirectory + rollingFileSizeThreshold:self.rollingFileSizeThreshold]; return obj; } diff --git a/ResearchKitActiveTask/Common/Recorders/CMLogItem+TimestampSince1970.swift b/ResearchKitActiveTask/Common/Recorders/CMLogItem+TimestampSince1970.swift new file mode 100644 index 0000000000..74d10318ba --- /dev/null +++ b/ResearchKitActiveTask/Common/Recorders/CMLogItem+TimestampSince1970.swift @@ -0,0 +1,78 @@ +/* + Copyright (c) 2025, 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 CoreMotion + +// MARK: - Public + +public extension CMLogItem { + /// The timestamp as a time interval in seconds since 1970, calculated from `timestamp` with the system + /// uptime as reference. + /// + /// - Important: This API has the potential of being misused to access device signals to try to identify + /// the device or user, also known as fingerprinting. Regardless of whether a user gives your app + /// permission to track,fingerprinting is not allowed. When you use this API in your app or third-party + /// SDK (an SDK not provided by Apple), declare your usage and the reason for using the API in your app + /// or third-party SDK’s PrivacyInfo.xcprivacy file. For more information, including the list of valid + /// reasons for using the API, see [Describing use of required reason API](https://developer.apple.com/documentation/BundleResources/describing-use-of-required-reason-api). + /// + /// - SeeAlso: `timeInterval` + /// - SeeAlso: `ProcessInfo.processInfo.systemUptime` + @objc + var timestampSince1970: TimeInterval { + timestampSince1970() + } +} + +// MARK: - Internal + +extension CMLogItem { + /// Calculates `timestamp` as a time interval in seconds since 1970, using `referenceUptime` if + /// provided, or the system uptime otherwise. + /// + /// - Parameter referenceUptime: The amount of time the system has been awake since the last time it was + /// restarted. If not provided, uses the system uptime. **This value should always be positive.** + /// + /// - Returns the timestamp as a time interval since 1970. + /// + /// - Important: the `referenceUptime` parameter is a duration. It should always be positive. If deriving + /// it from a `Date` using `timeIntervalFromNow`, the returned time interval will be negative for a date + /// in the past. In that case, make sure to pass the absolute value. + /// + /// - SeeAlso: `timeInterval` + /// - SeeAlso: `ProcessInfo.processInfo.systemUptime` + func timestampSince1970( + using referenceUptime: TimeInterval = ProcessInfo.processInfo.systemUptime + ) -> TimeInterval { + let bootDateFromReferenceUptime = Date(timeIntervalSinceNow: -referenceUptime) + let timeStampDate = bootDateFromReferenceUptime.addingTimeInterval(timestamp) + return timeStampDate.timeIntervalSince1970 + } +} diff --git a/ResearchKitActiveTask/Common/Recorders/Device Motion/CMDeviceMotion+ORKJSONDictionary.m b/ResearchKitActiveTask/Common/Recorders/Device Motion/CMDeviceMotion+ORKJSONDictionary.m index b4300d5871..b5ac254864 100644 --- a/ResearchKitActiveTask/Common/Recorders/Device Motion/CMDeviceMotion+ORKJSONDictionary.m +++ b/ResearchKitActiveTask/Common/Recorders/Device Motion/CMDeviceMotion+ORKJSONDictionary.m @@ -30,7 +30,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE #import "CMDeviceMotion+ORKJSONDictionary.h" - +#import "ResearchKitActiveTask/ResearchKitActiveTask-Swift.h" @implementation CMDeviceMotion (ORKJSONDictionary) @@ -41,7 +41,9 @@ - (NSDictionary *)ork_JSONDictionary { CMAcceleration userAccel = self.userAcceleration; CMCalibratedMagneticField field = self.magneticField; - NSDictionary *dictionary = @{@"timestamp": [NSDecimalNumber numberWithDouble:self.timestamp], + NSDictionary *dictionary = @{ + @"timestamp": [NSDecimalNumber numberWithDouble:self.timestamp], + @"timestampSince1970": [NSDecimalNumber numberWithDouble:self.timestampSince1970], @"attitude": @{ @"x": [NSDecimalNumber numberWithDouble:attitude.x], @"y": [NSDecimalNumber numberWithDouble:attitude.y], diff --git a/ResearchKitActiveTask/Common/Recorders/Device Motion/ORKDeviceMotionRecorder.h b/ResearchKitActiveTask/Common/Recorders/Device Motion/ORKDeviceMotionRecorder.h index e4d5be961e..959c8fecb5 100644 --- a/ResearchKitActiveTask/Common/Recorders/Device Motion/ORKDeviceMotionRecorder.h +++ b/ResearchKitActiveTask/Common/Recorders/Device Motion/ORKDeviceMotionRecorder.h @@ -65,14 +65,30 @@ ORK_CLASS_AVAILABLE @param identifier The unique identifier of the recorder (assigned by the recorder configuration). @param frequency The frequency of motion data collection from CoreMotion in hertz (Hz). @param step The step that requested this recorder. - @param outputDirectory The directory in which the device motion data should be stored. + + @return An initialized motion data recorder. + */ +- (instancetype)initWithIdentifier:(NSString *)identifier + frequency:(double)frequency + step:(nullable ORKStep *)step; + +/** + Returns an initialized device motion recorder using the specified frequency. + + @param identifier The unique identifier of the recorder (assigned by the recorder configuration). + @param frequency The frequency of motion data collection from CoreMotion in hertz (Hz). + @param step The step that requested this recorder. + @param outputDirectory The url to the directory in which all output file data should be written (if producing `ORKFileResult` instances). + @param rollingFileSizeThreshold The file-size threshold in bytes used to determine when data is rolled over to multiple files as data is being written. + If the value is 0, data is written to only one file and not rolled over to multiple files. @return An initialized motion data recorder. */ - (instancetype)initWithIdentifier:(NSString *)identifier frequency:(double)frequency step:(nullable ORKStep *)step - outputDirectory:(nullable NSURL *)outputDirectory NS_DESIGNATED_INITIALIZER; + outputDirectory:(nullable NSURL *)outputDirectory + rollingFileSizeThreshold:(size_t)rollingFileSizeThreshold NS_DESIGNATED_INITIALIZER; @end diff --git a/ResearchKitActiveTask/Common/Recorders/Device Motion/ORKDeviceMotionRecorder.m b/ResearchKitActiveTask/Common/Recorders/Device Motion/ORKDeviceMotionRecorder.m index 7e5c434d47..0b7d834514 100644 --- a/ResearchKitActiveTask/Common/Recorders/Device Motion/ORKDeviceMotionRecorder.m +++ b/ResearchKitActiveTask/Common/Recorders/Device Motion/ORKDeviceMotionRecorder.m @@ -54,13 +54,25 @@ @interface ORKDeviceMotionRecorder () { @implementation ORKDeviceMotionRecorder +- (instancetype)initWithIdentifier:(NSString *)identifier + frequency:(double)frequency + step:(ORKStep *)step { + return [self initWithIdentifier:identifier + frequency:frequency + step:step + outputDirectory:nil + rollingFileSizeThreshold:0]; +} + - (instancetype)initWithIdentifier:(NSString *)identifier frequency:(double)frequency step:(ORKStep *)step - outputDirectory:(NSURL *)outputDirectory { + outputDirectory:(NSURL *)outputDirectory + rollingFileSizeThreshold:(size_t)rollingFileSizeThreshold { self = [super initWithIdentifier:identifier step:step - outputDirectory:outputDirectory]; + outputDirectory:outputDirectory + rollingFileSizeThreshold:rollingFileSizeThreshold]; if (self) { self.frequency = frequency; self.continuesInBackground = YES; @@ -129,12 +141,13 @@ - (void)stop { [_logger finishCurrentLog]; NSError *error = nil; - __block NSURL *fileUrl = nil; + __block NSMutableArray *fileUrls = [[NSMutableArray alloc] init]; [_logger enumerateLogs:^(NSURL *logFileUrl, BOOL *stop) { - fileUrl = logFileUrl; - } error:&error]; + [fileUrls addObject:logFileUrl]; + } + error:&error]; - [self reportFileResultWithFile:fileUrl error:error]; + [self reportFileResultsWithFiles:fileUrls error:error]; [super stop]; } @@ -172,24 +185,34 @@ @implementation ORKDeviceMotionRecorderConfiguration #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wobjc-designated-initializers" -- (instancetype)initWithIdentifier:(NSString *)identifier { - @throw [NSException exceptionWithName:NSGenericException reason:@"Use subclass designated initializer" userInfo:nil]; +- (instancetype)initWithIdentifier:(NSString *)identifier frequency:(double)freq { + return [self initWithIdentifier:identifier frequency:freq outputDirectory:nil rollingFileSizeThreshold:0]; } -- (instancetype)initWithIdentifier:(NSString *)identifier frequency:(double)freq { - self = [super initWithIdentifier:identifier]; +- (instancetype)initWithIdentifier:(NSString *)identifier + frequency:(double)freq + outputDirectory:(nullable NSURL *)outputDirectory { + return [self initWithIdentifier:identifier + frequency:freq + outputDirectory:outputDirectory + rollingFileSizeThreshold:0]; +} + +- (instancetype)initWithIdentifier:(NSString *)identifier frequency:(double)frequency outputDirectory:(nullable NSURL *)outputDirectory rollingFileSizeThreshold:(size_t)rollingFileSizeThreshold { + self = [super initWithIdentifier:identifier outputDirectory:outputDirectory rollingFileSizeThreshold:rollingFileSizeThreshold]; if (self) { - _frequency = freq; + _frequency = frequency; } return self; } #pragma clang diagnostic pop -- (ORKRecorder *)recorderForStep:(ORKStep *)step outputDirectory:(NSURL *)outputDirectory { +- (ORKRecorder *)recorderForStep:(ORKStep *)step { return [[ORKDeviceMotionRecorder alloc] initWithIdentifier:self.identifier frequency:self.frequency step:step - outputDirectory:outputDirectory]; + outputDirectory:self.outputDirectory + rollingFileSizeThreshold:self.rollingFileSizeThreshold]; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { diff --git a/ResearchKitActiveTask/Common/Recorders/Health/ORKHealthClinicalTypeRecorder.h b/ResearchKitActiveTask/Common/Recorders/Health/ORKHealthClinicalTypeRecorder.h index c3e440ef8b..92a793e39d 100644 --- a/ResearchKitActiveTask/Common/Recorders/Health/ORKHealthClinicalTypeRecorder.h +++ b/ResearchKitActiveTask/Common/Recorders/Health/ORKHealthClinicalTypeRecorder.h @@ -56,7 +56,24 @@ API_AVAILABLE(ios(12.0)) @param healthClinicalType The HKClinicalType data that should be collected during the active task. @param healthFHIRResourceType The HKFHIRResourceType for the predicate used to query the HKClinicalType. @param step The step that requested this recorder. - @param outputDirectory The directory in which the health records data queried from HealthKit should be stored. + + @return An initialized health quantity type recorder. + */ +- (instancetype)initWithIdentifier:(NSString *)identifier + healthClinicalType:(HKClinicalType *)healthClinicalType + healthFHIRResourceType:(nullable HKFHIRResourceType)healthFHIRResourceType + step:(nullable ORKStep *)step; + +/** + Returns an initialized health clinical type recorder using the specified HKClinicalType and HKFHIRResourceType. + + @param identifier The unique identifier of the recorder (assigned by the recorder configuration). + @param healthClinicalType The HKClinicalType data that should be collected during the active task. + @param healthFHIRResourceType The HKFHIRResourceType for the predicate used to query the HKClinicalType. + @param step The step that requested this recorder. + @param outputDirectory The url to the directory in which all output file data should be written (if producing `ORKFileResult` instances). + @param rollingFileSizeThreshold The file-size threshold in bytes used to determine when data is rolled over to multiple files as data is being written. + If the value is 0, data is written to only one file and not rolled over to multiple files. @return An initialized health quantity type recorder. */ @@ -64,7 +81,8 @@ API_AVAILABLE(ios(12.0)) healthClinicalType:(HKClinicalType *)healthClinicalType healthFHIRResourceType:(nullable HKFHIRResourceType)healthFHIRResourceType step:(nullable ORKStep *)step - outputDirectory:(nullable NSURL *)outputDirectory NS_DESIGNATED_INITIALIZER API_AVAILABLE(ios(12.0)); + outputDirectory:(nullable NSURL *)outputDirectory + rollingFileSizeThreshold:(size_t)rollingFileSizeThreshold NS_DESIGNATED_INITIALIZER API_AVAILABLE(ios(12.0)); @end @@ -72,6 +90,20 @@ ORK_CLASS_AVAILABLE API_AVAILABLE(ios(12.0)) @interface ORKHealthClinicalTypeRecorderConfiguration : ORKRecorderConfiguration +/** + Returns an initialized health clinical type recorder configuration using the specified clinical type. + + @param identifier The unique identifier of the recorder configuration. + @param healthClinicalType The HKClinicalType that should be collected during the active task. + @param healthFHIRResourceType The HKFHIRResourceType that should be used as predicate while querying for the healthClinicalType. + Providing a HKFHIRResourceType that does not correspond to a HKClinicalType will NOT generate any result. + + @return An initialized health clinical type recorder configuration. + */ +- (instancetype)initWithIdentifier:(NSString *)identifier + healthClinicalType:(HKClinicalType *)healthClinicalType + healthFHIRResourceType:(nullable HKFHIRResourceType)healthFHIRResourceType API_AVAILABLE(ios(12.0)); + /** Returns an initialized health clinical type recorder configuration using the specified clinical type. @@ -79,13 +111,19 @@ API_AVAILABLE(ios(12.0)) @param identifier The unique identifier of the recorder configuration. @param healthClinicalType The HKClinicalType that should be collected during the active task. - @param healthFHIRResourceType The HKFHIRResourceType that should be used as predicate while querying for the healthClinicalType. Providing a HKFHIRResourceType that does not correspond to a HKClinicalType will NOT generate any result. + @param healthFHIRResourceType The HKFHIRResourceType that should be used as predicate while querying for the healthClinicalType. + Providing a HKFHIRResourceType that does not correspond to a HKClinicalType will NOT generate any result. + @param outputDirectory The url to the directory in which all output file data should be written (if producing `ORKFileResult` instances). + @param rollingFileSizeThreshold The file-size threshold in bytes used to determine when data is rolled over to multiple files as data is being written. + If the value is 0, data is written to only one file and not rolled over to multiple files. @return An initialized health clinical type recorder configuration. */ - (instancetype)initWithIdentifier:(NSString *)identifier healthClinicalType:(HKClinicalType *)healthClinicalType - healthFHIRResourceType:(nullable HKFHIRResourceType)healthFHIRResourceType NS_DESIGNATED_INITIALIZER API_AVAILABLE(ios(12.0)); + healthFHIRResourceType:(nullable HKFHIRResourceType)healthFHIRResourceType + outputDirectory:(nullable NSURL *)outputDirectory + rollingFileSizeThreshold:(size_t)rollingFileSizeThreshold NS_DESIGNATED_INITIALIZER API_AVAILABLE(ios(12.0)); /** Returns a new health clinical type recorder configuration initialized from data in the given unarchiver. diff --git a/ResearchKitActiveTask/Common/Recorders/Health/ORKHealthClinicalTypeRecorder.m b/ResearchKitActiveTask/Common/Recorders/Health/ORKHealthClinicalTypeRecorder.m index 6eb95e3a7d..71287734a7 100644 --- a/ResearchKitActiveTask/Common/Recorders/Health/ORKHealthClinicalTypeRecorder.m +++ b/ResearchKitActiveTask/Common/Recorders/Health/ORKHealthClinicalTypeRecorder.m @@ -54,11 +54,25 @@ @implementation ORKHealthClinicalTypeRecorder - (instancetype)initWithIdentifier:(NSString *)identifier healthClinicalType:(HKClinicalType *)healthClinicalType healthFHIRResourceType:(nullable HKFHIRResourceType)healthFHIRResourceType + step:(ORKStep *)step { + return [self initWithIdentifier:identifier + healthClinicalType:healthClinicalType + healthFHIRResourceType:healthFHIRResourceType + step:step + outputDirectory:nil + rollingFileSizeThreshold:0]; +} + +- (instancetype)initWithIdentifier:(NSString *)identifier + healthClinicalType:(HKClinicalType *)healthClinicalType + healthFHIRResourceType:(HKFHIRResourceType)healthFHIRResourceType step:(ORKStep *)step - outputDirectory:(NSURL *)outputDirectory { + outputDirectory:(nullable NSURL *)outputDirectory + rollingFileSizeThreshold:(size_t)rollingFileSizeThreshold { self = [super initWithIdentifier:identifier step:step - outputDirectory:outputDirectory]; + outputDirectory:outputDirectory + rollingFileSizeThreshold:rollingFileSizeThreshold]; if (self) { NSParameterAssert(healthClinicalType != nil); _healthClinicalType = healthClinicalType; @@ -133,12 +147,13 @@ - (void)stop { [_logger finishCurrentLog]; NSError *error = nil; - __block NSURL *fileUrl = nil; + __block NSMutableArray *fileUrls = [[NSMutableArray alloc] init]; [_logger enumerateLogs:^(NSURL *logFileUrl, BOOL *stop) { - fileUrl = logFileUrl; - } error:&error]; + [fileUrls addObject:logFileUrl]; + } + error:&error]; - [self reportFileResultWithFile:fileUrl error:error]; + [self reportFileResultsWithFiles:fileUrls error:error]; [super stop]; } @@ -182,7 +197,19 @@ - (instancetype)initWithIdentifier:(NSString *)identifier { - (instancetype)initWithIdentifier:(NSString *)identifier healthClinicalType:(HKClinicalType *)healthClinicalType healthFHIRResourceType:(nullable HKFHIRResourceType)healthFHIRResourceType { - self = [super initWithIdentifier:identifier]; + return [self initWithIdentifier:identifier + healthClinicalType:healthClinicalType + healthFHIRResourceType:healthFHIRResourceType + outputDirectory:nil + rollingFileSizeThreshold:0]; +} + +- (instancetype)initWithIdentifier:(NSString *)identifier + healthClinicalType:(HKClinicalType *)healthClinicalType + healthFHIRResourceType:(HKFHIRResourceType)healthFHIRResourceType + outputDirectory:(nullable NSURL *)outputDirectory + rollingFileSizeThreshold:(size_t)rollingFileSizeThreshold { + self = [super initWithIdentifier:identifier outputDirectory:outputDirectory rollingFileSizeThreshold:rollingFileSizeThreshold]; if (self) { NSParameterAssert(healthClinicalType != nil); _healthClinicalType = healthClinicalType; @@ -192,13 +219,13 @@ - (instancetype)initWithIdentifier:(NSString *)identifier } #pragma clang diagnostic pop -- (ORKRecorder *)recorderForStep:(ORKStep *)step - outputDirectory:(NSURL *)outputDirectory { +- (ORKRecorder *)recorderForStep:(ORKStep *)step { return [[ORKHealthClinicalTypeRecorder alloc] initWithIdentifier:self.identifier healthClinicalType:_healthClinicalType healthFHIRResourceType:_healthFHIRResourceType step:step - outputDirectory:outputDirectory]; + outputDirectory:self.outputDirectory + rollingFileSizeThreshold:self.rollingFileSizeThreshold]; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { diff --git a/ResearchKitActiveTask/Common/Recorders/Health/ORKHealthQuantityTypeRecorder.h b/ResearchKitActiveTask/Common/Recorders/Health/ORKHealthQuantityTypeRecorder.h index 0218a3825d..8cad38aff0 100644 --- a/ResearchKitActiveTask/Common/Recorders/Health/ORKHealthQuantityTypeRecorder.h +++ b/ResearchKitActiveTask/Common/Recorders/Health/ORKHealthQuantityTypeRecorder.h @@ -69,7 +69,42 @@ ORK_CLASS_AVAILABLE @param quantityType The quantity type that should be collected during the active task. @param unit The unit for the data that should be collected and serialized. @param step The step that requested this recorder. - @param outputDirectory The directory in which the HealthKit data should be stored. + + @return An initialized health quantity type recorder. +*/ +- (instancetype)initWithIdentifier:(NSString *)identifier + healthQuantityType:(HKQuantityType *)quantityType + unit:(HKUnit *)unit + step:(nullable ORKStep *)step; + +/** + Returns an initialized health quantity type recorder using the specified quantity type and unit. + + @param identifier The unique identifier of the recorder (assigned by the recorder configuration). + @param quantityType The quantity type that should be collected during the active task. + @param unit The unit for the data that should be collected and serialized. + @param step The step that requested this recorder. + @param outputDirectory The url to the directory in which all output file data should be written (if producing `ORKFileResult` instances). + + @return An initialized health quantity type recorder. +*/ +- (instancetype)initWithIdentifier:(NSString *)identifier + healthQuantityType:(HKQuantityType *)quantityType + unit:(HKUnit *)unit + step:(nullable ORKStep *)step + outputDirectory:(nullable NSURL *)outputDirectory; + + +/** + Returns an initialized health quantity type recorder using the specified quantity type and unit. + + @param identifier The unique identifier of the recorder (assigned by the recorder configuration). + @param quantityType The quantity type that should be collected during the active task. + @param unit The unit for the data that should be collected and serialized. + @param step The step that requested this recorder. + @param outputDirectory The url to the directory in which all output file data should be written (if producing `ORKFileResult` instances). + @param rollingFileSizeThreshold The file-size threshold in bytes used to determine when data is rolled over to multiple files as data is being written. + If the value is 0, data is written to only one file and not rolled over to multiple files. @return An initialized health quantity type recorder. */ @@ -77,7 +112,8 @@ ORK_CLASS_AVAILABLE healthQuantityType:(HKQuantityType *)quantityType unit:(HKUnit *)unit step:(nullable ORKStep *)step - outputDirectory:(nullable NSURL *)outputDirectory NS_DESIGNATED_INITIALIZER; + outputDirectory:(nullable NSURL *)outputDirectory + rollingFileSizeThreshold:(size_t)rollingFileSizeThreshold NS_DESIGNATED_INITIALIZER; @end @@ -108,7 +144,44 @@ ORK_CLASS_AVAILABLE @return An initialized health quantity type recorder configuration. */ -- (instancetype)initWithIdentifier:(NSString *)identifier healthQuantityType:(HKQuantityType *)quantityType unit:(HKUnit *)unit NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithIdentifier:(NSString *)identifier healthQuantityType:(HKQuantityType *)quantityType unit:(HKUnit *)unit; + +/** + Returns an initialized health quantity type recorder configuration using the specified quantity type and unit designation. + + This method is the designated initializer. + + @param identifier The unique identifier of the recorder configuration. + @param quantityType The quantity type that should be collected during the active task. + @param unit The unit for the data that should be collected and serialized. + @param outputDirectory The url to the directory in which all output file data should be written (if producing `ORKFileResult` instances). + + @return An initialized health quantity type recorder configuration. + */ +- (instancetype)initWithIdentifier:(NSString *)identifier + healthQuantityType:(HKQuantityType *)quantityType + unit:(HKUnit *)unit + outputDirectory:(nullable NSURL *)outputDirectory; + +/** + Returns an initialized health quantity type recorder configuration using the specified quantity type and unit designation. + + This method is the designated initializer. + + @param identifier The unique identifier of the recorder configuration. + @param quantityType The quantity type that should be collected during the active task. + @param unit The unit for the data that should be collected and serialized. + @param outputDirectory The url to the directory in which all output file data should be written (if producing `ORKFileResult` instances). + @param rollingFileSizeThreshold The file-size threshold in bytes used to determine when data is rolled over to multiple files as data is being written. + If the value is 0, data is written to only one file and not rolled over to multiple files. + + @return An initialized health quantity type recorder configuration. + */ +- (instancetype)initWithIdentifier:(NSString *)identifier + healthQuantityType:(HKQuantityType *)quantityType + unit:(HKUnit *)unit + outputDirectory:(nullable NSURL *)outputDirectory + rollingFileSizeThreshold:(size_t)rollingFileSizeThreshold NS_DESIGNATED_INITIALIZER; /** Returns a new health quantity type recorder configuration initialized from data in the given unarchiver. diff --git a/ResearchKitActiveTask/Common/Recorders/Health/ORKHealthQuantityTypeRecorder.m b/ResearchKitActiveTask/Common/Recorders/Health/ORKHealthQuantityTypeRecorder.m index 1803a0e56f..dd4c1d7697 100644 --- a/ResearchKitActiveTask/Common/Recorders/Health/ORKHealthQuantityTypeRecorder.m +++ b/ResearchKitActiveTask/Common/Recorders/Health/ORKHealthQuantityTypeRecorder.m @@ -69,14 +69,41 @@ - (instancetype)initWithType:(HKSampleType *)type @implementation ORKHealthQuantityTypeRecorder +- (instancetype)initWithIdentifier:(NSString *)identifier + healthQuantityType:(HKQuantityType *)quantityType + unit:(HKUnit *)unit + step:(ORKStep *)step { + return [self initWithIdentifier:identifier + healthQuantityType:quantityType + unit:unit + step:step + outputDirectory:nil + rollingFileSizeThreshold:0]; +} + +- (instancetype)initWithIdentifier:(NSString *)identifier + healthQuantityType:(HKQuantityType *)quantityType + unit:(HKUnit *)unit + step:(ORKStep *)step + outputDirectory:(nullable NSURL *)outputDirectory { + return [self initWithIdentifier:identifier + healthQuantityType:quantityType + unit:unit + step:step + outputDirectory:outputDirectory + rollingFileSizeThreshold:0]; +} + - (instancetype)initWithIdentifier:(NSString *)identifier healthQuantityType:(HKQuantityType *)quantityType unit:(HKUnit *)unit step:(ORKStep *)step - outputDirectory:(NSURL *)outputDirectory { + outputDirectory:(nullable NSURL *)outputDirectory + rollingFileSizeThreshold:(size_t)rollingFileSizeThreshold { self = [super initWithIdentifier:identifier step:step - outputDirectory:outputDirectory]; + outputDirectory:outputDirectory + rollingFileSizeThreshold:rollingFileSizeThreshold]; if (self) { NSParameterAssert(quantityType != nil); NSParameterAssert(unit != nil); @@ -262,12 +289,13 @@ - (void)stop { [_logger finishCurrentLog]; NSError *error = nil; - __block NSURL *fileUrl = nil; + __block NSMutableArray *fileUrls = [[NSMutableArray alloc] init]; [_logger enumerateLogs:^(NSURL *logFileUrl, BOOL *stop) { - fileUrl = logFileUrl; - } error:&error]; + [fileUrls addObject:logFileUrl]; + } + error:&error]; - [self reportFileResultWithFile:fileUrl error:error]; + [self reportFileResultsWithFiles:fileUrls error:error]; [super stop]; } @@ -316,7 +344,26 @@ - (instancetype)initWithIdentifier:(NSString *)identifier { } - (instancetype)initWithIdentifier:(NSString *)identifier healthQuantityType:(HKQuantityType *)quantityType unit:(HKUnit *)unit { - self = [super initWithIdentifier:identifier]; + return [self initWithIdentifier:identifier healthQuantityType:quantityType unit:unit outputDirectory:nil rollingFileSizeThreshold:0]; +} + +- (instancetype)initWithIdentifier:(NSString *)identifier + healthQuantityType:(HKQuantityType *)quantityType + unit:(HKUnit *)unit + outputDirectory:(nullable NSURL *)outputDirectory { + return [self initWithIdentifier:identifier + healthQuantityType:quantityType + unit:unit + outputDirectory:outputDirectory + rollingFileSizeThreshold:0]; +} + +- (instancetype)initWithIdentifier:(NSString *)identifier + healthQuantityType:(HKQuantityType *)quantityType + unit:(HKUnit *)unit + outputDirectory:(nullable NSURL *)outputDirectory + rollingFileSizeThreshold:(size_t)rollingFileSizeThreshold { + self = [super initWithIdentifier:identifier outputDirectory:outputDirectory rollingFileSizeThreshold:rollingFileSizeThreshold]; if (self) { NSParameterAssert(quantityType != nil); NSParameterAssert(unit != nil); @@ -328,12 +375,13 @@ - (instancetype)initWithIdentifier:(NSString *)identifier healthQuantityType:(HK } #pragma clang diagnostic pop -- (ORKRecorder *)recorderForStep:(ORKStep *)step outputDirectory:(NSURL *)outputDirectory { +- (ORKRecorder *)recorderForStep:(ORKStep *)step { return [[ORKHealthQuantityTypeRecorder alloc] initWithIdentifier:self.identifier healthQuantityType:_quantityType unit:_unit step:step - outputDirectory:outputDirectory]; + outputDirectory:self.outputDirectory + rollingFileSizeThreshold:self.rollingFileSizeThreshold]; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { diff --git a/ResearchKitActiveTask/Common/Recorders/Location/ORKLocationRecorder.h b/ResearchKitActiveTask/Common/Recorders/Location/ORKLocationRecorder.h index 00da6a32b5..5cc18a42db 100644 --- a/ResearchKitActiveTask/Common/Recorders/Location/ORKLocationRecorder.h +++ b/ResearchKitActiveTask/Common/Recorders/Location/ORKLocationRecorder.h @@ -52,13 +52,27 @@ ORK_CLASS_AVAILABLE @param identifier The unique identifier of the recorder (assigned by the recorder configuration). @param step The step that requested this recorder. - @param outputDirectory The directory in which the location data should be stored. + + @return An initialized location recorder. +*/ +- (instancetype)initWithIdentifier:(NSString *)identifier + step:(nullable ORKStep *)step; + +/** + Returns an initialized location recorder. + + @param identifier The unique identifier of the recorder (assigned by the recorder configuration). + @param step The step that requested this recorder. + @param outputDirectory The url to the directory in which all output file data should be written (if producing `ORKFileResult` instances). + @param rollingFileSizeThreshold The file-size threshold in bytes used to determine when data is rolled over to multiple files as data is being written. + If the value is 0, data is written to only one file and not rolled over to multiple files. @return An initialized location recorder. */ - (instancetype)initWithIdentifier:(NSString *)identifier step:(nullable ORKStep *)step - outputDirectory:(nullable NSURL *)outputDirectory NS_DESIGNATED_INITIALIZER; + outputDirectory:(nullable NSURL *)outputDirectory + rollingFileSizeThreshold:(size_t)rollingFileSizeThreshold NS_DESIGNATED_INITIALIZER; /** diff --git a/ResearchKitActiveTask/Common/Recorders/Location/ORKLocationRecorder.m b/ResearchKitActiveTask/Common/Recorders/Location/ORKLocationRecorder.m index 6ba724a5ef..ab501948f6 100644 --- a/ResearchKitActiveTask/Common/Recorders/Location/ORKLocationRecorder.m +++ b/ResearchKitActiveTask/Common/Recorders/Location/ORKLocationRecorder.m @@ -58,8 +58,18 @@ @interface ORKLocationRecorder () { @implementation ORKLocationRecorder -- (instancetype)initWithIdentifier:(NSString *)identifier step:(ORKStep *)step outputDirectory:(NSURL *)outputDirectory { - self = [super initWithIdentifier:identifier step:step outputDirectory:outputDirectory]; +- (instancetype)initWithIdentifier:(NSString *)identifier step:(ORKStep *)step { + return [self initWithIdentifier:identifier step:step outputDirectory:nil rollingFileSizeThreshold:0]; +} + +- (instancetype)initWithIdentifier:(NSString *)identifier + step:(ORKStep *)step + outputDirectory:(nullable NSURL *)outputDirectory + rollingFileSizeThreshold:(size_t)rollingFileSizeThreshold { + self = [super initWithIdentifier:identifier + step:step + outputDirectory:outputDirectory + rollingFileSizeThreshold:rollingFileSizeThreshold]; if (self) { self.continuesInBackground = YES; } @@ -125,12 +135,13 @@ - (void)stop { NSError *error = _recordingError; _recordingError = nil; - __block NSURL *fileUrl = nil; + __block NSMutableArray *fileUrls = [[NSMutableArray alloc] init]; [_logger enumerateLogs:^(NSURL *logFileUrl, BOOL *stop) { - fileUrl = logFileUrl; - } error:&error]; + [fileUrls addObject:logFileUrl]; + } + error:&error]; - [self reportFileResultWithFile:fileUrl error:error]; + [self reportFileResultsWithFiles:fileUrls error:error]; [super stop]; } @@ -183,11 +194,26 @@ - (NSString *)mimeType { @implementation ORKLocationRecorderConfiguration - (instancetype)initWithIdentifier:(NSString *)identifier { - return [super initWithIdentifier:identifier]; + return [self initWithIdentifier:identifier outputDirectory:nil rollingFileSizeThreshold:0]; +} + +- (instancetype)initWithIdentifier:(NSString *)identifier outputDirectory:(nullable NSURL *)outputDirectory { + return [self initWithIdentifier:identifier outputDirectory:outputDirectory rollingFileSizeThreshold:0]; +} + +- (instancetype)initWithIdentifier:(NSString *)identifier + outputDirectory:(nullable NSURL *)outputDirectory + rollingFileSizeThreshold:(size_t)rollingFileSizeThreshold { + return [super initWithIdentifier:identifier + outputDirectory:outputDirectory + rollingFileSizeThreshold:rollingFileSizeThreshold]; } -- (ORKRecorder *)recorderForStep:(ORKStep *)step outputDirectory:(NSURL *)outputDirectory { - return [[ORKLocationRecorder alloc] initWithIdentifier:self.identifier step:step outputDirectory:outputDirectory]; +- (ORKRecorder *)recorderForStep:(ORKStep *)step { + return [[ORKLocationRecorder alloc] initWithIdentifier:self.identifier + step:step + outputDirectory:self.outputDirectory + rollingFileSizeThreshold:self.rollingFileSizeThreshold]; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { diff --git a/ResearchKitActiveTask/Common/Recorders/Pedometer/ORKPedometerRecorder.h b/ResearchKitActiveTask/Common/Recorders/Pedometer/ORKPedometerRecorder.h index da2f73e506..20baefa204 100644 --- a/ResearchKitActiveTask/Common/Recorders/Pedometer/ORKPedometerRecorder.h +++ b/ResearchKitActiveTask/Common/Recorders/Pedometer/ORKPedometerRecorder.h @@ -61,18 +61,31 @@ ORK_CLASS_AVAILABLE // Negative if an invalid value. @property (nonatomic, readonly) NSInteger totalDistance; +/** + Returns an initialized pedometer recorder. + + @param identifier The unique identifier of the recorder (assigned by the recorder configuration). + @param step The step that requested this recorder. + + @return An initialized pedometer recorder. + */ +- (instancetype)initWithIdentifier:(NSString *)identifier + step:(nullable ORKStep *)step; + /** Returns an initialized pedometer recorder. @param identifier The unique identifier of the recorder (assigned by the recorder configuration). @param step The step that requested this recorder. @param outputDirectory The directory in which the pedometer data should be stored. + @param rollingFileSizeThreshold The file-size threshold in bytes used to determine when data is rolled over to multiple files as data is being written. If the value is 0, data is written to only one file and not rolled over to multiple files. @return An initialized pedometer recorder. */ - (instancetype)initWithIdentifier:(NSString *)identifier step:(nullable ORKStep *)step - outputDirectory:(nullable NSURL *)outputDirectory NS_DESIGNATED_INITIALIZER; + outputDirectory:(nullable NSURL *)outputDirectory + rollingFileSizeThreshold:(size_t)rollingFileSizeThreshold NS_DESIGNATED_INITIALIZER; @end diff --git a/ResearchKitActiveTask/Common/Recorders/Pedometer/ORKPedometerRecorder.m b/ResearchKitActiveTask/Common/Recorders/Pedometer/ORKPedometerRecorder.m index 88913d1ee5..07ea6c50d9 100644 --- a/ResearchKitActiveTask/Common/Recorders/Pedometer/ORKPedometerRecorder.m +++ b/ResearchKitActiveTask/Common/Recorders/Pedometer/ORKPedometerRecorder.m @@ -38,6 +38,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE #import "ORKHelpers_Internal.h" #import "CMPedometerData+ORKJSONDictionary.h" +#import "ResearchKit/ResearchKit-Swift.h" @interface ORKPedometerRecorder () { ORKDataLogger *_logger; @@ -51,12 +52,19 @@ @interface ORKPedometerRecorder () { @implementation ORKPedometerRecorder +- (instancetype)initWithIdentifier:(NSString *)identifier + step:(ORKStep *)step { + return [self initWithIdentifier:identifier step:step outputDirectory:nil rollingFileSizeThreshold:0]; +} + - (instancetype)initWithIdentifier:(NSString *)identifier step:(ORKStep *)step - outputDirectory:(NSURL *)outputDirectory { + outputDirectory:(nullable NSURL *)outputDirectory + rollingFileSizeThreshold:(size_t)rollingFileSizeThreshold { self = [super initWithIdentifier:identifier step:step - outputDirectory:outputDirectory]; + outputDirectory:outputDirectory + rollingFileSizeThreshold:rollingFileSizeThreshold]; if (self) { self.continuesInBackground = YES; } @@ -141,13 +149,13 @@ - (void)stop { [_logger finishCurrentLog]; NSError *error = nil; - __block NSURL *fileUrl = nil; + __block NSMutableArray *fileUrls = [[NSMutableArray alloc] init]; [_logger enumerateLogs:^(NSURL *logFileUrl, BOOL *stop) { - fileUrl = logFileUrl; + [fileUrls addObject:logFileUrl]; } error:&error]; - [self reportFileResultWithFile:fileUrl error:error]; + [self reportFileResultsWithFiles:fileUrls error:error]; [super stop]; } @@ -185,13 +193,26 @@ - (void)reset { @implementation ORKPedometerRecorderConfiguration - (instancetype)initWithIdentifier:(NSString *)identifier { - return [super initWithIdentifier:identifier]; + return [self initWithIdentifier:identifier outputDirectory:nil rollingFileSizeThreshold:0]; +} + +- (instancetype)initWithIdentifier:(NSString *)identifier outputDirectory:(nullable NSURL *)outputDirectory { + return [self initWithIdentifier:identifier outputDirectory:outputDirectory rollingFileSizeThreshold:0]; +} + +- (instancetype)initWithIdentifier:(NSString *)identifier + outputDirectory:(nullable NSURL *)outputDirectory + rollingFileSizeThreshold:(size_t)rollingFileSizeThreshold { + return [super initWithIdentifier:identifier + outputDirectory:outputDirectory + rollingFileSizeThreshold:rollingFileSizeThreshold]; } -- (ORKRecorder *)recorderForStep:(ORKStep *)step outputDirectory:(NSURL *)outputDirectory { +- (ORKRecorder *)recorderForStep:(ORKStep *)step { return [[ORKPedometerRecorder alloc] initWithIdentifier:self.identifier step:step - outputDirectory:outputDirectory]; + outputDirectory:self.outputDirectory + rollingFileSizeThreshold:self.rollingFileSizeThreshold]; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { diff --git a/ResearchKitActiveTask/Common/Recorders/Touch/ORKTouchRecorder.h b/ResearchKitActiveTask/Common/Recorders/Touch/ORKTouchRecorder.h index d343a67b46..64eb83ced2 100644 --- a/ResearchKitActiveTask/Common/Recorders/Touch/ORKTouchRecorder.h +++ b/ResearchKitActiveTask/Common/Recorders/Touch/ORKTouchRecorder.h @@ -52,6 +52,33 @@ ORK_CLASS_AVAILABLE */ @property (nonatomic, strong, readonly, nullable) UIView *touchView; +/** + Returns an initialized pedometer recorder. + + @param identifier The unique identifier of the recorder (assigned by the recorder configuration). + @param step The step that requested this recorder. + + @return An initialized pedometer recorder. + */ +- (instancetype)initWithIdentifier:(NSString *)identifier + step:(nullable ORKStep *)step; + +/** + Returns an initialized pedometer recorder. + + @param identifier The unique identifier of the recorder (assigned by the recorder configuration). + @param step The step that requested this recorder. + @param outputDirectory The url to the directory in which all output file data should be written (if producing `ORKFileResult` instances). + @param rollingFileSizeThreshold The file-size threshold in bytes used to determine when data is rolled over to multiple files as data is being written. + If the value is 0, data is written to only one file and not rolled over to multiple files. + + @return An initialized pedometer recorder. + */ +- (instancetype)initWithIdentifier:(NSString *)identifier + step:(nullable ORKStep *)step + outputDirectory:(nullable NSURL *)outputDirectory + rollingFileSizeThreshold:(size_t)rollingFileSizeThreshold NS_DESIGNATED_INITIALIZER; + @end /** @@ -63,16 +90,30 @@ ORK_CLASS_AVAILABLE ORK_CLASS_AVAILABLE @interface ORKTouchRecorderConfiguration : ORKRecorderConfiguration +/** + Returns an initialized touch recorder configuration. + + @param identifier The unique identifier of the recorder configuration. + + @return An initialized touch recorder configuration. + */ +- (instancetype)initWithIdentifier:(NSString *)identifier; + /** Returns an initialized touch recorder configuration. This method is the designated initializer. @param identifier The unique identifier of the recorder configuration. + @param outputDirectory The url to the directory in which all output file data should be written (if producing `ORKFileResult` instances). + @param rollingFileSizeThreshold The file-size threshold in bytes used to determine when data is rolled over to multiple files as data is being written. + If the value is 0, data is written to only one file and not rolled over to multiple files. @return An initialized touch recorder configuration. */ -- (instancetype)initWithIdentifier:(NSString *)identifier NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithIdentifier:(NSString *)identifier + outputDirectory:(nullable NSURL *)outputDirectory + rollingFileSizeThreshold:(size_t)rollingFileSizeThreshold NS_DESIGNATED_INITIALIZER; /** Returns a new touch recorder configuration initialized from data in the given unarchiver. diff --git a/ResearchKitActiveTask/Common/Recorders/Touch/ORKTouchRecorder.m b/ResearchKitActiveTask/Common/Recorders/Touch/ORKTouchRecorder.m index 77f421f08a..b4855a3d3a 100644 --- a/ResearchKitActiveTask/Common/Recorders/Touch/ORKTouchRecorder.m +++ b/ResearchKitActiveTask/Common/Recorders/Touch/ORKTouchRecorder.m @@ -104,6 +104,17 @@ @interface ORKTouchRecorder () { @implementation ORKTouchRecorder +- (instancetype)initWithIdentifier:(NSString *)identifier step:(ORKStep *)step { + return [self initWithIdentifier:identifier step:step outputDirectory:nil rollingFileSizeThreshold:0]; +} + +- (instancetype)initWithIdentifier:(NSString *)identifier + step:(ORKStep *)step + outputDirectory:(nullable NSURL *)outputDirectory + rollingFileSizeThreshold:(size_t)rollingFileSizeThreshold { + return [super initWithIdentifier:identifier step:step outputDirectory:outputDirectory rollingFileSizeThreshold:rollingFileSizeThreshold]; +} + - (void)dealloc { [_logger finishCurrentLog]; } @@ -151,12 +162,13 @@ - (void)stop { [_logger finishCurrentLog]; NSError *error = nil; - __block NSURL *fileUrl = nil; + __block NSMutableArray *fileUrls = [[NSMutableArray alloc] init]; [_logger enumerateLogs:^(NSURL *logFileUrl, BOOL *stop) { - fileUrl = logFileUrl; - } error:&error]; + [fileUrls addObject:logFileUrl]; + } + error:&error]; - [self reportFileResultWithFile:fileUrl error:error]; + [self reportFileResultsWithFiles:fileUrls error:error]; [super stop]; } @@ -208,13 +220,22 @@ - (void)view:(UIView *)view didDetectTouch:(UITouch *)touch { @implementation ORKTouchRecorderConfiguration - (instancetype)initWithIdentifier:(NSString *)identifier { - return [super initWithIdentifier:identifier]; + return [self initWithIdentifier:identifier outputDirectory:nil rollingFileSizeThreshold:0]; +} + +- (instancetype)initWithIdentifier:(NSString *)identifier + outputDirectory:(nullable NSURL *)outputDirectory + rollingFileSizeThreshold:(size_t)rollingFileSizeThreshold { + return [super initWithIdentifier:identifier + outputDirectory: outputDirectory + rollingFileSizeThreshold:rollingFileSizeThreshold]; } -- (ORKRecorder *)recorderForStep:(ORKStep *)step outputDirectory:(NSURL *)outputDirectory { +- (ORKRecorder *)recorderForStep:(ORKStep *)step { ORKTouchRecorder *recorder = [[ORKTouchRecorder alloc] initWithIdentifier:self.identifier step:step - outputDirectory:outputDirectory]; + outputDirectory:self.outputDirectory + rollingFileSizeThreshold:self.rollingFileSizeThreshold]; return recorder; } diff --git a/ResearchKitActiveTask/Reaction Time/ORKReactionTimeResult.h b/ResearchKitActiveTask/Reaction Time/ORKReactionTimeResult.h index ba87b02cd0..ffc946dabd 100644 --- a/ResearchKitActiveTask/Reaction Time/ORKReactionTimeResult.h +++ b/ResearchKitActiveTask/Reaction Time/ORKReactionTimeResult.h @@ -54,7 +54,7 @@ ORK_CLASS_AVAILABLE @property (nonatomic, assign) NSTimeInterval timestamp; -@property (nonatomic, strong) ORKFileResult *fileResult; +@property (nonatomic, strong) NSArray *fileResults; @end diff --git a/ResearchKitActiveTask/Reaction Time/ORKReactionTimeResult.m b/ResearchKitActiveTask/Reaction Time/ORKReactionTimeResult.m index 96a9f1d635..0729de636b 100644 --- a/ResearchKitActiveTask/Reaction Time/ORKReactionTimeResult.m +++ b/ResearchKitActiveTask/Reaction Time/ORKReactionTimeResult.m @@ -42,14 +42,14 @@ @implementation ORKReactionTimeResult - (void)encodeWithCoder:(NSCoder *)aCoder { [super encodeWithCoder:aCoder]; ORK_ENCODE_DOUBLE(aCoder, timestamp); - ORK_ENCODE_OBJ(aCoder, fileResult); + ORK_ENCODE_OBJ(aCoder, fileResults); } - (instancetype)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if (self) { ORK_DECODE_DOUBLE(aDecoder, timestamp); - ORK_DECODE_OBJ_CLASS(aDecoder, fileResult, ORKFileResult); + ORK_DECODE_OBJ_CLASS(aDecoder, fileResults, NSArray); } return self; } @@ -64,22 +64,22 @@ - (BOOL)isEqual:(id)object { __typeof(self) castObject = object; return (isParentSame && (self.timestamp == castObject.timestamp) && - ORKEqualObjects(self.fileResult, castObject.fileResult)) ; + ORKEqualObjects(self.fileResults, castObject.fileResults)) ; } - (NSUInteger)hash { - return super.hash ^ [NSNumber numberWithDouble:self.timestamp].hash ^ self.fileResult.hash; + return super.hash ^ [NSNumber numberWithDouble:self.timestamp].hash ^ self.fileResults.hash; } - (instancetype)copyWithZone:(NSZone *)zone { ORKReactionTimeResult *result = [super copyWithZone:zone]; - result.fileResult = [self.fileResult copy]; + result.fileResults = [self.fileResults copy]; result.timestamp = self.timestamp; return result; } - (NSString *)descriptionWithNumberOfPaddingSpaces:(NSUInteger)numberOfPaddingSpaces { - return [NSString stringWithFormat:@"%@; timestamp: %f; fileResult: %@%@", [self descriptionPrefixWithNumberOfPaddingSpaces:numberOfPaddingSpaces], self.timestamp, self.fileResult.description, self.descriptionSuffix]; + return [NSString stringWithFormat:@"%@; timestamp: %f; fileResult: %@%@", [self descriptionPrefixWithNumberOfPaddingSpaces:numberOfPaddingSpaces], self.timestamp, self.fileResults.description, self.descriptionSuffix]; } @end diff --git a/ResearchKitActiveTask/Reaction Time/ORKReactionTimeViewController.m b/ResearchKitActiveTask/Reaction Time/ORKReactionTimeViewController.m index ff12fc1a5f..294f76fe86 100644 --- a/ResearchKitActiveTask/Reaction Time/ORKReactionTimeViewController.m +++ b/ResearchKitActiveTask/Reaction Time/ORKReactionTimeViewController.m @@ -51,6 +51,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE @implementation ORKReactionTimeViewController { ORKReactionTimeContentView *_reactionTimeContentView; NSMutableArray *_results; + NSMutableArray *_fileResults; NSTimer *_stimulusTimer; NSTimer *_timeoutTimer; NSTimeInterval _stimulusTimestamp; @@ -68,6 +69,7 @@ - (void)viewDidLoad { // Do any additional setup after loading the view. [self configureTitle]; _results = [NSMutableArray new]; + _fileResults = [NSMutableArray new]; _reactionTimeContentView = [ORKReactionTimeContentView new]; self.activeStepView.activeCustomView = _reactionTimeContentView; [_reactionTimeContentView setStimulusHidden:YES]; @@ -126,13 +128,20 @@ - (void)applicationDidBecomeActive:(NSNotification *)notification { #pragma mark - ORKRecorderDelegate -- (void)recorder:(ORKRecorder *)recorder didCompleteWithResult:(ORKResult *)result { +- (void)recorder:(ORKRecorder *)recorder didCompleteWithResults:(NSArray *)results { + [_fileResults addObjectsFromArray:results]; + if (_validResult) { ORKReactionTimeResult *reactionTimeResult = [[ORKReactionTimeResult alloc] initWithIdentifier:self.step.identifier]; reactionTimeResult.timestamp = _stimulusTimestamp; - reactionTimeResult.fileResult = (ORKFileResult *)result; + + // Save the list of file results related to that result, then reset the array for the next step + reactionTimeResult.fileResults = [_fileResults copy]; + [_fileResults removeAllObjects]; + [_results addObject:reactionTimeResult]; } + [self attemptDidFinish]; } diff --git a/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/ORKdBHLToneAudiometryAudioGenerator.m b/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/ORKdBHLToneAudiometryAudioGenerator.m index c7e335646f..33d2393b1a 100644 --- a/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/ORKdBHLToneAudiometryAudioGenerator.m +++ b/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/ORKdBHLToneAudiometryAudioGenerator.m @@ -57,6 +57,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE typedef NSString * ORKVolumeCurveFilename NS_STRING_ENUM; ORKVolumeCurveFilename const ORKVolumeCurveFilenameAirPods = @"volume_curve_AIRPODS"; +ORKVolumeCurveFilename const ORKVolumeCurveFilenameAirPodsGen2 = @"volume_curve_AIRPODSV2"; ORKVolumeCurveFilename const ORKVolumeCurveFilenameAirPodsGen3 = @"volume_curve_AIRPODSV3"; ORKVolumeCurveFilename const ORKVolumeCurveFilenameAirPodsPro = @"volume_curve_AIRPODSPRO"; ORKVolumeCurveFilename const ORKVolumeCurveFilenameAirPodsProGen2 = @"volume_curve_AIRPODSPROV2"; @@ -188,10 +189,12 @@ - (instancetype)initForHeadphoneType:(ORKHeadphoneTypeIdentifier)headphoneType { ORKHeadphoneTypeIdentifier headphoneTypeIdentifier; ORKVolumeCurveFilename volumeCurveFilename; - if ([headphoneTypeUppercased isEqualToString:ORKHeadphoneTypeIdentifierAirPodsGen1] || - [headphoneTypeUppercased isEqualToString:ORKHeadphoneTypeIdentifierAirPodsGen2]) { + if ([headphoneTypeUppercased isEqualToString:ORKHeadphoneTypeIdentifierAirPodsGen1]) { headphoneTypeIdentifier = ORKHeadphoneTypeIdentifierAirPods; volumeCurveFilename = ORKVolumeCurveFilenameAirPods; + } else if ([headphoneTypeUppercased isEqualToString:ORKHeadphoneTypeIdentifierAirPodsGen2]) { + headphoneTypeIdentifier = ORKHeadphoneTypeIdentifierAirPodsGen2; + volumeCurveFilename = ORKVolumeCurveFilenameAirPodsGen2; } else if ([headphoneTypeUppercased isEqualToString:ORKHeadphoneTypeIdentifierAirPodsGen3]) { headphoneTypeIdentifier = ORKHeadphoneTypeIdentifierAirPodsGen3; volumeCurveFilename = ORKVolumeCurveFilenameAirPodsGen3; diff --git a/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/frequency_dBSPL_AIRPODSV2.plist b/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/frequency_dBSPL_AIRPODSV2.plist new file mode 100644 index 0000000000..c98ca25bf7 --- /dev/null +++ b/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/frequency_dBSPL_AIRPODSV2.plist @@ -0,0 +1,28 @@ + + + + + 125 + 88.97 + 250 + 86.30 + 500 + 84.49 + 750 + 83.59 + 1000 + 83.89 + 1500 + 87.60 + 2000 + 90.07 + 3000 + 92.51 + 4000 + 88.87 + 6000 + 91.46 + 8000 + 89.61 + + diff --git a/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/retspl_AIRPODSMAX.plist b/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/retspl_AIRPODSMAX.plist index 26dc1b1b0f..5361aa0860 100644 --- a/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/retspl_AIRPODSMAX.plist +++ b/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/retspl_AIRPODSMAX.plist @@ -3,26 +3,26 @@ 125 - 31 + 25.13 250 - 21 + 19.29 500 - 13 + 13.45 750 - 9 + 10.515 1000 - 7 + 7.58 1500 - 10 + 11.45 2000 - 12 + 15.32 3000 - 13.5 + 12.06 4000 - 16 + 8.79 6000 - 16 + 3.74 8000 - 15.5 + 18.68 diff --git a/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/retspl_AIRPODSPRO.plist b/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/retspl_AIRPODSPRO.plist index 26dc1b1b0f..c6e9ceeb6a 100644 --- a/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/retspl_AIRPODSPRO.plist +++ b/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/retspl_AIRPODSPRO.plist @@ -3,26 +3,26 @@ 125 - 31 + 34.04 250 - 21 + 23.52 500 - 13 + 12.99 750 - 9 + 11.13 1000 - 7 + 9.27 1500 - 10 + 11.69 2000 - 12 + 14.11 3000 - 13.5 + 13.42 4000 - 16 + 12.72 6000 - 16 + 14.62 8000 - 15.5 + 16.51 diff --git a/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/retspl_AIRPODSPROV2.plist b/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/retspl_AIRPODSPROV2.plist index 26dc1b1b0f..c6e9ceeb6a 100644 --- a/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/retspl_AIRPODSPROV2.plist +++ b/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/retspl_AIRPODSPROV2.plist @@ -3,26 +3,26 @@ 125 - 31 + 34.04 250 - 21 + 23.52 500 - 13 + 12.99 750 - 9 + 11.13 1000 - 7 + 9.27 1500 - 10 + 11.69 2000 - 12 + 14.11 3000 - 13.5 + 13.42 4000 - 16 + 12.72 6000 - 16 + 14.62 8000 - 15.5 + 16.51 diff --git a/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/retspl_AIRPODSV2.plist b/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/retspl_AIRPODSV2.plist new file mode 100644 index 0000000000..94254c97c2 --- /dev/null +++ b/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/retspl_AIRPODSV2.plist @@ -0,0 +1,28 @@ + + + + + 125 + 42.45 + 250 + 30.2 + 500 + 17.94 + 750 + 16.45 + 1000 + 14.96 + 1500 + 13.85 + 2000 + 12.73 + 3000 + 13.27 + 4000 + 13.8 + 6000 + 14.81 + 8000 + 15.82 + + diff --git a/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/retspl_AIRPODSV3.plist b/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/retspl_AIRPODSV3.plist index 26dc1b1b0f..e3cf8b63b1 100644 --- a/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/retspl_AIRPODSV3.plist +++ b/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/retspl_AIRPODSV3.plist @@ -3,26 +3,26 @@ 125 - 31 + 39.05 250 - 21 + 26.68 500 - 13 + 14.31 750 - 9 + 13.16 1000 - 7 + 12.01 1500 - 10 + 12.64 2000 - 12 + 13.27 3000 - 13.5 + 14.48 4000 - 16 + 15.69 6000 - 16 + 14.77 8000 - 15.5 + 13.85 diff --git a/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/retspl_EARPODS.plist b/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/retspl_EARPODS.plist index 26dc1b1b0f..bcbaca9bec 100644 --- a/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/retspl_EARPODS.plist +++ b/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/retspl_EARPODS.plist @@ -3,26 +3,26 @@ 125 - 31 + 38.78 250 - 21 + 28.99 500 - 13 + 21.66 750 - 9 + 17.97 1000 - 7 + 14.28 1500 - 10 + 13.02 2000 - 12 + 11.75 3000 - 13.5 + 13.69 4000 - 16 + 15.62 6000 - 16 + 16.77 8000 - 15.5 + 17.92 diff --git a/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/volume_curve_AIRPODSV2.plist b/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/volume_curve_AIRPODSV2.plist new file mode 100644 index 0000000000..b6ce2c3c79 --- /dev/null +++ b/ResearchKitActiveTask/dBHL Tone Audiometry/ORKAudiometry/volume_curve_AIRPODSV2.plist @@ -0,0 +1,38 @@ + + + + + 1.0000 + 0 + 0.9375 + -3 + 0.8750 + -6.5 + 0.8125 + -10 + 0.7500 + -13.5 + 0.6875 + -17 + 0.6250 + -21 + 0.5625 + -25 + 0.5000 + -29 + 0.4375 + -33 + 0.3750 + -37.5 + 0.3125 + -42 + 0.2500 + -47 + 0.1875 + -52.5 + 0.1250 + -58.5 + 0.0625 + -65.5 + + diff --git a/ResearchKitActiveTask/environmentSPLMeter/ORKEnvironmentSPLMeterStep.h b/ResearchKitActiveTask/environmentSPLMeter/ORKEnvironmentSPLMeterStep.h index 728a15a978..68854326cb 100644 --- a/ResearchKitActiveTask/environmentSPLMeter/ORKEnvironmentSPLMeterStep.h +++ b/ResearchKitActiveTask/environmentSPLMeter/ORKEnvironmentSPLMeterStep.h @@ -53,6 +53,8 @@ ORK_CLASS_AVAILABLE */ @property (nonatomic, assign) NSInteger requiredContiguousSamples; +- (instancetype)initWithIdentifier:(NSString *)identifier outputDirectory:(nullable NSURL *)outputDirectory; + @end NS_ASSUME_NONNULL_END diff --git a/ResearchKitActiveTask/environmentSPLMeter/ORKEnvironmentSPLMeterStep.m b/ResearchKitActiveTask/environmentSPLMeter/ORKEnvironmentSPLMeterStep.m index 06b84d8d75..2de4dedc78 100644 --- a/ResearchKitActiveTask/environmentSPLMeter/ORKEnvironmentSPLMeterStep.m +++ b/ResearchKitActiveTask/environmentSPLMeter/ORKEnvironmentSPLMeterStep.m @@ -40,21 +40,25 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE @implementation ORKEnvironmentSPLMeterStep - (instancetype)initWithIdentifier:(NSString *)identifier { + return [self initWithIdentifier:identifier outputDirectory:nil]; +} + +- (instancetype)initWithIdentifier:(NSString *)identifier outputDirectory:(NSURL *)outputDirectory { self = [super initWithIdentifier:identifier]; if (self) { - [self commonInit]; + [self commonInitWithOutputDirectory:outputDirectory]; } return self; } -- (void)commonInit { +- (void)commonInitWithOutputDirectory:(NSURL *)outputDirectory { self.thresholdValue = ORKEnvironmentSPLMeterTaskDefaultThresholdValue; self.samplingInterval = ORKEnvironmentSPLMeterTaskMinimumSamplingInterval; self.requiredContiguousSamples = ORKEnvironmentSPLMeterTaskDefaultRequiredContiguousSamples; self.stepDuration = CGFLOAT_MAX; 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]]; + ORKAudioStreamerConfiguration *config = [[ORKAudioStreamerConfiguration alloc] initWithIdentifier:[NSString stringWithFormat:@"%@_streamerConfiguration",self.identifier] outputDirectory:outputDirectory]; config.bypassAudioEngineStart = YES; self.recorderConfigurations = @[config]; } diff --git a/ResearchKitTests/CMLogItem+timestampSince1970.swift b/ResearchKitTests/CMLogItem+timestampSince1970.swift new file mode 100644 index 0000000000..3e13dceaf1 --- /dev/null +++ b/ResearchKitTests/CMLogItem+timestampSince1970.swift @@ -0,0 +1,101 @@ +/* + Copyright (c) 2025, 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 CoreMotion +import Testing + +@testable import ResearchKitActiveTask + +@Suite +struct CMLogItemTimestampTests { + let sut = MockCMLogItem() + + static let calendar = { + var calendar = Calendar(identifier: .gregorian) + calendar.timeZone = TimeZone(identifier: "America/Los_Angeles")! + return calendar + }() + + @Test("Calculating correct timestamp since 1970 given reference uptime", arguments: [ + BootTime.past, + BootTime.recent, + BootTime.now, + ]) + func calculateTimeStampSince1970(using lastBootTime: BootTime) { + // Given the reference uptime + let referenceUptime = lastBootTime.referenceUptime + + // When calculating the timestamp value since 1970 in reference to the reference up time + let timestampSince1970 = sut.timestampSince1970(using: referenceUptime) + + // Then the log item's dates calculated using timestamp or timestampSince1970 are identical as expected. + let dateAtTimestampFromTimeInterval = lastBootTime.date.addingTimeInterval(sut.timestamp) + let dateAtTimestampFromTimeIntervalSince1970 = Date(timeIntervalSince1970: timestampSince1970) + + #expect(Self.calendar.isDate( + dateAtTimestampFromTimeIntervalSince1970, + equalTo: dateAtTimestampFromTimeInterval, + toGranularity: .second), + """ + Given a last boot date of \(lastBootTime.date), \ + translating to a reference uptime of \(referenceUptime), \ + expected the date derived from the calculated timestamp since 1970 (\(timestampSince1970)) \ + to yield the last boot date + \(sut.timestamp) seconds = \(dateAtTimestampFromTimeInterval), \ + but got \(dateAtTimestampFromTimeIntervalSince1970) instead. + """ + ) + } +} + +class MockCMLogItem: CMLogItem { + override var timestamp: TimeInterval { + 900.00 // The log item event happened 900s, i.e 15 min, after the reference uptime. + } +} + +extension CMLogItemTimestampTests { + enum BootTime { + case past + case recent + case now + + var date: Date { + Date(timeIntervalSinceNow: -referenceUptime) + } + + var referenceUptime: TimeInterval { + switch self { + case .past: 1556172614.3611889 // 1976/04/01 10:10:10 + case .recent: 5 // 5s ago + case .now: 0 // 0s ago + } + } + } +} diff --git a/ResearchKitTests/ORKActiveTaskResultTests.swift b/ResearchKitTests/ORKActiveTaskResultTests.swift index 068c2c8725..3582ca2387 100644 --- a/ResearchKitTests/ORKActiveTaskResultTests.swift +++ b/ResearchKitTests/ORKActiveTaskResultTests.swift @@ -45,12 +45,13 @@ class ORKAmslerGridResultTests: XCTestCase { let bundle = Bundle(identifier: "org.researchkit.ResearchKit") image = UIImage(named: "amslerGrid", in: bundle, compatibleWith: nil) path = UIBezierPath(rect: CGRect(x: 0, y: 0, width: 50, height: 50)) - result = ORKAmslerGridResult(identifier: identifier, image: image, path: [path], eyeSide: .left) + result = ORKAmslerGridResult(identifier: identifier) + result.path = [path] + result.eyeSide = .left } func testProperties() { XCTAssertEqual(result.identifier, identifier) - XCTAssertEqual(result.image, image) XCTAssertEqual(result.path, [path]) XCTAssertEqual(result.eyeSide, ORKAmslerGridEyeSide.left) } @@ -59,7 +60,9 @@ class ORKAmslerGridResultTests: XCTestCase { result.startDate = date result.endDate = date - let newResult = ORKAmslerGridResult(identifier: identifier, image: image, path: [path], eyeSide: .left) + let newResult = ORKAmslerGridResult(identifier: identifier) + newResult.path = [path] + newResult.eyeSide = .left newResult.startDate = date newResult.endDate = date XCTAssert(result.isEqual(newResult)) @@ -249,13 +252,13 @@ class ORKReactionTimeResultTests: XCTestCase { fileResult = ORKFileResult(identifier: identifier) url = URL(fileURLWithPath: "FILEURL") fileResult.fileURL = url - result.fileResult = fileResult + result.fileResults = [fileResult] } func testProperties() { XCTAssertEqual(result.identifier, identifier) XCTAssertEqual(result.timestamp, 10) - XCTAssertEqual(result.fileResult, fileResult) + XCTAssertEqual(result.fileResults, [fileResult]) } func testIsEqual() { @@ -264,7 +267,7 @@ class ORKReactionTimeResultTests: XCTestCase { let newResult = ORKReactionTimeResult(identifier: identifier) newResult.timestamp = 10 - newResult.fileResult = fileResult + newResult.fileResults = [fileResult] newResult.startDate = date newResult.endDate = date diff --git a/ResearchKitTests/ORKDataLoggerTests.m b/ResearchKitTests/ORKDataLoggerTests.m index b24e7680e6..709752f199 100644 --- a/ResearchKitTests/ORKDataLoggerTests.m +++ b/ResearchKitTests/ORKDataLoggerTests.m @@ -124,6 +124,25 @@ - (void)testJSONFormatting { XCTAssertEqualObjects(jsonOut[@"items"][0], jsonObject); } +- (void)testJSONFileExtension { + NSDictionary *jsonObject = @{@"test": @[@"a", @"b"], @"blah": @(1) }; + + [self logJsonObjectAndRolloverAndWaitOnce:jsonObject]; + [self logJsonObjectAndRolloverAndWaitOnce:jsonObject]; + [self logJsonObjectAndRolloverAndWaitOnce:jsonObject]; + + __block int count = 0; + + [_dataLogger enumerateLogs:^(NSURL *logFileUrl, BOOL *stop) { + count ++; + NSString *fileExtension = [logFileUrl pathExtension]; + NSLog(@"%@", fileExtension); + XCTAssertEqualObjects(fileExtension, @"json"); + } error:nil]; + + XCTAssertEqual(count, 3); +} + - (void)testContinuesExistingLog { // Test that if you create a logger, and then kill it and create a new logger, the new one // continues from the right place without forcing a roll-over diff --git a/ResearchKitTests/ORKESerialization.m b/ResearchKitTests/ORKESerialization.m index d2ae538ab3..74014be967 100644 --- a/ResearchKitTests/ORKESerialization.m +++ b/ResearchKitTests/ORKESerialization.m @@ -42,6 +42,10 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE #import #import +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION +#import +#endif + ORKESerializationKey const ORKESerializationKeyImageName = @"imageName"; static NSString *_ClassKey = @"_class"; @@ -279,9 +283,11 @@ static CLLocationCoordinate2D coordinateFromDictionary(NSDictionary *dict) { } #endif +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION static NSString *identifierFromClinicalType(HKClinicalType *type) { return type.identifier; } +#endif #if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION static CLCircularRegion *circularRegionFromDictionary(NSDictionary *dict) { @@ -395,9 +401,11 @@ static NSRegularExpressionOptions regularExpressionOptionsFromArray(NSArray *arr } #endif +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION static HKClinicalType *typeFromIdentifier(NSString *identifier) { return [HKClinicalType clinicalTypeForIdentifier:identifier]; } +#endif static UIColor * _Nullable colorFromDictionary(NSDictionary *dict) { CGFloat r = [[dict objectForKey:@"r"] floatValue]; @@ -813,7 +821,8 @@ @implementation ORKESerializer static NSMutableDictionary *ORKESerializationEncodingTable(void) { static dispatch_once_t onceToken; static NSMutableDictionary *internalEncodingTable = nil; - dispatch_once(&onceToken, ^{ + dispatch_once(&onceToken, + ^{ internalEncodingTable = [@{ ENTRY(ORKResultSelector, @@ -830,14 +839,14 @@ @implementation ORKESerializer })), ENTRY(ORKPredicateStepNavigationRule, ^id(NSDictionary *dict, ORKESerializationPropertyGetter getter) { - ORKPredicateStepNavigationRule *rule = [[ORKPredicateStepNavigationRule alloc] initWithResultPredicates:GETPROP(dict, resultPredicates) + ORKPredicateStepNavigationRule *rule = [[ORKPredicateStepNavigationRule alloc] initWithResultPredicateFormats:GETPROP(dict, resultPredicateFormats) destinationStepIdentifiers:GETPROP(dict, destinationStepIdentifiers) defaultStepIdentifier:GETPROP(dict, defaultStepIdentifier) validateArrays:NO]; return rule; }, (@{ - PROPERTY(resultPredicates, NSPredicate, NSArray, NO, nil, nil), + PROPERTY(resultPredicateFormats, NSString, NSArray, NO, nil, nil), PROPERTY(destinationStepIdentifiers, NSString, NSArray, NO, nil, nil), PROPERTY(defaultStepIdentifier, NSString, NSObject, NO, nil, nil), PROPERTY(additionalTaskResults, ORKTaskResult, NSArray, YES, nil, nil), @@ -918,6 +927,16 @@ @implementation ORKESerializer PROPERTY(earlyTerminationConfiguration, ORKEarlyTerminationConfiguration, NSObject, YES, nil, nil), PROPERTY(shouldAutomaticallyAdjustImageTintColor, NSNumber, NSObject, YES, nil, nil), })), + ENTRY(ORKKeyValueStepModifier, + ^id(NSDictionary *dict, ORKESerializationPropertyGetter getter) { + ORKKeyValueStepModifier *stepModifier = [[ORKKeyValueStepModifier alloc] initWithResultPredicateFormat:GETPROP(dict, resultPredicateFormat) keyValueMap:GETPROP(dict, keyValueMap)]; + return stepModifier; + }, + (@{ + PROPERTY(resultPredicateFormat, NSString, NSObject, NO, nil, nil), + PROPERTY(keyValueMap, NSDictionary, NSObject, NO, nil, nil) + } + )), ENTRY(ORKBodyItem, ^id(__unused NSDictionary *dict, __unused ORKESerializationPropertyGetter getter) { ORKBodyItem *bodyItem = [[ORKBodyItem alloc] initWithText:GETPROP(dict, text) @@ -999,11 +1018,18 @@ @implementation ORKESerializer })), ENTRY(ORKRecorderConfiguration, ^id(NSDictionary *dict, ORKESerializationPropertyGetter getter) { - ORKRecorderConfiguration *recorderConfiguration = [[ORKRecorderConfiguration alloc] initWithIdentifier:GETPROP(dict, identifier)]; - return recorderConfiguration; + NSNumber *rollingFileSizeThreshold = GETPROP(dict, rollingFileSizeThreshold); + ORKRecorderConfiguration *recorderConfiguration = [[ORKRecorderConfiguration alloc] initWithIdentifier:GETPROP(dict, identifier) + outputDirectory:GETPROP(dict, outputDirectory) + rollingFileSizeThreshold:rollingFileSizeThreshold.doubleValue]; + return recorderConfiguration; }, (@{ PROPERTY(identifier, NSString, NSObject, NO, nil, nil), + PROPERTY(outputDirectory, NSURL, NSObject, YES, + ^id(id url, __unused ORKESerializationContext *context) { return [(NSURL *)url absoluteString]; }, + ^id(id string, __unused ORKESerializationContext *context) { return [NSURL URLWithString:string]; }), + PROPERTY(rollingFileSizeThreshold, NSNumber, NSObject, YES, nil, nil), })), ENTRY(ORKQuestionStep, ^id(NSDictionary *dict, ORKESerializationPropertyGetter getter) { @@ -1092,9 +1118,10 @@ @implementation ORKESerializer (@{ PROPERTY(result, NSString, NSObject, YES, nil, nil) })), +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION ENTRY(ORKHealthQuantityTypeRecorderConfiguration, ^id(NSDictionary *dict, ORKESerializationPropertyGetter getter) { - return [[ORKHealthQuantityTypeRecorderConfiguration alloc] initWithIdentifier:GETPROP(dict, identifier) healthQuantityType:GETPROP(dict, quantityType) unit:GETPROP(dict, unit)]; + return [[ORKHealthQuantityTypeRecorderConfiguration alloc] initWithIdentifier:GETPROP(dict, identifier) healthQuantityType:GETPROP(dict, quantityType) unit:GETPROP(dict, unit)]; }, (@{ PROPERTY(quantityType, HKQuantityType, NSObject, NO, @@ -1102,8 +1129,9 @@ @implementation ORKESerializer ^id(id string, __unused ORKESerializationContext *context) { return [HKQuantityType quantityTypeForIdentifier:string]; }), PROPERTY(unit, HKUnit, NSObject, NO, ^id(id unit, __unused ORKESerializationContext *context) { return [(HKUnit *)unit unitString]; }, - ^id(id string, __unused ORKESerializationContext *context) { return [HKUnit unitFromString:string]; }), + ^id(id string, __unused ORKESerializationContext *context) { return [HKUnit unitFromString:string]; }) })), +#endif ENTRY(ORKActiveStep, ^id(NSDictionary *dict, ORKESerializationPropertyGetter getter) { return [[ORKActiveStep alloc] initWithIdentifier:GETPROP(dict, identifier)]; @@ -1413,7 +1441,7 @@ @implementation ORKESerializer return [[ORKAccelerometerRecorderConfiguration alloc] initWithIdentifier:GETPROP(dict, identifier) frequency:((NSNumber *)GETPROP(dict, frequency)).doubleValue]; }, (@{ - PROPERTY(frequency, NSNumber, NSObject, NO, nil, nil), + PROPERTY(frequency, NSNumber, NSObject, NO, nil, nil) })), ENTRY(ORKAudioRecorderConfiguration, ^id(NSDictionary *dict, ORKESerializationPropertyGetter getter) { @@ -1430,13 +1458,11 @@ @implementation ORKESerializer PROPERTY(eyeSide, NSNumber, NSObject, YES, nil, nil), })), ENTRY(ORKAmslerGridResult, - ^id(NSDictionary *dict, ORKESerializationPropertyGetter getter) { - return [[ORKAmslerGridResult alloc] initWithIdentifier:GETPROP(dict, identifier)image:[UIImage new] path:GETPROP(dict, path) eyeSide:(ORKAmslerGridEyeSide)[GETPROP(dict, eyeSide) integerValue]]; - }, + nil, (@{ PROPERTY(eyeSide, NSNumber, NSObject, NO, nil, nil), - PROPERTY(path, UIBezierPath, NSArray, NO, nil, nil), - IMAGEPROPERTY(image, NSObject, YES), + PROPERTY(imageFileResult, ORKFileResult, NSObject, NO, nil, nil), + PROPERTY(drawingPathFileResult, ORKFileResult, NSObject, NO, nil, nil), })), ENTRY(ORKConsentDocument, nil, @@ -1572,6 +1598,7 @@ @implementation ORKESerializer }, (@{ PROPERTY(frequency, NSNumber, NSObject, NO, nil, nil), + PROPERTY(rollingFileSizeThreshold, NSNumber, NSObject, YES, nil, nil), })), ENTRY(ORKdBHLToneAudiometryOnboardingStep, ^id(NSDictionary *dict, ORKESerializationPropertyGetter getter) { @@ -1624,6 +1651,7 @@ @implementation ORKESerializer (@{ PROPERTY(pageTask, ORKOrderedTask, NSObject, NO, nil, nil), })), +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION ENTRY(ORKHealthKitCharacteristicTypeAnswerFormat, ^id(NSDictionary *dict, ORKESerializationPropertyGetter getter) { return [[ORKHealthKitCharacteristicTypeAnswerFormat alloc] initWithCharacteristicType:GETPROP(dict, characteristicType)]; @@ -1662,6 +1690,7 @@ @implementation ORKESerializer ^id(id string, __unused ORKESerializationContext *context) { return @(ORKNumericAnswerStyleFromString(string)); }), PROPERTY(shouldRequestAuthorization, NSNumber, NSObject, YES, nil, nil), })), +#endif ENTRY(ORKAnswerFormat, nil, (@{ @@ -1988,6 +2017,7 @@ @implementation ORKESerializer return [[ORKPedometerRecorderConfiguration alloc] initWithIdentifier:GETPROP(dict,identifier)]; }, (@{ + PROPERTY(rollingFileSizeThreshold, NSNumber, NSObject, YES, nil, nil), })), ENTRY(ORKTouchRecorderConfiguration, ^id(NSDictionary *dict, ORKESerializationPropertyGetter getter) { @@ -2062,11 +2092,15 @@ @implementation ORKESerializer PROPERTY(seed, NSNumber, NSObject, NO, nil, nil), PROPERTY(sequence, NSNumber, NSArray, NO, nil, nil), PROPERTY(gameSize, NSNumber, NSObject, NO, nil, nil), - PROPERTY(gameStatus, NSNumber, NSObject, NO, nil, nil), + PROPERTY(gameStatus, NSNumber, NSObject, NO, + ^id(id numeric, __unused ORKESerializationContext *context) { + return tableMapForward(((NSNumber *)numeric).integerValue, memoryGameStatusTable()); + }, + ^id(id string, __unused ORKESerializationContext *context) { + return @(tableMapReverse(string, memoryGameStatusTable())); + }), PROPERTY(score, NSNumber, NSObject, NO, nil, nil), - PROPERTY(touchSamples, ORKSpatialSpanMemoryGameTouchSample, NSArray, NO, - ^id(id numeric, __unused ORKESerializationContext *context) { return tableMapForward(((NSNumber *)numeric).integerValue, memoryGameStatusTable()); }, - ^id(id string, __unused ORKESerializationContext *context) { return @(tableMapReverse(string, memoryGameStatusTable())); }), + PROPERTY(touchSamples, ORKSpatialSpanMemoryGameTouchSample, NSArray, NO, nil, nil), PROPERTY(targetRects, NSValue, NSArray, NO, ^id(id value, __unused ORKESerializationContext *context) { return value?dictionaryFromCGRect(((NSValue *)value).CGRectValue):nil; }, ^id(id dict, __unused ORKESerializationContext *context) { return [NSValue valueWithCGRect:rectFromDictionary(dict)]; }) @@ -2129,7 +2163,7 @@ @implementation ORKESerializer nil, (@{ PROPERTY(timestamp, NSNumber, NSObject, NO, nil, nil), - PROPERTY(fileResult, ORKResult, NSObject, NO, nil, nil) + PROPERTY(fileResults, NSArray, NSObject, NO, nil, nil) })), ENTRY(ORKSpeechInNoiseResult, nil, @@ -2541,6 +2575,7 @@ @implementation ORKESerializer PROPERTY(platform, NSString, NSObject, NO, nil, nil) })), } mutableCopy]; +#if ORK_FEATURE_HEALTHKIT_AUTHORIZATION if (@available(iOS 12.0, *)) { [internalEncodingTable addEntriesFromDictionary:@{ ENTRY(ORKHealthClinicalTypeRecorderConfiguration, ^id(NSDictionary *dict, ORKESerializationPropertyGetter getter) { @@ -2553,6 +2588,7 @@ @implementation ORKESerializer PROPERTY(healthFHIRResourceType, NSString, NSObject, NO, nil, nil), })) }]; } +#endif }); return internalEncodingTable; } @@ -2662,7 +2698,7 @@ static id objectForJsonObject(id input, } static BOOL isValid(id object) { - return [NSJSONSerialization isValidJSONObject:object] || [object isKindOfClass:[NSNumber class]] || [object isKindOfClass:[NSString class]] || [object isKindOfClass:[NSNull class]] || [object isKindOfClass:[ORKNoAnswer class]]; + return [NSJSONSerialization isValidJSONObject:object] || [object isKindOfClass:[NSValue class]] || [object isKindOfClass:[NSNumber class]] || [object isKindOfClass:[NSString class]] || [object isKindOfClass:[NSNull class]] || [object isKindOfClass:[ORKNoAnswer class]]; } static id jsonObjectForObject(id object, ORKESerializationContext *context) { diff --git a/ResearchKitTests/ORKJSONSerializationTests.m b/ResearchKitTests/ORKJSONSerializationTests.m index 703f08e10b..5f21b69067 100644 --- a/ResearchKitTests/ORKJSONSerializationTests.m +++ b/ResearchKitTests/ORKJSONSerializationTests.m @@ -55,7 +55,10 @@ @implementation TestCompilerFlagHelper + (NSArray *)_fetchExclusionList { NSArray *classesToExclude = @[]; - + + + + #if !ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION NSArray *locationClasses = @[ @"ORKLocation", @@ -274,8 +277,12 @@ - (instancetype)orktest_init_alt { \ ORK_MAKE_TEST_INIT(ORKSkipStepNavigationRule, ^{return [super init];}); ORK_MAKE_TEST_INIT(ORKFormItemVisibilityRule, ^{return [super init];}); ORK_MAKE_TEST_INIT(ORKStepModifier, ^{return [super init];}); -ORK_MAKE_TEST_INIT(ORKKeyValueStepModifier, ^{return [super init];}); -ORK_MAKE_TEST_INIT(ORKAnswerFormat, ^{return [super init];}); +ORK_MAKE_TEST_INIT(ORKKeyValueStepModifier, + ^{ NSPredicate* resultPredicate = [ORKResultPredicate predicateForBooleanQuestionResultWithResultSelector:[ORKResultSelector selectorWithResultIdentifier:@"test"] + expectedAnswer:YES]; + ORKKeyValueStepModifier* stepModifier = [self initWithResultPredicate:resultPredicate keyValueMap:@{@"title":@"YES"}]; + return stepModifier; +});ORK_MAKE_TEST_INIT(ORKAnswerFormat, ^{return [super init];}); ORK_MAKE_TEST_INIT(ORKDontKnowAnswer, ^{return [ORKDontKnowAnswer answer];}); ORK_MAKE_TEST_INIT(ORKLoginStep, ^{return [self initWithIdentifier:[NSUUID UUID].UUIDString title:@"title" text:@"text" loginViewControllerClass:NSClassFromString(@"ORKLoginStepViewController") ];}); ORK_MAKE_TEST_INIT(ORKVerificationStep, ^{return [self initWithIdentifier:[NSUUID UUID].UUIDString text:@"text" verificationViewControllerClass:NSClassFromString(@"ORKVerificationStepViewController") ];}); @@ -520,9 +527,13 @@ - (id)init { @"ORKTextAnswerFormat.validationRegex", @"ORKFileResult.fileURL", @"ORKFrontFacingCameraTask.fileURL", + @"ORKAmslerGridResult.imageFileResult.fileURL", + @"ORKAmslerGridResult.drawingPathFileResult.fileURL", @"ORKTaskResult.outputDirectory", @"ORKPageResult.outputDirectory", @"ORKPredicateFormItemVisibilityRule.predicateFormat", // Prevent trying to assign a bogus empty string as predicateFormat during testing + @"ORKPredicateStepNavigationRule.resultPredicateFormats", // Prevent trying to assign bogus empty strings as resultPredicateFormats during testing + @"ORKKeyValueStepModifier.resultPredicateFormat", // Prevent trying to assign a bogus empty string as resultPredicateFormat during testing @"ORKAccuracyStroopStep.actualDisplayColor", @"ORKAccuracyStroopResult.didSelectCorrectColor", @"ORKAccuracyStroopResult.timeTakenToSelect", @@ -534,6 +545,7 @@ - (id)init { _knownNotSerializedProperties = @[ @"ORKActiveStep.image", @"ORKAmslerGridResult.image", + @"ORKAmslerGridResult.path", @"ORKAnswerFormat.formStepViewControllerCellClass", @"ORKAnswerFormat.healthKitUnit", @"ORKAnswerFormat.healthKitUserUnit", @@ -577,6 +589,8 @@ - (id)init { @"ORKOrderedTask.requestedPermissions", @"ORKPageStep.steps", @"ORKPredicateFormItemVisibilityRule.predicate", // roundtripping format->predicate->format is unsupported in NSPredicate, so no point in serializing the predicate as text. + @"ORKPredicateStepNavigationRule.resultPredicates", // roundtripping format->predicate->format is unsupported in NSPredicate, so no point in serializing the predicate as text. + @"ORKKeyValueStepModifier.resultPredicate", // roundtripping format->predicate->format is unsupported in NSPredicate, so no point in serializing the predicate as text. @"ORKQuestionResult.answer", @"ORKQuestionStep.question", @"ORKQuestionStep.questionType", @@ -1021,6 +1035,13 @@ - (void)testORKSerialization { } else if ([aClass isSubclassOfClass:[ORKPredicateFormItemVisibilityRule class]]) { // predicateFormat cannot be an empty sring for deserialization to work [instance setValue:@"$title == 'testSerialization' && $className == 'ORKPredicateFormItemVisibilityRule'" forKey:@"predicateFormat"]; + } else if ([aClass isSubclassOfClass:[ORKPredicateStepNavigationRule class]]) { + // resultPredicateFormats cannot be an empty string array for deserialization to work + NSArray *resultPredicateFormats = @[@"$title == 'testSerialization' && $className == 'ORKPredicateStepNavigationRule'"]; + [instance setValue:resultPredicateFormats forKey:@"resultPredicateFormats"]; + } else if ([aClass isSubclassOfClass:[ORKKeyValueStepModifier class]]) { + // resultPredicateFormat cannot be an empty string array for deserialization to work + [instance setValue:@"$title == 'testSerialization' && $className == 'ORKKeyValueStepModifier'" forKey:@"resultPredicateFormat"]; } else if ([aClass isSubclassOfClass:[ORKDateAnswerFormat class]]) { // Seems to be unstable for some input timestamps [instance setValue:dateFormatOverrideDate forKey:@"defaultDate"]; @@ -1034,6 +1055,26 @@ - (void)testORKSerialization { [instance setValue:@(2023) forKey:@"relativeYear"]; } else if ([aClass isSubclassOfClass:[ORKColorChoice class]]) { [instance setValue:@"blah" forKey:@"value"]; + } else if ([aClass isSubclassOfClass:[ORKSpatialSpanMemoryGameRecord class]]) { + ORKSpatialSpanMemoryGameTouchSample *touchSample1 = [[ORKSpatialSpanMemoryGameTouchSample alloc] init]; + touchSample1.timestamp = 1.0373990833177231; + touchSample1.targetIndex = 6; + touchSample1.location = CGPointMake(335, 330); + touchSample1.correct = true; + + ORKSpatialSpanMemoryGameTouchSample *touchSample2 = [[ORKSpatialSpanMemoryGameTouchSample alloc] init]; + touchSample2.timestamp = 1.270191750023514; + touchSample2.targetIndex = 7; + touchSample2.location = CGPointMake(334, 410); + touchSample2.correct = false; + + NSArray *touchSamples = @[touchSample1, touchSample2]; + [instance setValue:touchSamples forKey:@"touchSamples"]; + + CGRect rect1 = CGRectMake(44, 256, 114, 114); + CGRect rect2 = CGRectMake(44, 370, 114, 114); + NSArray *targetRects = @[[NSValue valueWithCGRect:rect1], [NSValue valueWithCGRect:rect2]]; + [instance setValue:targetRects forKey:@"targetRects"]; } // Serialization @@ -1179,8 +1220,6 @@ - (BOOL)applySomeValueToClassProperty:(ClassProperty *)p forObject:(id)instance } else if (p.propertyClass == [ORKNoAnswer class]) { ORKNoAnswer *value = (index ? [ORKDontKnowAnswer answer] : [_ORKTestNoAnswer answer]); [instance setValue:value forKey:p.propertyName]; - } else if (aClass == [ORKKeyValueStepModifier class] && [p.propertyName isEqual:@"keyValueMap"]) { - [instance setValue:@{@"prop": index?@"value":@"value1"} forKey:p.propertyName]; } else if (aClass == [ORKTableStep class] && [p.propertyName isEqual:@"items"]) { [instance setValue:@[index?@"item":@"item2"] forKey:p.propertyName]; } else if ([aClass isSubclassOfClass:ORK3DModelStep.class] && [p.propertyName isEqualToString:@"modelManager"]) { @@ -1368,8 +1407,8 @@ - (void)testEquality { if (ORKIsResearchKitClass(aClass) && [aClass conformsToProtocol:@protocol(NSSecureCoding)] && - [aClass conformsToProtocol:@protocol(NSCopying)]) { - + [aClass conformsToProtocol:@protocol(NSCopying)]) + { [classesWithSecureCodingAndCopying addObject:aClass]; } } @@ -1421,6 +1460,8 @@ - (void)testEquality { @"ORKTableStep.allowsSelection", @"ORKPDFViewerStep.actionBarOption", @"ORKPredicateFormItemVisibilityRule.predicate", // when testing equality, test_init instance of this rule has nonnull predicate which breaks assumptions about instance and copiedInstance in our test. So exclude this property for equality testing. + @"ORKPredicateStepNavigationRule.resultPredicates", // when testing equality, test_init instance of this rule has nonnull predicates which breaks assumptions about instance and copiedInstance in our test. So exclude this property for equality testing. + @"ORKKeyValueStepModifier.resultPredicate", // when testing equality, test_init instance of this rule has nonnull predicate which breaks assumptions about instance and copiedInstance in our test. So exclude this property for equality testing. @"ORKBodyItem.customButtonConfigurationHandler", @"ORKAccuracyStroopStep.actualDisplayColor", @"ORKAccuracyStroopResult.didSelectCorrectColor", diff --git a/ResearchKitTests/ORKRecorderConfiguration/ORKAccelerometerRecorderConfiguration.swift b/ResearchKitTests/ORKRecorderConfiguration/ORKAccelerometerRecorderConfiguration.swift new file mode 100644 index 0000000000..9f670125fb --- /dev/null +++ b/ResearchKitTests/ORKRecorderConfiguration/ORKAccelerometerRecorderConfiguration.swift @@ -0,0 +1,88 @@ +/* + Copyright (c) 2025, Apple Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. No license is granted to the trademarks of + the copyright holders even if such marks are included in this software. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import Testing + +@testable import ResearchKitActiveTask + +extension ORKRecorderConfigurationTests { + @Suite("ORKAccelerometerRecorderConfiguration") + struct ORKAccelerometerRecorderConfigurationTests { + let anyFrequency: Double = 1.0 + + @available(*, deprecated) // To avoid a warning about the function being tested being deprecated. + @Test( + """ + When passing nil to the deprecated recorderForStep:outputDirectory function's outputDirectory \ + parameter, the configuration's output directory is the one set in the configuration. + """ + ) + func recorderForStepOutputDirectory_DeprecationMechanism_outputDirectoryPassedIsNil() throws { + let expectedOutputDirectory = anyOutputDirectory + + let accelerometerRecorderConfiguration = ORKAccelerometerRecorderConfiguration( + identifier: anyIdentifier, + frequency: anyFrequency, + outputDirectory: anyOutputDirectory, + rollingFileSizeThreshold: anyRollingFileSizeThreshold + ) + + let recorder = accelerometerRecorderConfiguration.recorder(for: anyStep, outputDirectory: nil) + + #expect(recorder?.outputDirectory == expectedOutputDirectory) + } + + @available(*, deprecated) // To avoid a warning about the function being tested being deprecated. + @Test( + """ + When passing a value to the deprecated recorderForStep:outputDirectory function's outputDirectory \ + parameter, the configuration's output directory is that value. + """ + ) + func recorderForStepOutputDirectory_DeprecationMechanism_outputDirectoryPassedIsNonNil() throws { + let initialOutputDirectory = anyOutputDirectory + let expectedFinalOutputDirectory = anyOtherOutputDirectory + + let accelerometerRecorderConfiguration = ORKAccelerometerRecorderConfiguration( + identifier: anyIdentifier, + frequency: anyFrequency, + outputDirectory: initialOutputDirectory, + rollingFileSizeThreshold: anyRollingFileSizeThreshold + ) + + let recorder = accelerometerRecorderConfiguration.recorder( + for: anyStep, + outputDirectory: expectedFinalOutputDirectory + ) + + #expect(recorder?.outputDirectory == expectedFinalOutputDirectory) + } + } +} diff --git a/ResearchKitTests/ORKRecorderConfiguration/ORKAudioRecorderConfiguration.swift b/ResearchKitTests/ORKRecorderConfiguration/ORKAudioRecorderConfiguration.swift new file mode 100644 index 0000000000..45f30b49f8 --- /dev/null +++ b/ResearchKitTests/ORKRecorderConfiguration/ORKAudioRecorderConfiguration.swift @@ -0,0 +1,88 @@ +/* + Copyright (c) 2025, Apple Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. No license is granted to the trademarks of + the copyright holders even if such marks are included in this software. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import Testing + +@testable import ResearchKitActiveTask + +extension ORKRecorderConfigurationTests { + @Suite("ORKAudioRecorderConfiguration") + struct ORKAudioRecorderConfigurationTests { + let anyRecorderSettings: [String: Any] = [UUID().uuidString: UUID().uuidString] + + @available(*, deprecated) // To avoid a warning about the function being tested being deprecated. + @Test( + """ + When passing nil to the deprecated recorderForStep:outputDirectory function's outputDirectory \ + parameter, the configuration's output directory is the one set in the configuration. + """ + ) + func recorderForStepOutputDirectory_DeprecationMechanism_outputDirectoryPassedIsNil() throws { + let expectedOutputDirectory = anyOutputDirectory + + let audioRecorderConfiguration = ORKAudioRecorderConfiguration( + identifier: anyIdentifier, + recorderSettings: anyRecorderSettings, + outputDirectory: expectedOutputDirectory, + rollingFileSizeThreshold: anyRollingFileSizeThreshold + ) + + let recorder = audioRecorderConfiguration.recorder(for: anyStep, outputDirectory: nil) + + #expect(recorder?.outputDirectory == expectedOutputDirectory) + } + + @available(*, deprecated) // To avoid a warning about the function being tested being deprecated. + @Test( + """ + When passing a value to the deprecated recorderForStep:outputDirectory function's outputDirectory \ + parameter, the configuration's output directory is that value. + """ + ) + func recorderForStepOutputDirectory_DeprecationMechanism_outputDirectoryPassedIsNonNil() throws { + let initialOutputDirectory = anyOutputDirectory + let expectedFinalOutputDirectory = anyOtherOutputDirectory + + let audioRecorderConfiguration = ORKAudioRecorderConfiguration( + identifier: anyIdentifier, + recorderSettings: anyRecorderSettings, + outputDirectory: initialOutputDirectory, + rollingFileSizeThreshold: anyRollingFileSizeThreshold + ) + + let recorder = audioRecorderConfiguration.recorder( + for: anyStep, + outputDirectory: expectedFinalOutputDirectory + ) + + #expect(recorder?.outputDirectory == expectedFinalOutputDirectory) + } + } +} diff --git a/ResearchKitTests/ORKRecorderConfiguration/ORKAudioStreamerRecorderConfiguration.swift b/ResearchKitTests/ORKRecorderConfiguration/ORKAudioStreamerRecorderConfiguration.swift new file mode 100644 index 0000000000..054bd123d0 --- /dev/null +++ b/ResearchKitTests/ORKRecorderConfiguration/ORKAudioStreamerRecorderConfiguration.swift @@ -0,0 +1,84 @@ +/* + Copyright (c) 2025, Apple Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. No license is granted to the trademarks of + the copyright holders even if such marks are included in this software. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import Testing + +@testable import ResearchKitActiveTask + +extension ORKRecorderConfigurationTests { + @Suite("ORKAudioStreamerRecorderConfiguration") + struct ORKAudioStreamerRecorderConfigurationTests { + @available(*, deprecated) // To avoid a warning about the function being tested being deprecated. + @Test( + """ + When passing nil to the deprecated recorderForStep:outputDirectory function's outputDirectory \ + parameter, the configuration's output directory is the one set in the configuration. + """ + ) + func recorderForStepOutputDirectory_DeprecationMechanism_outputDirectoryPassedIsNil() throws { + let expectedOutputDirectory = anyOutputDirectory + + let audioStreamerRecorderConfiguration = ORKAudioStreamerConfiguration( + identifier: anyIdentifier, + outputDirectory: anyOutputDirectory, + rollingFileSizeThreshold: anyRollingFileSizeThreshold + ) + + let recorder = audioStreamerRecorderConfiguration.recorder(for: anyStep, outputDirectory: nil) + + #expect(recorder?.outputDirectory == expectedOutputDirectory) + } + + @available(*, deprecated) // To avoid a warning about the function being tested being deprecated. + @Test( + """ + When passing a value to the deprecated recorderForStep:outputDirectory function's outputDirectory \ + parameter, the configuration's output directory is that value. + """ + ) + func recorderForStepOutputDirectory_DeprecationMechanism_outputDirectoryPassedIsNonNil() throws { + let initialOutputDirectory = anyOutputDirectory + let anyOtherOutputDirectory = anyOtherOutputDirectory + + let audioStreamerRecorderConfiguration = ORKAudioStreamerConfiguration( + identifier: anyIdentifier, + outputDirectory: initialOutputDirectory, + rollingFileSizeThreshold: anyRollingFileSizeThreshold + ) + + let recorder = audioStreamerRecorderConfiguration.recorder( + for: anyStep, + outputDirectory: anyOtherOutputDirectory + ) + + #expect(recorder?.outputDirectory == anyOtherOutputDirectory) + } + } +} diff --git a/ResearchKitTests/ORKRecorderConfiguration/ORKDeviceMotionRecorderConfiguration.swift b/ResearchKitTests/ORKRecorderConfiguration/ORKDeviceMotionRecorderConfiguration.swift new file mode 100644 index 0000000000..4900c6dc8d --- /dev/null +++ b/ResearchKitTests/ORKRecorderConfiguration/ORKDeviceMotionRecorderConfiguration.swift @@ -0,0 +1,88 @@ +/* + Copyright (c) 2025, Apple Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. No license is granted to the trademarks of + the copyright holders even if such marks are included in this software. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import Testing + +@testable import ResearchKitActiveTask + +extension ORKRecorderConfigurationTests { + @Suite("ORKDeviceMotionRecorderConfiguration") + struct ORKDeviceMotionRecorderConfigurationTests { + let anyFrequency: Double = 1.0 + + @available(*, deprecated) // To avoid a warning about the function being tested being deprecated. + @Test( + """ + When passing nil to the deprecated recorderForStep:outputDirectory function's outputDirectory \ + parameter, the configuration's output directory is the one set in the configuration. + """ + ) + func recorderForStepOutputDirectory_DeprecationMechanism_outputDirectoryPassedIsNil() throws { + let expectedOutputDirectory = anyOutputDirectory + + let recorderConfiguration = ORKDeviceMotionRecorderConfiguration( + identifier: anyIdentifier, + frequency: anyFrequency, + outputDirectory: expectedOutputDirectory, + rollingFileSizeThreshold: anyRollingFileSizeThreshold + ) + + let recorder = recorderConfiguration.recorder(for: anyStep, outputDirectory: nil) + + #expect(recorder?.outputDirectory == expectedOutputDirectory) + } + + @available(*, deprecated) // To avoid a warning about the function being tested being deprecated. + @Test( + """ + When passing a value to the deprecated recorderForStep:outputDirectory function's outputDirectory \ + parameter, the configuration's output directory is that value. + """ + ) + func recorderForStepOutputDirectory_DeprecationMechanism_outputDirectoryPassedIsNonNil() throws { + let initialOutputDirectory = anyOutputDirectory + let expectedFinalOutputDirectory = anyOtherOutputDirectory + + let recorderConfiguration = ORKDeviceMotionRecorderConfiguration( + identifier: anyIdentifier, + frequency: anyFrequency, + outputDirectory: initialOutputDirectory, + rollingFileSizeThreshold: anyRollingFileSizeThreshold + ) + + let recorder = recorderConfiguration.recorder( + for: anyStep, + outputDirectory: expectedFinalOutputDirectory + ) + + #expect(recorder?.outputDirectory == expectedFinalOutputDirectory) + } + } +} diff --git a/ResearchKitTests/ORKRecorderConfiguration/ORKHealthClinicalTypeRecorderConfiguration.swift b/ResearchKitTests/ORKRecorderConfiguration/ORKHealthClinicalTypeRecorderConfiguration.swift new file mode 100644 index 0000000000..45cecd577b --- /dev/null +++ b/ResearchKitTests/ORKRecorderConfiguration/ORKHealthClinicalTypeRecorderConfiguration.swift @@ -0,0 +1,96 @@ +/* + Copyright (c) 2025, 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 ResearchKitActiveTask +import ResearchKitActiveTask_Private + + +import Testing + +@testable import ResearchKitActiveTask + +extension ORKRecorderConfigurationTests { + @Suite("ORKHealthClinicalTypeRecorderConfiguration") + struct ORKHealthClinicalTypeRecorderConfigurationTests { + let anyHealthClinicalType: HKClinicalType = .clinicalType(forIdentifier: .allergyRecord)! + let anyHealthFHIRResourceType: HKFHIRResourceType = .allergyIntolerance + + @available(*, deprecated) // To avoid a warning about the function being tested being deprecated. + @Test( + """ + When passing nil to the deprecated recorderForStep:outputDirectory function's outputDirectory \ + parameter, the configuration's output directory is the one set in the configuration. + """ + ) + func recorderForStepOutputDirectory_DeprecationMechanism_outputDirectoryPassedIsNil() throws { + let expectedOutputDirectory = anyOutputDirectory + + let recorderConfiguration = ORKHealthClinicalTypeRecorderConfiguration( + identifier: anyIdentifier, + healthClinicalType: anyHealthClinicalType, + healthFHIRResourceType: anyHealthFHIRResourceType, + outputDirectory: expectedOutputDirectory, + rollingFileSizeThreshold: anyRollingFileSizeThreshold + ) + + let recorder = recorderConfiguration.recorder(for: anyStep, outputDirectory: nil) + + #expect(recorder?.outputDirectory == expectedOutputDirectory) + } + + @available(*, deprecated) // To avoid a warning about the function being tested being deprecated. + @Test( + """ + When passing a value to the deprecated recorderForStep:outputDirectory function's outputDirectory \ + parameter, the configuration's output directory is that value. + """ + ) + func recorderForStepOutputDirectory_DeprecationMechanism_outputDirectoryPassedIsNonNil() throws { + let initialOutputDirectory = anyOutputDirectory + let expectedFinalOutputDirectory = anyOtherOutputDirectory + + let recorderConfiguration = ORKHealthClinicalTypeRecorderConfiguration( + identifier: anyIdentifier, + healthClinicalType: anyHealthClinicalType, + healthFHIRResourceType: anyHealthFHIRResourceType, + outputDirectory: initialOutputDirectory, + rollingFileSizeThreshold: anyRollingFileSizeThreshold + ) + + let recorder = recorderConfiguration.recorder( + for: anyStep, + outputDirectory: expectedFinalOutputDirectory + ) + + #expect(recorder?.outputDirectory == expectedFinalOutputDirectory) + } + } +} diff --git a/ResearchKitTests/ORKRecorderConfiguration/ORKHealthQuantityTypeRecordeConfiguration.swift b/ResearchKitTests/ORKRecorderConfiguration/ORKHealthQuantityTypeRecordeConfiguration.swift new file mode 100644 index 0000000000..55ea0c420f --- /dev/null +++ b/ResearchKitTests/ORKRecorderConfiguration/ORKHealthQuantityTypeRecordeConfiguration.swift @@ -0,0 +1,97 @@ +/* + Copyright (c) 2025, 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 ResearchKitActiveTask +import ResearchKitActiveTask_Private + + +import Testing + +@testable import ResearchKitActiveTask + +extension ORKRecorderConfigurationTests { + @Suite("ORKHealthQuantityTypeRecorderConfiguration") + struct ORKHealthQuantityTypeRecorderConfigurationTests { + let anyHealthQuantityType: HKQuantityType = .quantityType(forIdentifier: .activeEnergyBurned)! + let anyHealthFHIRResourceType: HKFHIRResourceType = .allergyIntolerance + let anyUnit: HKUnit = .gramUnit(with: .milli) + + @available(*, deprecated) // To avoid a warning about the function being tested being deprecated. + @Test( + """ + When passing nil to the deprecated recorderForStep:outputDirectory function's outputDirectory \ + parameter, the configuration's output directory is the one set in the configuration. + """ + ) + func recorderForStepOutputDirectory_DeprecationMechanism_outputDirectoryPassedIsNil() throws { + let expectedOutputDirectory = anyOutputDirectory + + let recorderConfiguration = ORKHealthQuantityTypeRecorderConfiguration( + identifier: anyIdentifier, + healthQuantityType: anyHealthQuantityType, + unit: anyUnit, + outputDirectory: expectedOutputDirectory, + rollingFileSizeThreshold: anyRollingFileSizeThreshold + ) + + let recorder = recorderConfiguration.recorder(for: anyStep, outputDirectory: nil) + + #expect(recorder?.outputDirectory == expectedOutputDirectory) + } + + @available(*, deprecated) // To avoid a warning about the function being tested being deprecated. + @Test( + """ + When passing a value to the deprecated recorderForStep:outputDirectory function's outputDirectory \ + parameter, the configuration's output directory is that value. + """ + ) + func recorderForStepOutputDirectory_DeprecationMechanism_outputDirectoryPassedIsNonNil() throws { + let initialOutputDirectory = anyOutputDirectory + let expectedFinalOutputDirectory = anyOtherOutputDirectory + + let recorderConfiguration = ORKHealthQuantityTypeRecorderConfiguration( + identifier: anyIdentifier, + healthQuantityType: anyHealthQuantityType, + unit: anyUnit, + outputDirectory: initialOutputDirectory, + rollingFileSizeThreshold: anyRollingFileSizeThreshold + ) + + let recorder = recorderConfiguration.recorder( + for: anyStep, + outputDirectory: expectedFinalOutputDirectory + ) + + #expect(recorder?.outputDirectory == expectedFinalOutputDirectory) + } + } +} + diff --git a/ResearchKitTests/ORKRecorderConfiguration/ORKLocationRecorderConfiguration.swift b/ResearchKitTests/ORKRecorderConfiguration/ORKLocationRecorderConfiguration.swift new file mode 100644 index 0000000000..67b2065d7c --- /dev/null +++ b/ResearchKitTests/ORKRecorderConfiguration/ORKLocationRecorderConfiguration.swift @@ -0,0 +1,84 @@ +/* + Copyright (c) 2025, Apple Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. No license is granted to the trademarks of + the copyright holders even if such marks are included in this software. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import Testing + +@testable import ResearchKitActiveTask + +extension ORKRecorderConfigurationTests { + @Suite("ORKLocationRecorderConfiguration") + struct ORKLocationRecorderConfigurationTests { + @available(*, deprecated) // To avoid a warning about the function being tested being deprecated. + @Test( + """ + When passing nil to the deprecated recorderForStep:outputDirectory function's outputDirectory \ + parameter, the configuration's output directory is the one set in the configuration. + """ + ) + func recorderForStepOutputDirectory_DeprecationMechanism_outputDirectoryPassedIsNil() throws { + let expectedOutputDirectory = anyOutputDirectory + + let recorderConfiguration = ORKLocationRecorderConfiguration( + identifier: anyIdentifier, + outputDirectory: expectedOutputDirectory, + rollingFileSizeThreshold: anyRollingFileSizeThreshold + ) + + let recorder = recorderConfiguration.recorder(for: anyStep, outputDirectory: nil) + + #expect(recorder?.outputDirectory == expectedOutputDirectory) + } + + @available(*, deprecated) // To avoid a warning about the function being tested being deprecated. + @Test( + """ + When passing a value to the deprecated recorderForStep:outputDirectory function's outputDirectory \ + parameter, the configuration's output directory is that value. + """ + ) + func recorderForStepOutputDirectory_DeprecationMechanism_outputDirectoryPassedIsNonNil() throws { + let initialOutputDirectory = anyOutputDirectory + let expectedFinalOutputDirectory = anyOtherOutputDirectory + + let recorderConfiguration = ORKLocationRecorderConfiguration( + identifier: anyIdentifier, + outputDirectory: initialOutputDirectory, + rollingFileSizeThreshold: anyRollingFileSizeThreshold + ) + + let recorder = recorderConfiguration.recorder( + for: anyStep, + outputDirectory: expectedFinalOutputDirectory + ) + + #expect(recorder?.outputDirectory == expectedFinalOutputDirectory) + } + } +} diff --git a/ResearchKitTests/ORKRecorderConfiguration/ORKPedometerRecorderConfiguration.swift b/ResearchKitTests/ORKRecorderConfiguration/ORKPedometerRecorderConfiguration.swift new file mode 100644 index 0000000000..75187adfba --- /dev/null +++ b/ResearchKitTests/ORKRecorderConfiguration/ORKPedometerRecorderConfiguration.swift @@ -0,0 +1,84 @@ +/* + Copyright (c) 2025, Apple Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. No license is granted to the trademarks of + the copyright holders even if such marks are included in this software. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import Testing +import ResearchKitActiveTask +@testable import ResearchKitActiveTask + +extension ORKRecorderConfigurationTests { + @Suite("ORKPedometerRecorderConfiguration") + struct ORKPedometerRecorderConfigurationTests { + @available(*, deprecated) // To avoid a warning about the function being tested being deprecated. + @Test( + """ + When passing nil to the deprecated recorderForStep:outputDirectory function's outputDirectory \ + parameter, the configuration's output directory is the one set in the configuration. + """ + ) + func recorderForStepOutputDirectory_DeprecationMechanism_outputDirectoryPassedIsNil() throws { + let expectedOutputDirectory = anyOutputDirectory + + let pedometerRecorderConfiguration = ORKPedometerRecorderConfiguration( + identifier: anyIdentifier, + outputDirectory: expectedOutputDirectory, + rollingFileSizeThreshold: anyRollingFileSizeThreshold + ) + + let recorder = pedometerRecorderConfiguration.recorder(for: anyStep, outputDirectory: nil) + + #expect(recorder?.outputDirectory == expectedOutputDirectory) + } + + @available(*, deprecated) // To avoid a warning about the function being tested being deprecated. + @Test( + """ + When passing a value to the deprecated recorderForStep:outputDirectory function's outputDirectory \ + parameter, the configuration's output directory is that value. + """ + ) + func recorderForStepOutputDirectory_DeprecationMechanism_outputDirectoryPassedIsNonNil() throws { + let initialOutputDirectory = anyOutputDirectory + let expectedFinalOutputDirectory = anyOtherOutputDirectory + + let pedometerConfiguration = ORKPedometerRecorderConfiguration( + identifier: anyIdentifier, + outputDirectory: initialOutputDirectory, + rollingFileSizeThreshold: anyRollingFileSizeThreshold + ) + + let recorder = pedometerConfiguration.recorder( + for: anyStep, + outputDirectory: expectedFinalOutputDirectory + ) + + #expect(recorder?.outputDirectory == expectedFinalOutputDirectory) + } + } +} diff --git a/ResearchKitTests/ORKRecorderConfiguration/ORKRecorderConfiguration.swift b/ResearchKitTests/ORKRecorderConfiguration/ORKRecorderConfiguration.swift new file mode 100644 index 0000000000..4d2f5c3098 --- /dev/null +++ b/ResearchKitTests/ORKRecorderConfiguration/ORKRecorderConfiguration.swift @@ -0,0 +1,44 @@ +/* + Copyright (c) 2025, Apple Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. No license is granted to the trademarks of + the copyright holders even if such marks are included in this software. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import Testing + +@testable import ResearchKitActiveTask + +@Suite("Deprecation mechanism for recorderForStep:outputDirectory") +struct ORKRecorderConfigurationTests { + + // Test constants + static let anyIdentifier: String = UUID().uuidString + static let anyOutputDirectory: URL? = URL(string: UUID().uuidString) + static let anyOtherOutputDirectory: URL? = URL(string: UUID().uuidString) + static let anyStep: ORKStep = .init(identifier: UUID().uuidString) + static let anyRollingFileSizeThreshold: Int = Int.random(in: 0...Int.max) +} diff --git a/ResearchKitTests/ORKRecorderTests.m b/ResearchKitTests/ORKRecorderTests.m index 7cc46a5abe..89c5ccc47b 100644 --- a/ResearchKitTests/ORKRecorderTests.m +++ b/ResearchKitTests/ORKRecorderTests.m @@ -197,6 +197,10 @@ - (NSTimeInterval)timestamp { return 1000.0; } +- (NSTimeInterval)timestampSince1970 { + return 1200.0; +} + @end @@ -291,6 +295,10 @@ - (NSTimeInterval)timestamp { return 1000.0; } +- (NSTimeInterval)timestampSince1970 { + return 1200.0; +} + - (CMAttitude *)attitude { return [ORKMockAttitude new]; } @@ -329,8 +337,9 @@ @interface ORKRecorderTests : XCTestCase @implementation ORKRecorderTests { NSString *_outputPath; + NSNumber *_rollingFileSizeThreshold; ORKRecorder *_recorder; - ORKResult *_result; + NSArray *_result; NSArray *_items; } @@ -350,7 +359,7 @@ - (void)setUp { ORK_Log_Error("Failed to create directory %@", error); } } - + _rollingFileSizeThreshold = @5000000; _recorder = nil; _result = nil; _items = nil; @@ -360,10 +369,10 @@ - (void)tearDown { [super tearDown]; } -- (void)recorder:(ORKRecorder *)recorder didCompleteWithResult:(ORKResult *)result { - ORK_Log_Debug("didCompleteWithResult: %@", result); +- (void)recorder:(ORKRecorder *)recorder didCompleteWithResults:(NSArray *)results { + ORK_Log_Debug("didCompleteWithResults: %@", results); _recorder = recorder; - _result = result; + _result = results; } - (void)recorder:(ORKRecorder *)recorder didFailWithError:(NSError *)error { @@ -373,8 +382,7 @@ - (void)recorder:(ORKRecorder *)recorder didFailWithError:(NSError *)error { } - (ORKRecorder *)createRecorder:(ORKRecorderConfiguration *)recorderConfiguration { - ORKRecorder *recorder = [recorderConfiguration recorderForStep:[[ORKStep alloc] initWithIdentifier:@"step"] - outputDirectory:[NSURL fileURLWithPath:_outputPath]]; + ORKRecorder *recorder = [recorderConfiguration recorderForStep:[[ORKStep alloc] initWithIdentifier:@"step"]]; XCTAssert([recorder.identifier isEqualToString:recorderConfiguration.identifier], @""); recorder.delegate = self; return recorder; @@ -383,31 +391,37 @@ - (ORKRecorder *)createRecorder:(ORKRecorderConfiguration *)recorderConfiguratio - (void)checkResult { XCTAssertNotNil(_result, @""); - XCTAssert([_result isKindOfClass:[ORKFileResult class]], @""); - XCTAssert([_recorder.identifier isEqualToString:_result.identifier], @""); - - ORKFileResult *fileResult = (ORKFileResult *)_result; - - NSError *error; - NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:[NSData dataWithContentsOfURL:fileResult.fileURL ] options:(NSJSONReadingOptions)0 error:&error]; - XCTAssertNil(error, @""); - XCTAssertNotNil(dict, @""); + XCTAssert([_result isKindOfClass:[NSArray class]], @""); - NSArray *items = dict[@"items"]; - XCTAssertEqual(items.count, kNumberOfSamples, @""); + for (ORKFileResult *fileResult in _result) { + XCTAssert([_recorder.identifier isEqualToString:fileResult.identifier], @""); + } - _items = items; + for (ORKFileResult *fileResult in _result) { + NSError *error; + NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:[NSData dataWithContentsOfURL:fileResult.fileURL ] options:(NSJSONReadingOptions)0 error:&error]; + XCTAssertNil(error, @""); + XCTAssertNotNil(dict, @""); + + NSArray *items = dict[@"items"]; + XCTAssertEqual(items.count, kNumberOfSamples, @""); + + _items = items; + } } #if ORK_FEATURE_CLLOCATIONMANAGER_AUTHORIZATION - (void)testLocationRecorder { - ORKLocationRecorder *recorder = (ORKLocationRecorder *)[self createRecorder:[[ORKLocationRecorderConfiguration alloc] initWithIdentifier:@"location"]]; + ORKLocationRecorder *recorder = (ORKLocationRecorder *)[self createRecorder:[[ORKLocationRecorderConfiguration alloc] initWithIdentifier:@"location" + outputDirectory:[NSURL fileURLWithPath:_outputPath] + rollingFileSizeThreshold:_rollingFileSizeThreshold]]; XCTAssertTrue([recorder isKindOfClass:[ORKLocationRecorder class]], @""); recorder = [[ORKMockLocationRecorder alloc] initWithIdentifier:@"location" step:recorder.step - outputDirectory:recorder.outputDirectory]; + outputDirectory:recorder.outputDirectory + rollingFileSizeThreshold:recorder.rollingFileSizeThreshold]; recorder.delegate = self; [recorder start]; @@ -473,14 +487,21 @@ - (void)testLocationRecorder { - (void)testAccelerometerRecorder { - ORKAccelerometerRecorderConfiguration *recorderConfiguration = [[ORKAccelerometerRecorderConfiguration alloc] initWithIdentifier:@"accelerometer" frequency:60.0]; + ORKAccelerometerRecorderConfiguration *recorderConfiguration = [[ORKAccelerometerRecorderConfiguration alloc] initWithIdentifier:@"accelerometer" + frequency:60.0 + outputDirectory:[NSURL fileURLWithPath:_outputPath] + rollingFileSizeThreshold:_rollingFileSizeThreshold]; Class recorderClass = [ORKAccelerometerRecorder class]; ORKAccelerometerRecorder *recorder = (ORKAccelerometerRecorder *)[self createRecorder:recorderConfiguration]; XCTAssertTrue([recorder isKindOfClass:recorderClass], @""); XCTAssertTrue([recorder.identifier isEqualToString:recorderConfiguration.identifier], @""); - ORKMockAccelerometerRecorder *newRecorder = [[ORKMockAccelerometerRecorder alloc] initWithIdentifier:@"accelerometer" frequency:recorder.frequency step:recorder.step outputDirectory:recorder.outputDirectory]; + ORKMockAccelerometerRecorder *newRecorder = [[ORKMockAccelerometerRecorder alloc] initWithIdentifier:@"accelerometer" + frequency:recorder.frequency + step:recorder.step + outputDirectory:recorder.outputDirectory + rollingFileSizeThreshold:recorder.rollingFileSizeThreshold]; newRecorder.delegate = self; ORKMockMotionManager *manager = [ORKMockMotionManager new]; @@ -509,6 +530,8 @@ - (void)testAccelerometerRecorder { for (NSDictionary *sample in _items) { XCTAssertTrue(ork_doubleEqual(data.timestamp, ((NSNumber *)sample[@"timestamp"]).doubleValue), @""); + XCTAssertTrue(ork_doubleEqual(data.timestampSince1970, ((NSNumber *)sample[@"timestampSince1970"]).doubleValue), @""); + XCTAssertTrue(ork_doubleEqual(data.acceleration.x, ((NSNumber *)sample[@"x"]).doubleValue), @""); XCTAssertTrue(ork_doubleEqual(data.acceleration.y, ((NSNumber *)sample[@"y"]).doubleValue), @""); XCTAssertTrue(ork_doubleEqual(data.acceleration.z, ((NSNumber *)sample[@"z"]).doubleValue), @""); @@ -517,13 +540,20 @@ - (void)testAccelerometerRecorder { - (void)testDeviceMotionRecorder { - ORKDeviceMotionRecorderConfiguration *recorderConfiguration = [[ORKDeviceMotionRecorderConfiguration alloc] initWithIdentifier:@"deviceMotion" frequency:60.0]; + ORKDeviceMotionRecorderConfiguration *recorderConfiguration = [[ORKDeviceMotionRecorderConfiguration alloc] initWithIdentifier:@"deviceMotion" + frequency:60.0 + outputDirectory:[NSURL fileURLWithPath:_outputPath] + rollingFileSizeThreshold:_rollingFileSizeThreshold]; Class recorderClass = [ORKDeviceMotionRecorder class]; ORKDeviceMotionRecorder *recorder = (ORKDeviceMotionRecorder *)[self createRecorder:recorderConfiguration]; XCTAssertTrue([recorder isKindOfClass:recorderClass], @""); - recorder = [[ORKMockDeviceMotionRecorder alloc] initWithIdentifier:@"deviceMotion" frequency:recorder.frequency step:recorder.step outputDirectory:recorder.outputDirectory]; + recorder = [[ORKMockDeviceMotionRecorder alloc] initWithIdentifier:@"deviceMotion" + frequency:recorder.frequency + step:recorder.step + outputDirectory:recorder.outputDirectory + rollingFileSizeThreshold:recorder.rollingFileSizeThreshold]; recorder.delegate = self; ORKMockMotionManager *manager = [ORKMockMotionManager new]; [(ORKMockAccelerometerRecorder*)recorder setMockManager:manager]; @@ -540,6 +570,7 @@ - (void)testDeviceMotionRecorder { for (NSDictionary *sample in _items) { XCTAssertTrue(ork_doubleEqual(motion.timestamp, ((NSNumber *)sample[@"timestamp"]).doubleValue), @""); + XCTAssertTrue(ork_doubleEqual(motion.timestampSince1970, ((NSNumber *)sample[@"timestampSince1970"]).doubleValue), @""); XCTAssertTrue(ork_doubleEqual(motion.attitude.quaternion.x, ((NSNumber *)sample[@"attitude"][@"x"]).doubleValue), @""); XCTAssertTrue(ork_doubleEqual(motion.attitude.quaternion.y, ((NSNumber *)sample[@"attitude"][@"y"]).doubleValue), @""); @@ -568,11 +599,16 @@ - (void)testDeviceMotionRecorder { - (void)testPedometerRecorder { Class recorderClass = [ORKPedometerRecorder class]; - ORKPedometerRecorder *recorder = (ORKPedometerRecorder *)[self createRecorder:[[ORKPedometerRecorderConfiguration alloc] initWithIdentifier:@"pedometer"]]; + ORKPedometerRecorder *recorder = (ORKPedometerRecorder *)[self createRecorder:[[ORKPedometerRecorderConfiguration alloc] initWithIdentifier:@"pedometer" + outputDirectory:[NSURL fileURLWithPath:_outputPath] + rollingFileSizeThreshold:_rollingFileSizeThreshold]]; XCTAssertTrue([recorder isKindOfClass:recorderClass], @""); - recorder = [[ORKMockPedometerRecorder alloc] initWithIdentifier:@"pedometer" step:recorder.step outputDirectory:recorder.outputDirectory]; + recorder = [[ORKMockPedometerRecorder alloc] initWithIdentifier:@"pedometer" + step:recorder.step + outputDirectory:recorder.outputDirectory + rollingFileSizeThreshold:recorder.rollingFileSizeThreshold]; recorder.delegate = self; ORKMockPedometer *pedometer = [ORKMockPedometer new]; [(ORKMockPedometerRecorder*)recorder setMockPedometer:pedometer]; @@ -602,7 +638,9 @@ - (void)testPedometerRecorder { - (void)testTouchRecorder { Class recorderClass = [ORKTouchRecorder class]; - ORKTouchRecorder *recorder = (ORKTouchRecorder *)[self createRecorder:[[ORKTouchRecorderConfiguration alloc] initWithIdentifier:@"touch"]]; + ORKTouchRecorder *recorder = (ORKTouchRecorder *)[self createRecorder:[[ORKTouchRecorderConfiguration alloc] initWithIdentifier:@"touch" + outputDirectory:[NSURL fileURLWithPath:_outputPath] + rollingFileSizeThreshold:0]]; XCTAssertTrue([recorder isKindOfClass:recorderClass], @""); diff --git a/ResearchKitTests/ORKStepViewControllerTests.swift b/ResearchKitTests/ORKStepViewControllerTests.swift index 93ee12c6f1..8aae87e6db 100644 --- a/ResearchKitTests/ORKStepViewControllerTests.swift +++ b/ResearchKitTests/ORKStepViewControllerTests.swift @@ -227,7 +227,7 @@ class ORKStepViewControllerTests: XCTestCase { } recorderExpectation = expectation(description: "ORKStepViewController notifies delegate that it's recorder failed") - let recorder = ORKRecorder(identifier: "RECORDER", step: nil, outputDirectory: nil) + let recorder = ORKRecorder(identifier: "RECORDER", step: nil) testController!.delegate!.stepViewController(testController, recorder: recorder, didFailWithError: TestError.recorderError) waitForExpectations(timeout: 10) { (error) in diff --git a/ResearchKitTests/samples.bundle/ORKAccelerometerRecorderConfiguration.json b/ResearchKitTests/samples.bundle/ORKAccelerometerRecorderConfiguration.json index 01fa96f38d..d8531299d5 100644 --- a/ResearchKitTests/samples.bundle/ORKAccelerometerRecorderConfiguration.json +++ b/ResearchKitTests/samples.bundle/ORKAccelerometerRecorderConfiguration.json @@ -1 +1 @@ -{"identifier":"","_class":"ORKAccelerometerRecorderConfiguration","frequency":0} \ No newline at end of file +{"_class":"ORKAccelerometerRecorderConfiguration","identifier":"","outputDirectory":"file:\/\/\/usr\/","rollingFileSizeThreshold":5000000,"frequency":0} diff --git a/ResearchKitTests/samples.bundle/ORKAmslerGridResult.json b/ResearchKitTests/samples.bundle/ORKAmslerGridResult.json index a9cd453748..72ca4a14ca 100644 --- a/ResearchKitTests/samples.bundle/ORKAmslerGridResult.json +++ b/ResearchKitTests/samples.bundle/ORKAmslerGridResult.json @@ -1 +1,26 @@ -{"_class":"ORKAmslerGridResult","endDate":"2019-05-27T00:35:06-0700","startDate":"2019-05-27T00:35:06-0700","eyeSide":0,"identifier":"","image":{"imageName":"F4FE0CA6-169B-4F8C-A902-A3328D686153"},"path":[],"userInfo":{}} +{ + "_class":"ORKAmslerGridResult", + "endDate":"2019-05-27T00:35:06-0700", + "startDate":"2019-05-27T00:35:06-0700", + "identifier":"", + "userInfo":{}, + "eyeSide":0, + "imageFileResult" : { + "_class":"ORKFileResult", + "endDate":"2019-05-27T00:35:06-0700", + "startDate":"2019-05-27T00:35:06-0700", + "contentType":"", + "identifier":"", + "userInfo":{}, + "fileName":"" + }, + "drawingPathFileResult" : { + "_class":"ORKFileResult", + "endDate":"2019-05-27T00:35:06-0700", + "startDate":"2019-05-27T00:35:06-0700", + "contentType":"", + "identifier":"", + "userInfo":{}, + "fileName":"" + } +} diff --git a/ResearchKitTests/samples.bundle/ORKAudioRecorderConfiguration.json b/ResearchKitTests/samples.bundle/ORKAudioRecorderConfiguration.json index 1bee1239ae..e53ac5e783 100644 --- a/ResearchKitTests/samples.bundle/ORKAudioRecorderConfiguration.json +++ b/ResearchKitTests/samples.bundle/ORKAudioRecorderConfiguration.json @@ -1 +1 @@ -{"identifier":"","_class":"ORKAudioRecorderConfiguration","recorderSettings":{}} \ No newline at end of file +{"_class":"ORKAudioRecorderConfiguration","identifier":"","rollingFileSizeThreshold":5000000,"outputDirectory":"file:\/\/\/usr\/","recorderSettings":{}} diff --git a/ResearchKitTests/samples.bundle/ORKAudioStreamerConfiguration.json b/ResearchKitTests/samples.bundle/ORKAudioStreamerConfiguration.json index 3678e3cf06..ba61bb4721 100644 --- a/ResearchKitTests/samples.bundle/ORKAudioStreamerConfiguration.json +++ b/ResearchKitTests/samples.bundle/ORKAudioStreamerConfiguration.json @@ -1 +1 @@ -{"_class":"ORKAudioStreamerConfiguration","identifier":""} +{"_class":"ORKAudioStreamerConfiguration","identifier":"","outputDirectory":"file:\/\/\/usr\/","rollingFileSizeThreshold":5000000} diff --git a/ResearchKitTests/samples.bundle/ORKDeviceMotionRecorderConfiguration.json b/ResearchKitTests/samples.bundle/ORKDeviceMotionRecorderConfiguration.json index 7dfd3b70c1..23f713efa3 100644 --- a/ResearchKitTests/samples.bundle/ORKDeviceMotionRecorderConfiguration.json +++ b/ResearchKitTests/samples.bundle/ORKDeviceMotionRecorderConfiguration.json @@ -1 +1 @@ -{"identifier":"","_class":"ORKDeviceMotionRecorderConfiguration","frequency":0} \ No newline at end of file +{"_class":"ORKDeviceMotionRecorderConfiguration","identifier":"","outputDirectory":"file:\/\/\/usr\/","rollingFileSizeThreshold":5000000,"frequency":0} diff --git a/ResearchKitTests/samples.bundle/ORKHealthClinicalTypeRecorderConfiguration.json b/ResearchKitTests/samples.bundle/ORKHealthClinicalTypeRecorderConfiguration.json index ba71844e36..d690466e01 100644 --- a/ResearchKitTests/samples.bundle/ORKHealthClinicalTypeRecorderConfiguration.json +++ b/ResearchKitTests/samples.bundle/ORKHealthClinicalTypeRecorderConfiguration.json @@ -1 +1 @@ -{"healthClinicalType":"HKClinicalTypeIdentifierAllergyRecord","_class":"ORKHealthClinicalTypeRecorderConfiguration","healthFHIRResourceType":"","identifier":""} \ No newline at end of file +{"_class":"ORKHealthClinicalTypeRecorderConfiguration","identifier":"","rollingFileSizeThreshold":5000000,"outputDirectory":"file:\/\/\/usr\/","healthFHIRResourceType":"","healthClinicalType":"HKClinicalTypeIdentifierAllergyRecord"} diff --git a/ResearchKitTests/samples.bundle/ORKHealthQuantityTypeRecorderConfiguration.json b/ResearchKitTests/samples.bundle/ORKHealthQuantityTypeRecorderConfiguration.json index 59145b991d..bed7d44ca8 100644 --- a/ResearchKitTests/samples.bundle/ORKHealthQuantityTypeRecorderConfiguration.json +++ b/ResearchKitTests/samples.bundle/ORKHealthQuantityTypeRecorderConfiguration.json @@ -1 +1 @@ -{"_class":"ORKHealthQuantityTypeRecorderConfiguration","identifier":"","quantityType":"HKQuantityTypeIdentifierBodyMass","unit":"kg"} +{"_class":"ORKHealthQuantityTypeRecorderConfiguration","identifier":"","outputDirectory":"file:\/\/\/usr\/","rollingFileSizeThreshold":5000000,"quantityType":"HKQuantityTypeIdentifierBodyMass","unit":"kg"} diff --git a/ResearchKitTests/samples.bundle/ORKKeyValueStepModifier.json b/ResearchKitTests/samples.bundle/ORKKeyValueStepModifier.json new file mode 100644 index 0000000000..4ae4a459d4 --- /dev/null +++ b/ResearchKitTests/samples.bundle/ORKKeyValueStepModifier.json @@ -0,0 +1 @@ +{"_class":"ORKKeyValueStepModifier","resultPredicateFormat":"SUBQUERY(SELF, $x, $x.identifier == $ORK_TASK_IDENTIFIER AND SUBQUERY($x.results, $y, $y.identifier == \"test\" AND $y.isPreviousResult == 0 AND SUBQUERY($y.results, $z, $z.identifier == \"test\" AND $z.answer == 1).@count > 0).@count > 0).@count > 0","keyValueMap":{"title":"Yes"}} diff --git a/ResearchKitTests/samples.bundle/ORKLocationRecorderConfiguration.json b/ResearchKitTests/samples.bundle/ORKLocationRecorderConfiguration.json index 2315bb937d..f877a6463b 100644 --- a/ResearchKitTests/samples.bundle/ORKLocationRecorderConfiguration.json +++ b/ResearchKitTests/samples.bundle/ORKLocationRecorderConfiguration.json @@ -1 +1 @@ -{"_class":"ORKLocationRecorderConfiguration","identifier":""} \ No newline at end of file +{"_class":"ORKLocationRecorderConfiguration","identifier":"","outputDirectory":"file:\/\/\/usr\/","rollingFileSizeThreshold":5000000} diff --git a/ResearchKitTests/samples.bundle/ORKPedometerRecorderConfiguration.json b/ResearchKitTests/samples.bundle/ORKPedometerRecorderConfiguration.json index f42a2bf8d6..9b9da19a69 100644 --- a/ResearchKitTests/samples.bundle/ORKPedometerRecorderConfiguration.json +++ b/ResearchKitTests/samples.bundle/ORKPedometerRecorderConfiguration.json @@ -1 +1 @@ -{"_class":"ORKPedometerRecorderConfiguration","identifier":""} \ No newline at end of file +{"_class":"ORKPedometerRecorderConfiguration","identifier":"","outputDirectory":"file:\/\/\/usr\/","rollingFileSizeThreshold":5000000} diff --git a/ResearchKitTests/samples.bundle/ORKPredicateStepNavigationRule.json b/ResearchKitTests/samples.bundle/ORKPredicateStepNavigationRule.json index 2bc34357fc..7adce84f3d 100644 --- a/ResearchKitTests/samples.bundle/ORKPredicateStepNavigationRule.json +++ b/ResearchKitTests/samples.bundle/ORKPredicateStepNavigationRule.json @@ -1 +1 @@ -{"defaultStepIdentifier":"","destinationStepIdentifiers":[],"additionalTaskResults":[],"_class":"ORKPredicateStepNavigationRule","resultPredicates":[]} \ No newline at end of file +{"_class":"ORKPredicateStepNavigationRule","defaultStepIdentifier":"","destinationStepIdentifiers":[],"additionalTaskResults":[],"resultPredicateFormats":["SUBQUERY(SELF, $x, $x.identifier == $ORK_TASK_IDENTIFIER AND SUBQUERY($x.results, $y, $y.identifier == \"test\" AND $y.isPreviousResult == 0 AND SUBQUERY($y.results, $z, $z.identifier == \"test\" AND $z.answer == 1).@count > 0).@count > 0).@count > 0"]} diff --git a/ResearchKitTests/samples.bundle/ORKReactionTimeResult.json b/ResearchKitTests/samples.bundle/ORKReactionTimeResult.json index f1fc8fc129..b3e69334f4 100644 --- a/ResearchKitTests/samples.bundle/ORKReactionTimeResult.json +++ b/ResearchKitTests/samples.bundle/ORKReactionTimeResult.json @@ -1 +1 @@ -{"_class":"ORKReactionTimeResult","endDate":"2019-05-27T00:35:06-0700","startDate":"2019-05-27T00:35:06-0700","identifier":"","fileResult":{"endDate":"2019-05-27T00:35:06-0700","_class":"ORKFileResult","startDate":"2019-05-27T00:35:06-0700"},"timestamp":0,"userInfo":{}} \ No newline at end of file +{"_class":"ORKReactionTimeResult","endDate":"2019-05-27T00:35:06-0700","startDate":"2019-05-27T00:35:06-0700","identifier":"","fileResults":[{"endDate":"2019-05-27T00:35:06-0700","_class":"ORKFileResult","startDate":"2019-05-27T00:35:06-0700"}],"timestamp":0,"userInfo":{}} diff --git a/ResearchKitTests/samples.bundle/ORKRecorderConfiguration.json b/ResearchKitTests/samples.bundle/ORKRecorderConfiguration.json index 52c4fb61a7..c2094e9834 100644 --- a/ResearchKitTests/samples.bundle/ORKRecorderConfiguration.json +++ b/ResearchKitTests/samples.bundle/ORKRecorderConfiguration.json @@ -1 +1 @@ -{"_class":"ORKRecorderConfiguration","identifier":""} \ No newline at end of file +{"_class":"ORKRecorderConfiguration","identifier":"","outputDirectory":"file:\/\/\/usr\/","rollingFileSizeThreshold":5000000} diff --git a/ResearchKitTests/samples.bundle/ORKSpatialSpanMemoryGameRecord.json b/ResearchKitTests/samples.bundle/ORKSpatialSpanMemoryGameRecord.json index 95587487fa..935fee0e36 100644 --- a/ResearchKitTests/samples.bundle/ORKSpatialSpanMemoryGameRecord.json +++ b/ResearchKitTests/samples.bundle/ORKSpatialSpanMemoryGameRecord.json @@ -1 +1,12 @@ -{"touchSamples":[],"score":0,"sequence":[],"_class":"ORKSpatialSpanMemoryGameRecord","gameStatus":0,"seed":0,"gameSize":0,"targetRects":[]} \ No newline at end of file +{"_class":"ORKSpatialSpanMemoryGameRecord","seed":0,"sequence":[],"gameSize":0,"targetRects":[{"origin":{"x":44,"y":256.66666666666669},"size":{"h":114,"w":114}}, + {"origin":{"x":44,"y":370.66666666666669},"size":{"h":114,"w":114}}, + {"origin":{"x":44,"y":484.66666666666669},"size":{"h":114.00000000000006,"w":114}}, + {"origin":{"x":158,"y":256.66666666666669},"size":{"h":114,"w":114}}, + {"origin":{"x":158,"y":370.66666666666669},"size":{"h":114,"w":114}}, + {"origin":{"x":158,"y":484.66666666666669},"size":{"h":114.00000000000006,"w":114}}, + {"origin":{"x":272,"y":256.66666666666669},"size":{"h":114,"w":114}}, + {"origin":{"x":272,"y":370.66666666666669},"size":{"h":114,"w":114}}, + {"origin":{"x":272,"y":484.66666666666669},"size":{"h":114.00000000000006,"w":114}} +],"touchSamples":[{"_class":"ORKSpatialSpanMemoryGameTouchSample","correct":true,"location":{"x":335,"y":330},"targetIndex":6,"timestamp":1.0373990833177231}, + {"_class":"ORKSpatialSpanMemoryGameTouchSample","correct":true,"location":{"x":334,"y":410.66665649414062},"targetIndex":7,"timestamp":1.270191750023514}, + {"_class":"ORKSpatialSpanMemoryGameTouchSample","correct":true,"location":{"x":220.33332824707031,"y":299},"targetIndex":3,"timestamp":1.7025653750170022}],"gameStatus":0,"score":0} diff --git a/ResearchKitTests/samples.bundle/ORKStreamingAudioRecorderConfiguration.json b/ResearchKitTests/samples.bundle/ORKStreamingAudioRecorderConfiguration.json index 5f2985421f..f7dfcd4c5a 100644 --- a/ResearchKitTests/samples.bundle/ORKStreamingAudioRecorderConfiguration.json +++ b/ResearchKitTests/samples.bundle/ORKStreamingAudioRecorderConfiguration.json @@ -1 +1 @@ -{"_class":"ORKStreamingAudioRecorderConfiguration","identifier":""} \ No newline at end of file +{"_class":"ORKStreamingAudioRecorderConfiguration","identifier":"","outputDirectory":"file:\/\/\/usr\/","rollingFileSizeThreshold":5000000} diff --git a/ResearchKitTests/samples.bundle/ORKTouchRecorderConfiguration.json b/ResearchKitTests/samples.bundle/ORKTouchRecorderConfiguration.json index f154626304..117919a770 100644 --- a/ResearchKitTests/samples.bundle/ORKTouchRecorderConfiguration.json +++ b/ResearchKitTests/samples.bundle/ORKTouchRecorderConfiguration.json @@ -1 +1 @@ -{"_class":"ORKTouchRecorderConfiguration","identifier":""} \ No newline at end of file +{"_class":"ORKTouchRecorderConfiguration","identifier":"","outputDirectory":"file:\/\/\/usr\/","rollingFileSizeThreshold":5000000} diff --git a/ResearchKitUI/Common/Answer Format/Control Views/ORKScaleSliderView.m b/ResearchKitUI/Common/Answer Format/Control Views/ORKScaleSliderView.m index 8fb44b838f..e168fb8677 100644 --- a/ResearchKitUI/Common/Answer Format/Control Views/ORKScaleSliderView.m +++ b/ResearchKitUI/Common/Answer Format/Control Views/ORKScaleSliderView.m @@ -91,7 +91,6 @@ - (instancetype)initWithFormatProvider:(id)formatP _slider.minimumTrackTintColor = self.tintColor; _slider.userInteractionEnabled = YES; _slider.contentMode = UIViewContentModeRedraw; - self.accessibilityElements = [self.accessibilityElements arrayByAddingObject:_slider]; [self addSubview:_slider]; _slider.maximumValue = [formatProvider maximumNumber].floatValue; @@ -151,7 +150,6 @@ - (instancetype)initWithFormatProvider:(id)formatP [self addSubview:_dontKnowBackgroundView]; [self addSubview:_dividerView]; [self addSubview:_dontKnowButton]; - self.accessibilityElements = [self.accessibilityElements arrayByAddingObject:_dontKnowButton]; } } @@ -797,6 +795,17 @@ - (BOOL)isAccessibilityElement { return NO; } +- (NSArray *)accessibilityElements { + NSMutableArray *accessibilityElements = [[NSMutableArray alloc] init]; + if (_slider) { + [accessibilityElements addObject:_slider]; + } + if (_dontKnowButton) { + [accessibilityElements addObject:_dontKnowButton]; + } + return accessibilityElements; +} + - (NSInteger)accessibilityElementCount { return self.accessibilityElements.count; } diff --git a/ResearchKitUI/Common/Answer Format/Form Step Views/ORKFormItemCell.m b/ResearchKitUI/Common/Answer Format/Form Step Views/ORKFormItemCell.m index 92a7019539..36f614ae5c 100644 --- a/ResearchKitUI/Common/Answer Format/Form Step Views/ORKFormItemCell.m +++ b/ResearchKitUI/Common/Answer Format/Form Step Views/ORKFormItemCell.m @@ -1779,6 +1779,32 @@ - (void)scaleSliderViewCurrentValueDidChange:(ORKScaleSliderView *)sliderView { [super inputValueDidChange]; } +#pragma mark Accessibility + +- (BOOL)isAccessibilityElement { + return NO; +} + +- (NSArray *)accessibilityElements { + NSMutableArray *accessibilityElements = [[NSMutableArray alloc] init]; + if (_sliderView) { + [accessibilityElements addObject:_sliderView]; + } + return accessibilityElements; +} + +- (NSInteger)accessibilityElementCount { + return self.accessibilityElements.count; +} + +- (id)accessibilityElementAtIndex:(NSInteger)index { + return [self.accessibilityElements objectAtIndex:index]; +} + +- (NSInteger)indexOfAccessibilityElement:(id)element { + return [self.accessibilityElements indexOfObject:element]; +} + @end #pragma mark - ORKFormItemPickerCell diff --git a/ResearchKitUI/Common/CheckmarkView/ORKCheckmarkView.m b/ResearchKitUI/Common/CheckmarkView/ORKCheckmarkView.m index 362f2babb7..44b47ad739 100644 --- a/ResearchKitUI/Common/CheckmarkView/ORKCheckmarkView.m +++ b/ResearchKitUI/Common/CheckmarkView/ORKCheckmarkView.m @@ -90,13 +90,18 @@ + (UIImage *)checkedImageWithoutCircle { } - (void)updateCheckView { + UIColor *existingTintColor = self.tintColor; if (_checked) { self.image = _checkedImage; - self.tintColor = ORKViewTintColor(self); + if (existingTintColor != ORKViewTintColor(self)) { + self.tintColor = ORKViewTintColor(self); + } } else { self.image = _uncheckedImage; - self.tintColor = _shouldIgnoreDarkMode ? [UIColor lightGrayColor] : [UIColor systemGray3Color]; + if (existingTintColor != ORKViewTintColor(self)) { + self.tintColor = _shouldIgnoreDarkMode ? [UIColor lightGrayColor] : [UIColor systemGray3Color]; + } } } @@ -123,4 +128,3 @@ - (void)tintColorDidChange { } @end - diff --git a/ResearchKitUI/Common/Container Views/ORKStepContentView.m b/ResearchKitUI/Common/Container Views/ORKStepContentView.m index 3475ee97c1..8f16369012 100644 --- a/ResearchKitUI/Common/Container Views/ORKStepContentView.m +++ b/ResearchKitUI/Common/Container Views/ORKStepContentView.m @@ -193,8 +193,6 @@ - (void)updateViewColors { return; } - // this requires the image to be set to the UIImageRenderingModeAlwaysTemplate rendering mode - // in the UIImageRenderingModeAlwaysOriginal rendering mode, tintColor does not apply [_topContentImageView updateRenderingModeForUserInterfaceStyle:self.traitCollection.userInterfaceStyle]; [_iconImageView updateRenderingModeForUserInterfaceStyle:self.traitCollection.userInterfaceStyle]; 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 index 55d30b4ace..2715d94b9c 100644 --- a/ResearchKitUI/Common/Step/Family History/Family History TableView Views/ORKFamilyHistoryRelatedPersonCell.m +++ b/ResearchKitUI/Common/Step/Family History/Family History TableView Views/ORKFamilyHistoryRelatedPersonCell.m @@ -153,7 +153,7 @@ - (void)presentOptionsMenuAlert { } } -- (void)setupSubViews { +- (void)setUpSubViews { _backgroundView = [UIView new]; _backgroundView.clipsToBounds = YES; _backgroundView.layer.cornerRadius = 12.0; @@ -193,8 +193,6 @@ - (void)setupSubViews { _conditionsLabel = [self _primaryLabel]; _conditionsLabel.text = ORKLocalizedString(@"FAMILY_HISTORY_CONDITIONS", @""); [_backgroundView addSubview:_conditionsLabel]; - - [self updateViewColors]; } - (void)updateViewColors { @@ -301,7 +299,7 @@ - (void)_clearActiveConstraints { ]; } -- (void)setupConstraints { +- (void)setUpConstraints { [self _clearActiveConstraints]; _viewConstraints = [NSMutableArray new]; @@ -425,8 +423,9 @@ - (void)configureWithDetailValues:(NSArray *)detailValues _conditionValues = conditionsValues; _isLastItemBeforeAddRelativeButton = isLastItemBeforeAddRelativeButton; - [self setupSubViews]; - [self setupConstraints]; + [self setUpSubViews]; + [self setUpConstraints]; + [self updateViewColors]; } - (UIFont *)titleLabelFont { diff --git a/ResearchKitUI/Common/Step/Form Step/ORKFormStepViewController.m b/ResearchKitUI/Common/Step/Form Step/ORKFormStepViewController.m index cabc4cce75..6d8411ac3d 100644 --- a/ResearchKitUI/Common/Step/Form Step/ORKFormStepViewController.m +++ b/ResearchKitUI/Common/Step/Form Step/ORKFormStepViewController.m @@ -1110,7 +1110,7 @@ - (void)setShouldPresentInReview:(BOOL)shouldPresentInReview { #pragma mark Helpers -- (ORKFormStep *)formStep { +- (ORKFormStep *)formStep { NSAssert(!self.step || [self.step isKindOfClass:[ORKFormStep class]], nil); return (ORKFormStep *)self.step; } @@ -1384,53 +1384,76 @@ - (BOOL)didAutoScrollToNextItem:(ORKFormItemCell *)cell { return YES; } -- (BOOL)shouldAutoScrollToNextSection:(NSIndexPath *)indexPath { - if (![self _isAutoScrollEnabled]) { - return NO; + +/// The proposed destination index path for auto-scrolling to the nexr section. +/// @returns The destination index path that should be used when scrolling to the next question, or `nil` if no auto-scroll should occur. +- (NSIndexPath *_Nullable)indexPathForAutoScrollingToNextSectionAfter:(NSIndexPath *)indexPath { + if (![self _isAutoScrollEnabled] || _autoScrollCancelled) { + return nil; } - BOOL result = YES; - NSIndexPath *nextIndexPath = [NSIndexPath indexPathForRow:0 inSection:(indexPath.section + 1)]; - // Technically, cellForRowAtIndexPath could return nil if the tableView hasn't decided to cache the cell - // Guarantee ourselves a cell by using dequeueReusableCellWithIdentifier—the only reason we need the cell is to test - // the cell's type, not for actual display, so using any cell paired with the reuseIdentifier should be fine - // do *not* use dequeueReusableCellWithIdentifier:indexPath: since that method should only be called within the dataSource - // tableView:cellForRowAtIndexPath: method - ORKFormItem *nextFormItem = [self _formItemForIndexPath:nextIndexPath]; ORKTableCellItemIdentifier *nextItemIdentifier = [_diffableDataSource itemIdentifierForIndexPath:nextIndexPath]; - NSString *nextReuseIdentifier = [self cellReuseIdentifierFromFormItem:nextFormItem cellItemIdentifier:nextItemIdentifier]; + if (nextItemIdentifier) { + // Technically, cellForRowAtIndexPath could return nil if the tableView hasn't decided to cache the cell + // Guarantee ourselves a cell by using dequeueReusableCellWithIdentifier—the only reason we need the cell is to test + // the cell's type, not for actual display, so using any cell paired with the reuseIdentifier should be fine + // do *not* use dequeueReusableCellWithIdentifier:indexPath: since that method should only be called within the dataSource + // tableView:cellForRowAtIndexPath: method + ORKFormItem *nextFormItem = [self _formItemForIndexPath:nextIndexPath]; + if (!nextFormItem) { + return nil; + } + NSString *nextReuseIdentifier = [self cellReuseIdentifierFromFormItem:nextFormItem cellItemIdentifier:nextItemIdentifier]; + // Can't autoscroll to something that doesn't exist + UITableViewCell *nextCell = [_tableView dequeueReusableCellWithIdentifier:nextReuseIdentifier]; + if (!nextCell) { + return nil; + } + // Don't autoscroll to a cell that already has an answer. + if (self.savedAnswers[nextFormItem.identifier]) { + return nil; + } + return nextIndexPath; + } else if (@available(iOS 15, *)) { + NSString *nextSectionIdentifier = [_diffableDataSource sectionIdentifierForIndex:nextIndexPath.section]; + ORKFormItem *formItem = [self _formItemForFormItemIdentifier:nextSectionIdentifier]; + if (!formItem) { + return nil; + } else { + // We need to scroll to a zero-item section. This requires us to adjust the index path. + return [NSIndexPath indexPathForRow:NSNotFound inSection:nextIndexPath.section]; + } + } else { + return nil; + } +} - result = result && (_autoScrollCancelled == NO); - // can't autoscroll to something that doesn't exist - UITableViewCell *nextCell = [_tableView dequeueReusableCellWithIdentifier:nextReuseIdentifier]; - result = result && (nextCell != nil); - - // don't autoscroll to a cell that already has an answer - result = result && (self.savedAnswers[nextFormItem.identifier] == nil); - - return result; +/// Determines if we should auto-scroll to the next section +- (BOOL)shouldAutoScrollToNextSectionAfter:(NSIndexPath *)indexPath { + return [self indexPathForAutoScrollingToNextSectionAfter:indexPath] != nil; } -- (void)autoScrollToNextSection:(NSIndexPath *)indexPath { + +- (void)autoScrollToNextSectionAfter:(NSIndexPath *)indexPath { if (![self _isAutoScrollEnabled]) { return; } - - NSIndexPath *nextIndexPath = [NSIndexPath indexPathForRow:0 inSection:(indexPath.section + 1)]; - UITableView *tableView = self.tableView; - UITableViewCell *nextCell = [tableView cellForRowAtIndexPath:nextIndexPath]; - BOOL didChangeFocus = [self focusUnansweredCell:nextCell]; - - if (didChangeFocus == YES) { - // if we did change focus, then that will perform the scrolling - } else { - // if we didn't change focus, then we need to handle the scrolling here + NSIndexPath *scrollDestinationIndexPath = [self indexPathForAutoScrollingToNextSectionAfter:indexPath]; + if (!scrollDestinationIndexPath) { + // If the index path returned by -shouldAutoScrollToNextSection: is nil, we're not supposed to auto-scroll to the next section. + return; + } + UITableViewCell *nextCell = [self.tableView cellForRowAtIndexPath:scrollDestinationIndexPath]; + if (!(nextCell && [self focusUnansweredCell:nextCell])) { + // if we didn't change focus, we need to scroll. + // otherwise (if we did change focus), that'll take care of the scrolling for us. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, DelayBeforeAutoScroll * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ - [tableView scrollToRowAtIndexPath:nextIndexPath atScrollPosition:UITableViewScrollPositionTop animated:YES]; + [_tableView scrollToRowAtIndexPath:scrollDestinationIndexPath atScrollPosition:UITableViewScrollPositionTop animated:YES]; }); + // if we did change focus, then that will perform the scrolling } } @@ -2020,8 +2043,8 @@ - (void)finishHandlingFormItemCellDidResignFirstResponder:(ORKTableCellItemIdent NSString *sectionIdentifier = [[snapshot sectionIdentifiers] objectAtIndex:indexPath.section]; ORKFormItemCell *cell = ORKDynamicCast([self.tableView cellForRowAtIndexPath:indexPath], ORKFormItemCell); - if (cell.isLastItem && [self shouldAutoScrollToNextSection:indexPath]) { - [self autoScrollToNextSection:indexPath]; + if (cell.isLastItem && [self shouldAutoScrollToNextSectionAfter:indexPath]) { + [self autoScrollToNextSectionAfter:indexPath]; return; } else if (cell.isLastItem && indexPath.section == (snapshot.numberOfSections - 1) && ![_identifiersOfAnsweredSections containsObject:sectionIdentifier]) { if (![self allNonOptionalFormItemsHaveAnswers]) { @@ -2267,7 +2290,7 @@ - (BOOL)scrollNextSectionToVisibleFromIndexPath:(NSIndexPath *)indexPath { if (allowAutoScrolling == YES) { // only allow autoscrolling to the next section if the next section exists if ((indexPath.section + 1) < [snapshot numberOfSections]) { - [self autoScrollToNextSection:indexPath]; + [self autoScrollToNextSectionAfter:indexPath]; handledAutoScroll = YES; } else { // We would go to the next section, but we literally can't diff --git a/ResearchKitUI/Common/Task/ORKTaskViewController.h b/ResearchKitUI/Common/Task/ORKTaskViewController.h index e373e0b814..342a8a533f 100644 --- a/ResearchKitUI/Common/Task/ORKTaskViewController.h +++ b/ResearchKitUI/Common/Task/ORKTaskViewController.h @@ -485,9 +485,10 @@ ORK_CLASS_AVAILABLE In general, set this property after instantiating the task view controller and before presenting it. - Before presenting the view controller, set the `outputDirectory` property to specify a - path where files should be written when an `ORKFileResult` object must be returned for - a step. + Before presenting the view controller for an active task, the `outputDirectory` must be set to specify a path + where files should be written when an `ORKFileResult` object must be returned for a step. This can be + accomplished by setting this property, or by using the task initializer that offers an `outputDirectory` + parameter. */ @property (nonatomic, copy, nullable) NSURL *outputDirectory; @@ -512,6 +513,12 @@ ORK_CLASS_AVAILABLE */ @property (nonatomic, strong, readonly, nullable) ORKStepViewController *currentStepViewController; +/** + The default temporary output directory where files generated by the task will be stored if used. + It is pointing to the Documents folder in the app container on the user's device. + */ ++ (NSURL *)ORKDefaultTemporaryOutputDirectory; + /** Forces navigation to the next step. diff --git a/ResearchKitUI/Common/Task/ORKTaskViewController.m b/ResearchKitUI/Common/Task/ORKTaskViewController.m index 7c1a82b7dc..7c3ba5489b 100644 --- a/ResearchKitUI/Common/Task/ORKTaskViewController.m +++ b/ResearchKitUI/Common/Task/ORKTaskViewController.m @@ -809,6 +809,12 @@ - (void)ensureDirectoryExists:(NSURL *)outputDirectory { } } ++ (NSURL *)ORKDefaultTemporaryOutputDirectory { + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString *documentsPath = [paths objectAtIndex:0]; + return [NSURL fileURLWithPath:documentsPath]; +} + - (void)setOutputDirectory:(NSURL *)outputDirectory { if (_hasBeenPresented) { @throw [NSException exceptionWithName:NSGenericException reason:@"Cannot change outputDirectory after presenting task controller" userInfo:nil];