Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bd60d945bb | |||
| 8f58410af9 | |||
| fc67cc944c | |||
| 64a068596b | |||
| c10693930d | |||
| c0f309edf3 | |||
| b1cc631748 |
@@ -88,6 +88,20 @@ Or, for the latest changes, use the `main` branch:
|
||||
git clone https://github.com/ResearchKit/ResearchKit.git
|
||||
```
|
||||
|
||||
CocoaPods Installation
|
||||
------------
|
||||
For latest stable release
|
||||
|
||||
```
|
||||
pod 'ResearchKit'
|
||||
```
|
||||
|
||||
For early development releases
|
||||
|
||||
```
|
||||
pod 'ResearchKit', :git => 'https://github.com/ResearchKit/ResearchKit.git', :branch => 'main'
|
||||
```
|
||||
|
||||
Building
|
||||
--------
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1250"
|
||||
LastUpgradeVersion = "1310"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -40,28 +40,14 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "B183A4731A8535D100C76870"
|
||||
BuildableName = "ResearchKit.framework"
|
||||
BlueprintName = "ResearchKit"
|
||||
ReferencedContainer = "container:ResearchKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "86CC8E991AC09332001CCD89"
|
||||
BuildableName = "ResearchKitTests.xctest"
|
||||
BlueprintName = "ResearchKitTests"
|
||||
ReferencedContainer = "container:ResearchKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
codeCoverageEnabled = "YES">
|
||||
<TestPlans>
|
||||
<TestPlanReference
|
||||
reference = "container:ResearchKit.xctestplan"
|
||||
default = "YES">
|
||||
</TestPlanReference>
|
||||
</TestPlans>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1250"
|
||||
LastUpgradeVersion = "1310"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1250"
|
||||
LastUpgradeVersion = "1310"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -167,7 +167,6 @@ internal class EyeActivitySlider: UIView {
|
||||
letterImageView.isHidden = true
|
||||
}
|
||||
|
||||
// swiftlint:disable large_tuple
|
||||
internal func fetchResultDataAndUpdateSlider() -> (outcome: Bool, letterAngle: Double, sliderAngle: Double, score: Int, incorrectAnswers: Int, maxScore: Int) {
|
||||
let outcome = getResult()
|
||||
let score = stepScores[currentStep]
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
#import "ORK3DModelStepViewController.h"
|
||||
#import "ORKHelpers_Internal.h"
|
||||
|
||||
|
||||
@implementation ORK3DModelStep
|
||||
|
||||
+ (Class)stepViewControllerClass {
|
||||
@@ -91,7 +90,6 @@
|
||||
return (isParentSame && ORKEqualObjects(self.modelManager, castObject.modelManager));
|
||||
}
|
||||
|
||||
|
||||
- (NSUInteger)hash {
|
||||
return [super hash] ^ [_modelManager hash];
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
||||
self = [super initWithIdentifier:identifier];
|
||||
if (self) {
|
||||
self.baseDisplayColor = ORKAccuracyStroopStep.colors[arc4random_uniform(ORKAccuracyStroopStep.colors.count)];
|
||||
self.baseDisplayColor = ORKAccuracyStroopStep.colors[arc4random_uniform(((uint32_t)ORKAccuracyStroopStep.colors.count))];
|
||||
self.isColorMatching = YES;
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@
|
||||
- (UIColor *)actualDisplayColor {
|
||||
return self.isColorMatching ?
|
||||
self.baseDisplayColor :
|
||||
ORKAccuracyStroopStep.colors[arc4random_uniform(ORKAccuracyStroopStep.colors.count)];
|
||||
ORKAccuracyStroopStep.colors[arc4random_uniform(((uint32_t) ORKAccuracyStroopStep.colors.count))];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -206,15 +206,6 @@ The default value of this property is `NO`.
|
||||
*/
|
||||
@property (nonatomic, copy, nullable) NSArray<ORKRecorderConfiguration *> *recorderConfigurations;
|
||||
|
||||
/**
|
||||
A Boolean value that determines if a step is a practice step or not.
|
||||
|
||||
When the value of this property is `YES`, the ResearchKit framework sets the allowsBackNavigation property to 'YES'
|
||||
|
||||
The default value of this property is `NO`.
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL isPractice;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -122,7 +122,6 @@
|
||||
ORK_DECODE_OBJ_CLASS(aDecoder, spokenInstruction, NSString);
|
||||
ORK_DECODE_OBJ_CLASS(aDecoder, finishedSpokenInstruction, NSString);
|
||||
ORK_DECODE_OBJ_ARRAY(aDecoder, recorderConfigurations, ORKRecorderConfiguration);
|
||||
ORK_DECODE_BOOL(aDecoder, isPractice);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -143,7 +142,6 @@
|
||||
ORK_ENCODE_OBJ(aCoder, spokenInstruction);
|
||||
ORK_ENCODE_OBJ(aCoder, finishedSpokenInstruction);
|
||||
ORK_ENCODE_OBJ(aCoder, recorderConfigurations);
|
||||
ORK_ENCODE_BOOL(aCoder, isPractice);
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object {
|
||||
@@ -164,8 +162,7 @@
|
||||
(self.shouldVibrateOnStart == castObject.shouldVibrateOnStart) &&
|
||||
(self.shouldVibrateOnFinish == castObject.shouldVibrateOnFinish) &&
|
||||
(self.shouldContinueOnFinish == castObject.shouldContinueOnFinish) &&
|
||||
(self.shouldUseNextAsSkipButton == castObject.shouldUseNextAsSkipButton) &&
|
||||
(self.isPractice == castObject.isPractice));
|
||||
(self.shouldUseNextAsSkipButton == castObject.shouldUseNextAsSkipButton));
|
||||
}
|
||||
|
||||
- (NSSet<HKObjectType *> *)requestedHealthKitTypesForReading {
|
||||
@@ -187,8 +184,4 @@
|
||||
return mask;
|
||||
}
|
||||
|
||||
- (BOOL)allowsBackNavigation {
|
||||
return self.isPractice;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -295,8 +295,9 @@
|
||||
outputDirectory:self.outputDirectory];
|
||||
recorder.configuration = provider;
|
||||
recorder.delegate = self;
|
||||
|
||||
[recorders addObject:recorder];
|
||||
if (recorder) {
|
||||
[recorders addObject:recorder];
|
||||
}
|
||||
}
|
||||
self.recorders = recorders;
|
||||
|
||||
@@ -566,7 +567,7 @@ static NSString *const _ORKRecorderResultsRestoreKey = @"recorderResults";
|
||||
[super decodeRestorableStateWithCoder:coder];
|
||||
|
||||
self.finished = [coder decodeBoolForKey:_ORKFinishedRestoreKey];
|
||||
_recorderResults = [coder decodeObjectOfClass:[NSArray class] forKey:_ORKRecorderResultsRestoreKey];
|
||||
_recorderResults = [coder decodeObjectOfClasses:[NSSet setWithArray:@[NSArray.self, ORKResult.self]] forKey:_ORKRecorderResultsRestoreKey];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -94,7 +94,7 @@ Float32 const VolumeClamp = 60.0;
|
||||
if (self) {
|
||||
ORK_DECODE_OBJ_CLASS(aDecoder, audioLevelStepIdentifier, NSString);
|
||||
ORK_DECODE_OBJ_CLASS(aDecoder, destinationStepIdentifier, NSString);
|
||||
ORK_DECODE_OBJ_CLASS(aDecoder, recordingSettings, NSDictionary);
|
||||
ORK_DECODE_OBJ_PLIST(aDecoder, recordingSettings);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -312,7 +312,7 @@
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [super initWithCoder:aDecoder];
|
||||
if (self) {
|
||||
ORK_DECODE_OBJ_CLASS(aDecoder, recorderSettings, NSDictionary);
|
||||
ORK_DECODE_OBJ_PLIST(aDecoder, recorderSettings);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -39,14 +39,11 @@
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
||||
self = [super initWithIdentifier:identifier];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (ORKRecorder *)recorderForStep:(ORKStep *)step outputDirectory:(NSURL *)outputDirectory {
|
||||
|
||||
ORKAudioStreamer *obj = [[ORKAudioStreamer alloc] initWithIdentifier:self.identifier step:step];
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ static const CGFloat BarViewHeight = 50.0;
|
||||
|
||||
_barView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[_containerView addSubview:_barView];
|
||||
|
||||
|
||||
[[_barView.leadingAnchor constraintEqualToAnchor:_containerView.leadingAnchor] setActive:YES];
|
||||
[[_barView.trailingAnchor constraintEqualToAnchor:_containerView.trailingAnchor] setActive:YES];
|
||||
|
||||
|
||||
@@ -53,7 +53,6 @@
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#include <sys/sysctl.h>
|
||||
|
||||
|
||||
static const NSTimeInterval SPL_METER_PLAY_DELAY_VOICEOVER = 3.0;
|
||||
|
||||
@interface ORKEnvironmentSPLMeterStepViewController ()<ORKRingViewDelegate, ORKEnvironmentSPLMeterContentViewVoiceOverDelegate> {
|
||||
@@ -121,6 +120,8 @@ static const NSTimeInterval SPL_METER_PLAY_DELAY_VOICEOVER = 3.0;
|
||||
[self requestRecordPermissionIfNeeded];
|
||||
[self configureAudioSession];
|
||||
[self setupFeedbackGenerator];
|
||||
|
||||
[self.taskViewController setNavigationBarColor:[self.view backgroundColor]];
|
||||
}
|
||||
|
||||
- (void)saveAudioSession {
|
||||
@@ -142,6 +143,19 @@ static const NSTimeInterval SPL_METER_PLAY_DELAY_VOICEOVER = 3.0;
|
||||
_navigationFooterView.continueButtonItem = continueButtonItem;
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
if (!_audioEngine.isRunning) {
|
||||
[self saveAudioSession];
|
||||
_sensitivityOffset = [self sensitivityOffsetForDevice];
|
||||
[self requestRecordPermissionIfNeeded];
|
||||
[self configureAudioSession];
|
||||
[self setupFeedbackGenerator];
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
[super viewDidAppear:animated];
|
||||
|
||||
@@ -163,12 +177,8 @@ static const NSTimeInterval SPL_METER_PLAY_DELAY_VOICEOVER = 3.0;
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
[super viewWillDisappear:animated];
|
||||
[self stopAudioEngine];
|
||||
[self resetAudioSession];
|
||||
[_eqUnit removeTapOnBus:0];
|
||||
[_audioEngine stop];
|
||||
[_rmsBuffer removeAllObjects];
|
||||
[self resetAudioSession];
|
||||
|
||||
}
|
||||
|
||||
- (NSString *)deviceType {
|
||||
@@ -243,7 +253,7 @@ static const NSTimeInterval SPL_METER_PLAY_DELAY_VOICEOVER = 3.0;
|
||||
}
|
||||
|
||||
[session setActive:YES error:&error];
|
||||
|
||||
|
||||
if (error) {
|
||||
ORK_Log_Error("Activating AVAudioSession failed with error message: \"%@\"", error.localizedDescription);
|
||||
}
|
||||
@@ -269,7 +279,7 @@ static const NSTimeInterval SPL_METER_PLAY_DELAY_VOICEOVER = 3.0;
|
||||
}
|
||||
|
||||
[session setActive:YES error:&error];
|
||||
|
||||
|
||||
if (error) {
|
||||
ORK_Log_Error("Activating AVAudioSession failed with error message: \"%@\"", error.localizedDescription);
|
||||
}
|
||||
@@ -389,7 +399,7 @@ static const NSTimeInterval SPL_METER_PLAY_DELAY_VOICEOVER = 3.0;
|
||||
});
|
||||
dispatch_semaphore_wait(_semaphoreRms, DISPATCH_TIME_FOREVER);
|
||||
} else if ([AVAudioSession sharedInstance].recordPermission == AVAudioSessionRecordPermissionDenied) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
[_eqUnit removeTapOnBus:0];
|
||||
[_audioEngine stop];
|
||||
[_rmsBuffer removeAllObjects];
|
||||
@@ -400,9 +410,7 @@ static const NSTimeInterval SPL_METER_PLAY_DELAY_VOICEOVER = 3.0;
|
||||
NSError *error = nil;
|
||||
[_audioEngine startAndReturnError:&error];
|
||||
} else {
|
||||
[_eqUnit removeTapOnBus:0];
|
||||
[_audioEngine stop];
|
||||
[_rmsBuffer removeAllObjects];
|
||||
[self stopAudioEngine];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -445,8 +453,18 @@ static const NSTimeInterval SPL_METER_PLAY_DELAY_VOICEOVER = 3.0;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)stopAudioEngine {
|
||||
if ([_audioEngine isRunning]) {
|
||||
dispatch_semaphore_signal(_semaphoreRms);
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
[_eqUnit removeTapOnBus:0];
|
||||
[_audioEngine stop];
|
||||
[_rmsBuffer removeAllObjects];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (void)reachedOptimumNoiseLevel {
|
||||
[_audioEngine stop];
|
||||
[self resetAudioSession];
|
||||
}
|
||||
|
||||
@@ -467,6 +485,7 @@ static const NSTimeInterval SPL_METER_PLAY_DELAY_VOICEOVER = 3.0;
|
||||
#pragma mark - ORKRingViewDelegate
|
||||
|
||||
- (void)ringViewDidFinishFillAnimation {
|
||||
[self reachedOptimumNoiseLevel];
|
||||
[self.environmentSPLMeterContentView reachedOptimumNoiseLevel];
|
||||
self.activeStepView.navigationFooterView.continueEnabled = YES;
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
{
|
||||
self = [super initWithCoder:coder];
|
||||
if (self) {
|
||||
ORK_DECODE_OBJ_CLASS(coder, userInfo, NSDictionary);
|
||||
ORK_DECODE_OBJ_PLIST(coder, userInfo);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -307,7 +307,7 @@ typedef NS_CLOSED_ENUM(NSInteger, ORKStartStopButtonState) {
|
||||
{
|
||||
[_startStopButton setTitle:ORKLocalizedString(@"FRONT_FACING_CAMERA_START_TITLE", nil) forState:UIControlStateNormal];
|
||||
[_startStopButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
|
||||
[_startStopButton setBackgroundColor:[UIColor systemBlueColor]];
|
||||
[_startStopButton setBackgroundColor:self.tintColor];
|
||||
|
||||
[_timerLabel setText:ORKLocalizedString(@"FRONT_FACING_CAMERA_START_TIME", nil)];
|
||||
[_timerLabel setTextColor:[UIColor darkGrayColor]];
|
||||
@@ -315,7 +315,7 @@ typedef NS_CLOSED_ENUM(NSInteger, ORKStartStopButtonState) {
|
||||
else
|
||||
{
|
||||
[_startStopButton setTitle:ORKLocalizedString(@"FRONT_FACING_CAMERA_STOP_TITLE", nil) forState:UIControlStateNormal];
|
||||
[_startStopButton setTitleColor:[UIColor systemBlueColor] forState:UIControlStateNormal];
|
||||
[_startStopButton setTitleColor:self.tintColor forState:UIControlStateNormal];
|
||||
[_startStopButton setBackgroundColor:[UIColor systemGrayColor]];
|
||||
|
||||
[_timerLabel setTextColor:[UIColor whiteColor]];
|
||||
@@ -352,6 +352,10 @@ typedef NS_CLOSED_ENUM(NSInteger, ORKStartStopButtonState) {
|
||||
_isTextCollapsed = !_isTextCollapsed;
|
||||
}
|
||||
|
||||
- (void)didMoveToWindow {
|
||||
self.tintColor = ORKViewTintColor(self);
|
||||
[self setStartStopButtonState:_startStopButtonState];
|
||||
}
|
||||
@end
|
||||
|
||||
@interface ORKFrontFacingCameraStepContentView ()
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
#import "ORKResult_Private.h"
|
||||
#import "ORKStepContainerView_Private.h"
|
||||
#import "ORKStepViewController_Internal.h"
|
||||
#import "ORKTaskViewController_Internal.h"
|
||||
|
||||
@interface ORKFrontFacingCameraStepViewController () <AVCaptureFileOutputRecordingDelegate, AVCaptureVideoDataOutputSampleBufferDelegate>
|
||||
|
||||
@@ -89,6 +90,8 @@
|
||||
[self setupContentView];
|
||||
[self setupConstraints];
|
||||
[self startSession];
|
||||
|
||||
[self.taskViewController setNavigationBarColor:[self.view backgroundColor]];
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
|
||||
@@ -38,7 +38,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
The `ORKReactionTimeResult` class represents the result of a single successful attempt within an ORKReactionTimeStep.
|
||||
|
||||
|
||||
The `timestamp` property is equal to the value of systemUptime (in NSProcessInfo) when the stimulus occurred.
|
||||
Each entry of motion data in this file contains a time interval which may be directly compared to timestamp in order to determine the elapsed time since the stimulus.
|
||||
|
||||
@@ -56,9 +56,7 @@ ORK_CLASS_AVAILABLE
|
||||
@property (nonatomic, copy) NSDate * timerEndDate;
|
||||
@property (nonatomic, copy, nullable) NSDate * stimulusStartDate;
|
||||
@property (nonatomic, copy, nullable) NSDate * reactionDate;
|
||||
@property (nonatomic) NSNumber *currentInterval;
|
||||
|
||||
|
||||
@property (nonatomic) double currentInterval;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -44,8 +44,7 @@
|
||||
ORK_ENCODE_OBJ(aCoder, timerEndDate);
|
||||
ORK_ENCODE_OBJ(aCoder, stimulusStartDate);
|
||||
ORK_ENCODE_OBJ(aCoder, reactionDate);
|
||||
ORK_ENCODE_OBJ(aCoder, currentInterval);
|
||||
|
||||
ORK_ENCODE_INTEGER(aCoder, currentInterval);
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
@@ -55,7 +54,7 @@
|
||||
ORK_DECODE_OBJ_CLASS(aDecoder, timerEndDate, NSDate);
|
||||
ORK_DECODE_OBJ_CLASS(aDecoder, stimulusStartDate, NSDate);
|
||||
ORK_DECODE_OBJ_CLASS(aDecoder, reactionDate, NSDate);
|
||||
ORK_DECODE_OBJ_CLASS(aDecoder, currentInterval, NSNumber);
|
||||
ORK_DECODE_INTEGER(aDecoder, currentInterval);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -73,8 +72,7 @@
|
||||
ORKEqualObjects(self.timerEndDate, castObject.timerEndDate) &&
|
||||
ORKEqualObjects(self.stimulusStartDate, castObject.stimulusStartDate) &&
|
||||
ORKEqualObjects(self.reactionDate, castObject.reactionDate) &&
|
||||
ORKEqualObjects(self.currentInterval, castObject.currentInterval)) ;
|
||||
|
||||
(self.currentInterval == castObject.currentInterval));
|
||||
}
|
||||
|
||||
- (NSUInteger)hash {
|
||||
@@ -87,7 +85,7 @@
|
||||
result.timerEndDate = [self.timerEndDate copy];
|
||||
result.stimulusStartDate = [self.stimulusStartDate copy];
|
||||
result.reactionDate = [self.reactionDate copy];
|
||||
result.currentInterval = [self.currentInterval copy];
|
||||
result.currentInterval = self.currentInterval;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@property (nonatomic, assign) SystemSoundID failureSound;
|
||||
|
||||
@property (nonatomic) NSNumber *currentInterval;
|
||||
@property (nonatomic) double currentInterval;
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@@ -104,7 +104,7 @@
|
||||
ORK_DECODE_UINT32(aDecoder, timeoutSound);
|
||||
ORK_DECODE_UINT32(aDecoder, failureSound);
|
||||
ORK_DECODE_INTEGER(aDecoder, numberOfAttempts);
|
||||
ORK_DECODE_OBJ_CLASS(aDecoder, currentInterval, NSNumber);
|
||||
ORK_DECODE_INTEGER(aDecoder, currentInterval);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -119,7 +119,7 @@
|
||||
ORK_ENCODE_UINT32(aCoder, timeoutSound);
|
||||
ORK_ENCODE_UINT32(aCoder, failureSound);
|
||||
ORK_ENCODE_INTEGER(aCoder, numberOfAttempts);
|
||||
ORK_ENCODE_OBJ(aCoder, currentInterval);
|
||||
ORK_ENCODE_INTEGER(aCoder, currentInterval);
|
||||
}
|
||||
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
|
||||
@@ -254,7 +254,8 @@ static const NSTimeInterval OutcomeAnimationDuration = 0.3;
|
||||
- (NSTimeInterval)stimulusInterval {
|
||||
ORKNormalizedReactionTimeStep *step = [self reactionTimeStep];
|
||||
NSNumber* interval = [self getRandomInterval];
|
||||
step.currentInterval = interval;
|
||||
step.currentInterval = interval.doubleValue;
|
||||
|
||||
return [interval doubleValue];
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
|
||||
#import "ORKPSATKeyboardView.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
#import "ORKBorderedButton.h"
|
||||
|
||||
|
||||
@@ -80,6 +80,7 @@ NSUInteger const ORKPSATMaximumAnswer = 17;
|
||||
- (void)setEnabled:(BOOL)enabled {
|
||||
for (ORKBorderedButton *answerButton in self.answerButtons) {
|
||||
[answerButton setEnabled:enabled];
|
||||
[answerButton setBackgroundColor:ORKViewTintColor(self)];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,6 @@
|
||||
#import "ORKSkin.h"
|
||||
#import "ORKPlaybackButton.h"
|
||||
|
||||
|
||||
static CGFloat const ORKSpeechInNoiseContentFlamesViewHeightConstant = 150.0;
|
||||
static CGFloat const ORKSpeechInNoiseContentFlamesViewVerticalSpacing = 44.0;
|
||||
static CGFloat const ORKSpeechInNoiseContentViewVerticalMargin = 44;
|
||||
|
||||
@@ -186,9 +186,8 @@
|
||||
|
||||
|
||||
- (void)setAllowUserToRecordInsteadOnNextStep:(BOOL)allowUserToRecordInsteadOnNextStep
|
||||
{
|
||||
_allowUserToRecordInsteadOnNextStep = allowUserToRecordInsteadOnNextStep;
|
||||
|
||||
{
|
||||
_allowUserToRecordInsteadOnNextStep = (allowUserToRecordInsteadOnNextStep && [SFSpeechRecognizer authorizationStatus] != SFSpeechRecognizerAuthorizationStatusDenied);
|
||||
}
|
||||
|
||||
- (CAShapeLayer *)recordingShapeLayer
|
||||
|
||||
@@ -73,6 +73,9 @@
|
||||
- (instancetype)copyWithZone:(NSZone *)zone {
|
||||
ORKStroopStep *step = [super copyWithZone:zone];
|
||||
step.numberOfAttempts = self.numberOfAttempts;
|
||||
step.useTextForStimuli = self.useTextForStimuli;
|
||||
step.useGridLayoutForButtons = self.useGridLayoutForButtons;
|
||||
step.randomizeVisualAndColorAlignment = self.randomizeVisualAndColorAlignment;
|
||||
return step;
|
||||
}
|
||||
|
||||
@@ -80,6 +83,9 @@
|
||||
self = [super initWithCoder:aDecoder];
|
||||
if (self ) {
|
||||
ORK_DECODE_INTEGER(aDecoder, numberOfAttempts);
|
||||
ORK_DECODE_BOOL(aDecoder, useTextForStimuli);
|
||||
ORK_DECODE_BOOL(aDecoder, useGridLayoutForButtons);
|
||||
ORK_DECODE_BOOL(aDecoder, randomizeVisualAndColorAlignment);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -87,13 +93,20 @@
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
[super encodeWithCoder:aCoder];
|
||||
ORK_ENCODE_INTEGER(aCoder, numberOfAttempts);
|
||||
ORK_ENCODE_BOOL(aCoder, useTextForStimuli);
|
||||
ORK_ENCODE_BOOL(aCoder, useGridLayoutForButtons);
|
||||
ORK_ENCODE_BOOL(aCoder, randomizeVisualAndColorAlignment);
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object {
|
||||
BOOL isParentSame = [super isEqual:object];
|
||||
|
||||
__typeof(self) castObject = object;
|
||||
return (isParentSame && (self.numberOfAttempts == castObject.numberOfAttempts));
|
||||
return (isParentSame
|
||||
&& (self.numberOfAttempts == castObject.numberOfAttempts)
|
||||
&& (self.useTextForStimuli == castObject.useTextForStimuli)
|
||||
&& (self.useGridLayoutForButtons == castObject.useGridLayoutForButtons)
|
||||
&& (self.randomizeVisualAndColorAlignment == castObject.randomizeVisualAndColorAlignment));
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
@import UIKit;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <ResearchKit/ResearchKit.h>
|
||||
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKUSDZModelManagerResult: ORKResult
|
||||
|
||||
@@ -42,10 +41,3 @@ ORK_CLASS_AVAILABLE
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
Copyright (c) 2022, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
@import UIKit;
|
||||
#import <ResearchKit/ORKAudiometryProtocol.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class ORKdBHLToneAudiometryStep;
|
||||
|
||||
/**
|
||||
The `ORKAudiometry` class performs an audiometry test based on `ORKAudiometryProtocol`
|
||||
*/
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKAudiometry : NSObject <ORKAudiometryProtocol>
|
||||
|
||||
/**
|
||||
Returns an ORKAudiometry object initialized with an ORKdBHLToneAudiometryStep.
|
||||
|
||||
@param step A ORKdBHLToneAudiometryStep object used to configure the audiometry test.
|
||||
|
||||
@return An ORKAudiometry object initialized.
|
||||
*/
|
||||
- (instancetype)initWithStep:(ORKdBHLToneAudiometryStep *)step;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,332 @@
|
||||
/*
|
||||
Copyright (c) 2022, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#import "ORKAudiometry.h"
|
||||
#import "ORKdBHLToneAudiometryStep.h"
|
||||
#import "ORKdBHLToneAudiometryResult.h"
|
||||
#import "ORKAudiometryStimulus.h"
|
||||
|
||||
@interface ORKAudiometryTransition: NSObject
|
||||
|
||||
@property (nonatomic, assign) float userInitiated;
|
||||
@property (nonatomic, assign) float totalTransitions;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ORKAudiometryTransition
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_userInitiated = 1;
|
||||
_totalTransitions = 1;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface ORKAudiometry () {
|
||||
double _prevFreq;
|
||||
double _currentdBHL;
|
||||
double _initialdBHLValue;
|
||||
double _dBHLStepUpSize;
|
||||
double _dBHLStepDownSize;
|
||||
double _dBHLMinimumThreshold;
|
||||
int _currentTestIndex;
|
||||
int _indexOfFreqLoopList;
|
||||
NSUInteger _indexOfStepUpMissingList;
|
||||
int _numberOfTransitionsPerFreq;
|
||||
NSInteger _maxNumberOfTransitionsPerFreq;
|
||||
BOOL _initialDescent;
|
||||
BOOL _ackOnce;
|
||||
BOOL _usingMissingList;
|
||||
NSArray *_freqLoopList;
|
||||
NSArray *_stepUpMissingList;
|
||||
NSMutableArray *_arrayOfResultSamples;
|
||||
NSMutableArray *_arrayOfResultUnits;
|
||||
NSMutableDictionary *_transitionsDictionary;
|
||||
|
||||
ORKdBHLToneAudiometryFrequencySample *_resultSample;
|
||||
ORKdBHLToneAudiometryUnit *_resultUnit;
|
||||
ORKAudioChannel _audioChannel;
|
||||
int _minimumThresholdCounter;
|
||||
|
||||
ORKAudiometryTimestampProvider _getTimestamp;
|
||||
ORKAudiometryStimulus *_nextStimulus;
|
||||
BOOL _preStimulusResponse;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ORKAudiometry
|
||||
|
||||
@synthesize progress;
|
||||
@synthesize testEnded;
|
||||
@synthesize timestampProvider;
|
||||
|
||||
- (instancetype)initWithStep:(ORKdBHLToneAudiometryStep *)step {
|
||||
self = [super init];
|
||||
|
||||
if (self) {
|
||||
_indexOfFreqLoopList = 0;
|
||||
_indexOfStepUpMissingList = 0;
|
||||
_initialDescent = YES;
|
||||
_ackOnce = NO;
|
||||
_usingMissingList = YES;
|
||||
_prevFreq = 0;
|
||||
_minimumThresholdCounter = 0;
|
||||
_currentTestIndex = 0;
|
||||
_transitionsDictionary = [NSMutableDictionary dictionary];
|
||||
_arrayOfResultSamples = [NSMutableArray array];
|
||||
_arrayOfResultUnits = [NSMutableArray array];
|
||||
_preStimulusResponse = YES;
|
||||
_getTimestamp = ^NSTimeInterval{
|
||||
return 0;
|
||||
};
|
||||
|
||||
[self configureWithStep:step];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setTimestampProvider:(ORKAudiometryTimestampProvider)provider {
|
||||
_getTimestamp = provider;
|
||||
}
|
||||
|
||||
- (ORKAudiometryStimulus *)nextStimulus {
|
||||
return _nextStimulus;
|
||||
}
|
||||
|
||||
- (void)registerPreStimulusDelay:(double)preStimulusDelay {
|
||||
_resultUnit.preStimulusDelay = preStimulusDelay;
|
||||
}
|
||||
|
||||
- (void)registerStimulusPlayback {
|
||||
_preStimulusResponse = NO;
|
||||
}
|
||||
|
||||
- (void)registerResponse:(BOOL)response {
|
||||
if (response) {
|
||||
[self stimulusAcknowledged];
|
||||
} else {
|
||||
[self stimulusMissed];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)signalClipped {
|
||||
if (_usingMissingList
|
||||
&& (_indexOfStepUpMissingList <= _stepUpMissingList.count)) {
|
||||
_usingMissingList = NO;
|
||||
_currentdBHL = _currentdBHL - [_stepUpMissingList[_indexOfStepUpMissingList - (_indexOfStepUpMissingList == 0 ? 0 : 1)] doubleValue] + _dBHLStepUpSize;
|
||||
[self estimatedBHLForFrequency:_freqLoopList[_indexOfFreqLoopList]];
|
||||
} else {
|
||||
_indexOfFreqLoopList += 1;
|
||||
if (_indexOfFreqLoopList >= _freqLoopList.count) {
|
||||
_resultSample.units = [_arrayOfResultUnits copy];
|
||||
testEnded = YES;
|
||||
} else {
|
||||
[self estimatedBHLForFrequency:_freqLoopList[_indexOfFreqLoopList]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray<ORKdBHLToneAudiometryFrequencySample *> *)resultSamples {
|
||||
return [_arrayOfResultSamples copy];
|
||||
}
|
||||
|
||||
- (void)configureWithStep:(ORKdBHLToneAudiometryStep *)step {
|
||||
_maxNumberOfTransitionsPerFreq = step.maxNumberOfTransitionsPerFrequency;
|
||||
_freqLoopList = step.frequencyList;
|
||||
_stepUpMissingList = @[ [NSNumber numberWithDouble:step.dBHLStepUpSizeFirstMiss],
|
||||
[NSNumber numberWithDouble:step.dBHLStepUpSizeSecondMiss],
|
||||
[NSNumber numberWithDouble:step.dBHLStepUpSizeThirdMiss] ];
|
||||
_currentdBHL = step.initialdBHLValue;
|
||||
_initialdBHLValue = step.initialdBHLValue;
|
||||
_dBHLStepDownSize = step.dBHLStepDownSize;
|
||||
_dBHLStepUpSize = step.dBHLStepUpSize;
|
||||
_dBHLMinimumThreshold = step.dBHLMinimumThreshold;
|
||||
_audioChannel = step.earPreference;
|
||||
|
||||
[self estimatedBHLForFrequency:_freqLoopList[_indexOfFreqLoopList]];
|
||||
}
|
||||
|
||||
- (void)estimatedBHLForFrequency:(NSNumber *)freq {
|
||||
if (_prevFreq != [freq doubleValue]) {
|
||||
progress = 0.001 + (CGFloat)_indexOfFreqLoopList / _freqLoopList.count;
|
||||
|
||||
_numberOfTransitionsPerFreq = 0;
|
||||
_currentdBHL = _initialdBHLValue;
|
||||
_initialDescent = YES;
|
||||
_ackOnce = NO;
|
||||
_usingMissingList = YES;
|
||||
_indexOfStepUpMissingList = 0;
|
||||
_minimumThresholdCounter = 0;
|
||||
_transitionsDictionary = nil;
|
||||
_transitionsDictionary = [NSMutableDictionary dictionary];
|
||||
if (_resultSample) {
|
||||
_resultSample.units = [_arrayOfResultUnits copy];
|
||||
}
|
||||
_arrayOfResultUnits = [NSMutableArray array];
|
||||
_prevFreq = [freq doubleValue];
|
||||
_resultSample = [ORKdBHLToneAudiometryFrequencySample new];
|
||||
_resultSample.channel = _audioChannel;
|
||||
_resultSample.frequency = [freq doubleValue];
|
||||
_resultSample.calculatedThreshold = ORKInvalidDBHLValue;
|
||||
[_arrayOfResultSamples addObject:_resultSample];
|
||||
} else {
|
||||
_numberOfTransitionsPerFreq += 1;
|
||||
if (_numberOfTransitionsPerFreq >= _maxNumberOfTransitionsPerFreq) {
|
||||
_indexOfFreqLoopList += 1;
|
||||
if (_indexOfFreqLoopList >= _freqLoopList.count) {
|
||||
_resultSample.units = [_arrayOfResultUnits copy];
|
||||
testEnded = YES;
|
||||
return;
|
||||
} else {
|
||||
[self estimatedBHLForFrequency:_freqLoopList[_indexOfFreqLoopList]];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_resultUnit = [ORKdBHLToneAudiometryUnit new];
|
||||
_resultUnit.dBHLValue = _currentdBHL;
|
||||
_resultUnit.startOfUnitTimeStamp = _getTimestamp();
|
||||
[_arrayOfResultUnits addObject:_resultUnit];
|
||||
|
||||
ORKAudiometryTransition *currentTransition = [_transitionsDictionary objectForKey:[NSNumber numberWithFloat:_currentdBHL]];
|
||||
if (!_initialDescent) {
|
||||
if (currentTransition) {
|
||||
currentTransition.userInitiated += 1;
|
||||
currentTransition.totalTransitions += 1;
|
||||
} else {
|
||||
currentTransition = [[ORKAudiometryTransition alloc] init];
|
||||
[_transitionsDictionary setObject:currentTransition forKey:[NSNumber numberWithFloat:_currentdBHL]];
|
||||
}
|
||||
}
|
||||
|
||||
_preStimulusResponse = YES;
|
||||
_nextStimulus = [[ORKAudiometryStimulus alloc] initWithFrequency:[freq doubleValue] level:_currentdBHL channel:_audioChannel];
|
||||
}
|
||||
|
||||
- (void)stimulusMissed {
|
||||
ORKAudiometryTransition *currentTransition = [_transitionsDictionary objectForKey:[NSNumber numberWithFloat:_currentdBHL]];
|
||||
NSUInteger storedTestIndex = _currentTestIndex;
|
||||
if (_currentTestIndex == storedTestIndex) {
|
||||
if (_initialDescent && _ackOnce) {
|
||||
_initialDescent = NO;
|
||||
ORKAudiometryTransition *newTransition = [[ORKAudiometryTransition alloc] init];
|
||||
newTransition.userInitiated -= 1;
|
||||
[_transitionsDictionary setObject:newTransition forKey:[NSNumber numberWithFloat:_currentdBHL]];
|
||||
}
|
||||
if (_usingMissingList && (_indexOfStepUpMissingList < _stepUpMissingList.count)) {
|
||||
_currentdBHL = _currentdBHL + [_stepUpMissingList[_indexOfStepUpMissingList] doubleValue];
|
||||
_indexOfStepUpMissingList = _indexOfStepUpMissingList + 1;
|
||||
} else {
|
||||
_usingMissingList = NO;
|
||||
_currentdBHL = _currentdBHL + _dBHLStepUpSize;
|
||||
}
|
||||
if (currentTransition) {
|
||||
currentTransition.userInitiated -= 1;
|
||||
}
|
||||
_resultUnit.timeoutTimeStamp = _getTimestamp();
|
||||
_currentTestIndex += 1;
|
||||
[self estimatedBHLForFrequency:_freqLoopList[_indexOfFreqLoopList]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)stimulusAcknowledged {
|
||||
_ackOnce = YES;
|
||||
_currentTestIndex += 1;
|
||||
_resultUnit.userTapTimeStamp = _getTimestamp();
|
||||
if (_preStimulusResponse) {
|
||||
NSNumber *currentKey = [NSNumber numberWithFloat:_currentdBHL];
|
||||
ORKAudiometryTransition *currentTransitionObject = [_transitionsDictionary objectForKey:currentKey];
|
||||
currentTransitionObject.userInitiated -= 1;
|
||||
} else if ([self validateResultFordBHL:_currentdBHL]) {
|
||||
_resultSample.calculatedThreshold = _currentdBHL;
|
||||
_indexOfFreqLoopList += 1;
|
||||
if (_indexOfFreqLoopList >= _freqLoopList.count) {
|
||||
_resultSample.units = [_arrayOfResultUnits copy];
|
||||
testEnded = YES;
|
||||
return;
|
||||
} else {
|
||||
_currentdBHL = _initialdBHLValue;
|
||||
[self estimatedBHLForFrequency:_freqLoopList[_indexOfFreqLoopList]];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_preStimulusResponse) {
|
||||
_usingMissingList = NO;
|
||||
|
||||
if (_currentdBHL - _dBHLStepDownSize > _dBHLMinimumThreshold) {
|
||||
_currentdBHL = _currentdBHL - _dBHLStepDownSize;
|
||||
} else {
|
||||
_currentdBHL = _dBHLMinimumThreshold;
|
||||
if (_initialDescent) {
|
||||
_minimumThresholdCounter += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[self estimatedBHLForFrequency:_freqLoopList[_indexOfFreqLoopList]];
|
||||
return;
|
||||
}
|
||||
|
||||
- (BOOL)validateResultFordBHL:(float)dBHL {
|
||||
NSNumber *currentKey = [NSNumber numberWithFloat:_currentdBHL];
|
||||
ORKAudiometryTransition *currentTransitionObject = [_transitionsDictionary objectForKey:currentKey];
|
||||
if ((currentTransitionObject.userInitiated/currentTransitionObject.totalTransitions >= 0.5) && currentTransitionObject.totalTransitions >= 2) {
|
||||
ORKAudiometryTransition *previousTransitionObject = [_transitionsDictionary objectForKey:[NSNumber numberWithFloat:(dBHL - _dBHLStepUpSize)]];
|
||||
if (((previousTransitionObject.userInitiated/previousTransitionObject.totalTransitions <= 0.5) && (previousTransitionObject.totalTransitions >= 2)) || dBHL == _dBHLMinimumThreshold) {
|
||||
if (currentTransitionObject.totalTransitions == 2) {
|
||||
if (currentTransitionObject.userInitiated/currentTransitionObject.totalTransitions == 1.0) {
|
||||
_resultSample.calculatedThreshold = dBHL;
|
||||
return YES;
|
||||
} else {
|
||||
return NO;
|
||||
}
|
||||
} else {
|
||||
_resultSample.calculatedThreshold = dBHL;
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
} else if (_minimumThresholdCounter > 2) {
|
||||
_resultSample.calculatedThreshold = dBHL;
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
Copyright (c) 2022, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#import <ResearchKit/ORKAudiometryStimulus.h>
|
||||
|
||||
typedef NSTimeInterval(^ORKAudiometryTimestampProvider)(void);
|
||||
|
||||
@class ORKdBHLToneAudiometryFrequencySample;
|
||||
|
||||
/**
|
||||
Defines the interface of an audiometry algorithm.
|
||||
*/
|
||||
@protocol ORKAudiometryProtocol <NSObject>
|
||||
|
||||
/**
|
||||
A float value indicating the progress of the test from 0.0 to 1.0 (read-only)
|
||||
*/
|
||||
@property (nonatomic, readonly) float progress;
|
||||
|
||||
/**
|
||||
A Boolean value indicating the end of the test (read-only)
|
||||
*/
|
||||
@property (nonatomic, readonly) BOOL testEnded;
|
||||
|
||||
/**
|
||||
A block used to retrieve timestamp from external sources to be included in the results.
|
||||
*/
|
||||
@property (nonatomic, strong) ORKAudiometryTimestampProvider timestampProvider;
|
||||
|
||||
/**
|
||||
This method should return a `ORKAudiometryStimulus` providing the parameters of the tone that should presented next, if available.
|
||||
*/
|
||||
- (ORKAudiometryStimulus *)nextStimulus;
|
||||
|
||||
/**
|
||||
Called just before presenting tone.
|
||||
*/
|
||||
- (void)registerStimulusPlayback;
|
||||
|
||||
/**
|
||||
Register the user response for the last presented tone.
|
||||
|
||||
@param BOOL A Boolean representing if the user acknowledged the last presented tone.
|
||||
*/
|
||||
- (void)registerResponse:(BOOL)response;
|
||||
|
||||
/**
|
||||
Informs the audiometry algorithm that the last provided tone could not be reproduced due to signal clipping. Optional.
|
||||
*/
|
||||
@optional
|
||||
- (void)signalClipped;
|
||||
|
||||
/**
|
||||
Used by some UIs to setup the prestimulus delay.
|
||||
|
||||
@param double The value of the preStimulusDelay
|
||||
*/
|
||||
@optional
|
||||
- (void)registerPreStimulusDelay:(double)preStimulusDelay;
|
||||
|
||||
/**
|
||||
Returns an array of containing the results of the audiometry test.
|
||||
|
||||
@return An array of `ORKdBHLToneAudiometryFrequencySample` representing the results of the audiometry test..
|
||||
*/
|
||||
- (NSArray<ORKdBHLToneAudiometryFrequencySample *> *)resultSamples;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
Copyright (c) 2022, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#import <ResearchKit/ORKTypes.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
The `ORKAudiometryStimulus` class represents a set of parameters to represent a tone to be
|
||||
presented to the user during an audiometry test.
|
||||
*/
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKAudiometryStimulus : NSObject
|
||||
|
||||
/**
|
||||
A double value representing the frequency in hertz of the audio tone to be presented (read-only)
|
||||
*/
|
||||
@property (nonatomic, readonly) double frequency;
|
||||
|
||||
/**
|
||||
A double value representing the level in dBHL of the audio tone to be presented (read-only)
|
||||
*/
|
||||
@property (nonatomic, readonly) double level;
|
||||
|
||||
/**
|
||||
A ORKAudioChannel enum value representing the audio channel of the tone to be presented (read-only)
|
||||
*/
|
||||
@property (nonatomic, readonly) ORKAudioChannel channel;
|
||||
|
||||
|
||||
/**
|
||||
Returns a ORKAudiometryStimulus initialized with the provided parameters.
|
||||
|
||||
@param frequency A double value representing the frequency in hertz of the audio tone.
|
||||
|
||||
@param level A double value representing the level in dBHL of the audio tone.
|
||||
|
||||
@param channel A ORKAudioChannel representing the audio channel of the tone.
|
||||
|
||||
@return A ORKAudiometryStimulus object initialized with the passed tone parameters.
|
||||
*/
|
||||
- (instancetype)initWithFrequency:(double)frequency level:(double)level channel:(ORKAudioChannel)channel;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
Copyright (c) 2022, Apple Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder(s) nor the names of any contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission. No license is granted to the trademarks of
|
||||
the copyright holders even if such marks are included in this software.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#import "ORKAudiometryStimulus.h"
|
||||
|
||||
@implementation ORKAudiometryStimulus
|
||||
|
||||
@synthesize frequency = _frequency;
|
||||
@synthesize level = _level;
|
||||
@synthesize channel = _channel;
|
||||
|
||||
- (instancetype)initWithFrequency:(double)frequency level:(double)level channel:(ORKAudioChannel)channel {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_frequency = frequency;
|
||||
_level = level;
|
||||
_channel = channel;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"<frequency %.1lf; dBHLValue: %.1lf; channel: %@>", self.frequency, self.level, self.channel == ORKAudioChannelLeft ? @"left" : @"right"];
|
||||
}
|
||||
|
||||
@end
|
||||
+24
-23
@@ -59,6 +59,7 @@ typedef NSString * ORKVolumeCurveFilename NS_STRING_ENUM;
|
||||
ORKVolumeCurveFilename const ORKVolumeCurveFilenameAirPods = @"volume_curve_AIRPODS";
|
||||
ORKVolumeCurveFilename const ORKVolumeCurveFilenameAirPodsGen3 = @"volume_curve_AIRPODSV3";
|
||||
ORKVolumeCurveFilename const ORKVolumeCurveFilenameAirPodsPro = @"volume_curve_AIRPODSPRO";
|
||||
ORKVolumeCurveFilename const ORKVolumeCurveFilenameAirPodsProGen2 = @"volume_curve_AIRPODSPROV2";
|
||||
ORKVolumeCurveFilename const ORKVolumeCurveFilenameAirPodsMax = @"volume_curve_AIRPODSMAX";
|
||||
ORKVolumeCurveFilename const ORKVolumeCurveFilenameWired = @"volume_curve_WIRED";
|
||||
|
||||
@@ -74,7 +75,7 @@ NSString * const filenameExtension = @"plist";
|
||||
AUNode _mixerNode;
|
||||
AudioUnit _mMixer;
|
||||
double _frequency;
|
||||
double _theta;
|
||||
unsigned long _thetaIndex;
|
||||
ORKAudioChannel _activeChannel;
|
||||
BOOL _playsStereo;
|
||||
BOOL _rampUp;
|
||||
@@ -106,32 +107,17 @@ static OSStatus ORKdBHLAudioGeneratorRenderTone(void *inRefCon,
|
||||
|
||||
amplitude = [audioGenerator->_amplitudeGain doubleValue];
|
||||
|
||||
double theta = audioGenerator->_theta;
|
||||
double theta_increment = 2.0 * M_PI * audioGenerator->_frequency / ORKdBHLSineWaveToneGeneratorSampleRateDefault;
|
||||
|
||||
double theta_increment = audioGenerator->_frequency / ORKdBHLSineWaveToneGeneratorSampleRateDefault;
|
||||
unsigned long theta_index = audioGenerator->_thetaIndex;
|
||||
|
||||
double fadeInFactor = audioGenerator->_fadeInFactor;
|
||||
|
||||
// This is a mono tone generator so we only need the first buffer
|
||||
Float32 *bufferActive = (Float32 *)ioData->mBuffers[audioGenerator->_activeChannel].mData;
|
||||
Float32 *bufferNonActive = (Float32 *)ioData->mBuffers[1 - audioGenerator->_activeChannel].mData;
|
||||
|
||||
|
||||
// Generate the samples
|
||||
for (UInt32 frame = 0; frame < inNumberFrames; frame++) {
|
||||
double bufferValue;
|
||||
|
||||
bufferValue = sin(theta) * amplitude * pow(10, 2.0 * fadeInFactor - 2);
|
||||
|
||||
bufferActive[frame] = bufferValue;
|
||||
if (audioGenerator->_playsStereo) {
|
||||
bufferNonActive[frame] = bufferValue;
|
||||
} else {
|
||||
bufferNonActive[frame] = 0;
|
||||
}
|
||||
|
||||
theta += theta_increment;
|
||||
if (theta > 2.0 * M_PI) {
|
||||
theta -= 2.0 * M_PI;
|
||||
}
|
||||
if (audioGenerator->_rampUp) {
|
||||
fadeInFactor += 1.0 / (ORKdBHLSineWaveToneGeneratorSampleRateDefault * audioGenerator->_fadeInDuration);
|
||||
if (fadeInFactor >= 1) {
|
||||
@@ -143,10 +129,21 @@ static OSStatus ORKdBHLAudioGeneratorRenderTone(void *inRefCon,
|
||||
fadeInFactor = 0;
|
||||
}
|
||||
}
|
||||
|
||||
double theta = theta_index * theta_increment;
|
||||
double bufferValue = sin(theta * 2.0 * M_PI) * amplitude * pow(10, 2.0 * fadeInFactor - 2);
|
||||
theta_index++;
|
||||
|
||||
bufferActive[frame] = bufferValue;
|
||||
if (audioGenerator->_playsStereo) {
|
||||
bufferNonActive[frame] = bufferValue;
|
||||
} else {
|
||||
bufferNonActive[frame] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Store the theta back in the view controller
|
||||
audioGenerator->_theta = theta;
|
||||
// Store the thetaIndex back in the view controller
|
||||
audioGenerator->_thetaIndex = theta_index;
|
||||
audioGenerator->_fadeInFactor = fadeInFactor;
|
||||
|
||||
return noErr;
|
||||
@@ -196,6 +193,9 @@ static OSStatus ORKdBHLAudioGeneratorZeroTone(void *inRefCon,
|
||||
} else if ([headphoneTypeUppercased isEqualToString:ORKHeadphoneTypeIdentifierAirPodsPro]) {
|
||||
headphoneTypeIdentifier = ORKHeadphoneTypeIdentifierAirPodsPro;
|
||||
volumeCurveFilename = ORKVolumeCurveFilenameAirPodsPro;
|
||||
} else if ([headphoneTypeUppercased isEqualToString:ORKHeadphoneTypeIdentifierAirPodsProGen2]) {
|
||||
headphoneTypeIdentifier = ORKHeadphoneTypeIdentifierAirPodsProGen2;
|
||||
volumeCurveFilename = ORKVolumeCurveFilenameAirPodsProGen2;
|
||||
} else if ([headphoneTypeUppercased isEqualToString:ORKHeadphoneTypeIdentifierAirPodsMax]) {
|
||||
headphoneTypeIdentifier = ORKHeadphoneTypeIdentifierAirPodsMax;
|
||||
volumeCurveFilename = ORKVolumeCurveFilenameAirPodsMax;
|
||||
@@ -239,6 +239,7 @@ static OSStatus ORKdBHLAudioGeneratorZeroTone(void *inRefCon,
|
||||
_fadeInDuration = 0.2;
|
||||
_rampUp = YES;
|
||||
_globaldBHL = dBHL;
|
||||
_thetaIndex = 0;
|
||||
|
||||
[self play];
|
||||
|
||||
@@ -392,7 +393,7 @@ static OSStatus ORKdBHLAudioGeneratorZeroTone(void *inRefCon,
|
||||
|
||||
- (NSNumber *)dbHLtoAmplitude: (double)dbHL atFrequency:(double)frequency {
|
||||
NSDecimalNumber *dBSPL = [NSDecimalNumber decimalNumberWithString:_sensitivityPerFrequency[[NSString stringWithFormat:@"%.0f",frequency]]];
|
||||
|
||||
|
||||
// get current volume
|
||||
float currentVolume = [self getCurrentSystemVolume];
|
||||
|
||||
+1
@@ -82,6 +82,7 @@ static const CGFloat TestingInProgressIndicatorRadius = 6.0;
|
||||
percentageFormatter.numberStyle = NSNumberFormatterPercentStyle;
|
||||
percentageFormatter.roundingIncrement = @(10);
|
||||
percentageFormatter.roundingMode = NSNumberFormatterRoundHalfUp;
|
||||
percentageFormatter.maximum = @100;
|
||||
percentageFormatter.locale = [NSLocale currentLocale];
|
||||
}
|
||||
|
||||
+1
@@ -30,6 +30,7 @@
|
||||
|
||||
|
||||
#import <ResearchKit/ORKResult.h>
|
||||
#import <ResearchKit/ORKTypes.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
+12
-7
@@ -67,16 +67,20 @@ const double ORKInvalidDBHLValue = DBL_MAX;
|
||||
BOOL isParentSame = [super isEqual:object];
|
||||
|
||||
__typeof(self) castObject = object;
|
||||
return (isParentSame &&
|
||||
self.outputVolume == castObject.outputVolume &&
|
||||
self.tonePlaybackDuration == castObject.tonePlaybackDuration &&
|
||||
self.postStimulusDelay == castObject.postStimulusDelay &&
|
||||
ORKEqualObjects(self.headphoneType, castObject.headphoneType) &&
|
||||
ORKEqualObjects(self.samples, castObject.samples)) ;
|
||||
return (isParentSame
|
||||
&& self.outputVolume == castObject.outputVolume
|
||||
&& self.tonePlaybackDuration == castObject.tonePlaybackDuration
|
||||
&& self.postStimulusDelay == castObject.postStimulusDelay
|
||||
&& ORKEqualObjects(self.headphoneType, castObject.headphoneType)
|
||||
&& ORKEqualObjects(self.samples, castObject.samples)
|
||||
);
|
||||
}
|
||||
|
||||
- (NSUInteger)hash {
|
||||
return super.hash ^ self.samples.hash;
|
||||
NSUInteger resultsHash = self.samples.hash ^ self.headphoneType.hash;
|
||||
|
||||
|
||||
return super.hash ^ resultsHash;
|
||||
}
|
||||
|
||||
- (instancetype)copyWithZone:(NSZone *)zone {
|
||||
@@ -86,6 +90,7 @@ const double ORKInvalidDBHLValue = DBL_MAX;
|
||||
result.tonePlaybackDuration = self.tonePlaybackDuration;
|
||||
result.postStimulusDelay = self.postStimulusDelay;
|
||||
result.samples = [self.samples copy];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
+7
@@ -30,14 +30,18 @@
|
||||
|
||||
|
||||
@import Foundation;
|
||||
#import <ResearchKit/ORKTypes.h>
|
||||
#import <ResearchKit/ORKDefines.h>
|
||||
#import <ResearchKit/ORKActiveStep.h>
|
||||
#import <ResearchKit/ORKAudiometryProtocol.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
ORK_CLASS_AVAILABLE
|
||||
@interface ORKdBHLToneAudiometryStep : ORKActiveStep
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier audiometryEngine:(nullable id<ORKAudiometryProtocol>)audiometry;
|
||||
|
||||
@property (nonatomic, assign) NSTimeInterval toneDuration;
|
||||
|
||||
@property (nonatomic, assign) NSTimeInterval maxRandomPreStimulusDelay;
|
||||
@@ -66,6 +70,9 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@property (nonatomic, copy, nullable) NSArray *frequencyList;
|
||||
|
||||
|
||||
- (id<ORKAudiometryProtocol>)audiometryEngine;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
+19
-3
@@ -30,8 +30,8 @@
|
||||
|
||||
|
||||
#import "ORKdBHLToneAudiometryStep.h"
|
||||
|
||||
#import "ORKdBHLToneAudiometryStepViewController.h"
|
||||
#import "ORKAudiometry.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
|
||||
@@ -47,16 +47,24 @@
|
||||
#define ORKdBHLToneAudiometryTaskdBHLStepDownSize 10.0
|
||||
#define ORKdBHLToneAudiometryTaskdBHLMinimumThreshold -10.0
|
||||
|
||||
@implementation ORKdBHLToneAudiometryStep
|
||||
|
||||
@implementation ORKdBHLToneAudiometryStep {
|
||||
id<ORKAudiometryProtocol> _audiometry;
|
||||
}
|
||||
|
||||
+ (Class)stepViewControllerClass {
|
||||
return [ORKdBHLToneAudiometryStepViewController class];
|
||||
}
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
||||
return [self initWithIdentifier:identifier audiometryEngine:nil];
|
||||
}
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier audiometryEngine:(id<ORKAudiometryProtocol>)audiometry {
|
||||
self = [super initWithIdentifier:identifier];
|
||||
if (self) {
|
||||
[self commonInit];
|
||||
_audiometry = audiometry;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -179,7 +187,15 @@
|
||||
&& (self.dBHLMinimumThreshold == castObject.dBHLMinimumThreshold)
|
||||
&& (self.earPreference == castObject.earPreference)
|
||||
&& ORKEqualObjects(self.headphoneType, castObject.headphoneType)
|
||||
&& ORKEqualObjects(self.frequencyList, castObject.frequencyList));
|
||||
&& ORKEqualObjects(self.frequencyList, castObject.frequencyList)
|
||||
);
|
||||
}
|
||||
|
||||
- (id<ORKAudiometryProtocol>)audiometryEngine {
|
||||
if (!_audiometry) {
|
||||
_audiometry = [[ORKAudiometry alloc] initWithStep:self];
|
||||
}
|
||||
return _audiometry;
|
||||
}
|
||||
|
||||
@end
|
||||
+55
-211
@@ -55,51 +55,16 @@
|
||||
#import "ORKTaskViewController_Internal.h"
|
||||
#import "ORKOrderedTask.h"
|
||||
|
||||
@interface ORKdBHLToneAudiometryTransitions: NSObject
|
||||
|
||||
@property (nonatomic, assign) float userInitiated;
|
||||
@property (nonatomic, assign) float totalTransitions;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ORKdBHLToneAudiometryTransitions
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_userInitiated = 1;
|
||||
_totalTransitions = 1;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
#import "ORKNavigableOrderedTask.h"
|
||||
#import "ORKStepNavigationRule.h"
|
||||
|
||||
@interface ORKdBHLToneAudiometryStepViewController () <ORKdBHLToneAudiometryAudioGeneratorDelegate> {
|
||||
double _prevFreq;
|
||||
double _currentdBHL;
|
||||
double _dBHLStepUpSize;
|
||||
double _dBHLStepDownSize;
|
||||
double _dBHLMinimumThreshold;
|
||||
int _currentTestIndex;
|
||||
int _indexOfFreqLoopList;
|
||||
NSUInteger _indexOfStepUpMissingList;
|
||||
int _numberOfTransitionsPerFreq;
|
||||
NSInteger _maxNumberOfTransitionsPerFreq;
|
||||
BOOL _initialDescent;
|
||||
BOOL _ackOnce;
|
||||
BOOL _usingMissingList;
|
||||
ORKdBHLToneAudiometryAudioGenerator *_audioGenerator;
|
||||
NSArray *_freqLoopList;
|
||||
NSArray *_stepUpMissingList;
|
||||
NSMutableArray *_arrayOfResultSamples;
|
||||
NSMutableArray *_arrayOfResultUnits;
|
||||
NSMutableDictionary *_transitionsDictionary;
|
||||
UIImpactFeedbackGenerator *_hapticFeedback;
|
||||
ORKdBHLToneAudiometryFrequencySample *_resultSample;
|
||||
ORKdBHLToneAudiometryUnit *_resultUnit;
|
||||
ORKAudioChannel _audioChannel;
|
||||
|
||||
ORKdBHLToneAudiometryAudioGenerator *_audioGenerator;
|
||||
UIImpactFeedbackGenerator *_hapticFeedback;
|
||||
|
||||
dispatch_block_t _preStimulusDelayWorkBlock;
|
||||
dispatch_block_t _pulseDurationWorkBlock;
|
||||
dispatch_block_t _postStimulusDelayWorkBlock;
|
||||
@@ -117,18 +82,13 @@
|
||||
|
||||
if (self) {
|
||||
self.suspendIfInactive = YES;
|
||||
_indexOfFreqLoopList = 0;
|
||||
_indexOfStepUpMissingList = 0;
|
||||
_initialDescent = YES;
|
||||
_ackOnce = NO;
|
||||
_usingMissingList = YES;
|
||||
_prevFreq = 0;
|
||||
_currentTestIndex = 0;
|
||||
_transitionsDictionary = [NSMutableDictionary dictionary];
|
||||
_arrayOfResultSamples = [NSMutableArray array];
|
||||
_arrayOfResultUnits = [NSMutableArray array];
|
||||
|
||||
ORKWeakTypeOf(self) weakSelf = self;
|
||||
self.audiometryEngine.timestampProvider = ^NSTimeInterval{
|
||||
ORKStrongTypeOf(self) strongSelf = weakSelf;
|
||||
return strongSelf ? strongSelf.runtime : 0;
|
||||
};
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -144,25 +104,18 @@
|
||||
return (ORKdBHLToneAudiometryStep *)self.step;
|
||||
}
|
||||
|
||||
- (id<ORKAudiometryProtocol>)audiometryEngine {
|
||||
return self.dBHLToneAudiometryStep.audiometryEngine;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
[self configureStep];
|
||||
}
|
||||
|
||||
- (void)configureStep {
|
||||
ORKdBHLToneAudiometryStep *dBHLTAStep = [self dBHLToneAudiometryStep];
|
||||
|
||||
_maxNumberOfTransitionsPerFreq = dBHLTAStep.maxNumberOfTransitionsPerFrequency;
|
||||
_freqLoopList = dBHLTAStep.frequencyList;
|
||||
_stepUpMissingList = @[ [NSNumber numberWithDouble:dBHLTAStep.dBHLStepUpSizeFirstMiss],
|
||||
[NSNumber numberWithDouble:dBHLTAStep.dBHLStepUpSizeSecondMiss],
|
||||
[NSNumber numberWithDouble:dBHLTAStep.dBHLStepUpSizeThirdMiss] ];
|
||||
_currentdBHL = dBHLTAStep.initialdBHLValue;
|
||||
_dBHLStepDownSize = dBHLTAStep.dBHLStepDownSize;
|
||||
_dBHLStepUpSize = dBHLTAStep.dBHLStepUpSize;
|
||||
_dBHLMinimumThreshold = dBHLTAStep.dBHLMinimumThreshold;
|
||||
|
||||
self.dBHLToneAudiometryContentView = [[ORKdBHLToneAudiometryContentView alloc] init];
|
||||
self.activeStepView.activeCustomView = self.dBHLToneAudiometryContentView;
|
||||
self.activeStepView.customContentFillsAvailableSpace = YES;
|
||||
@@ -193,6 +146,8 @@
|
||||
[self start];
|
||||
[self addObservers];
|
||||
}
|
||||
|
||||
|
||||
-(void)appWillTerminate:(NSNotification*)note {
|
||||
[self stopAudio];
|
||||
[self removeObservers];
|
||||
@@ -222,13 +177,12 @@
|
||||
- (void)viewDidDisappear:(BOOL)animated {
|
||||
[super viewDidDisappear:animated];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
|
||||
|
||||
_audioGenerator.delegate = nil;
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
[super viewWillDisappear:animated];
|
||||
|
||||
[self removeObservers];
|
||||
[self stopAudio];
|
||||
}
|
||||
@@ -244,7 +198,7 @@
|
||||
ORKdBHLToneAudiometryResult *toneResult = [[ORKdBHLToneAudiometryResult alloc] initWithIdentifier:self.step.identifier];
|
||||
toneResult.startDate = sResult.startDate;
|
||||
toneResult.endDate = now;
|
||||
toneResult.samples = [_arrayOfResultSamples copy];
|
||||
toneResult.samples = [self.audiometryEngine resultSamples];
|
||||
toneResult.outputVolume = [AVAudioSession sharedInstance].outputVolume;
|
||||
toneResult.headphoneType = self.dBHLToneAudiometryStep.headphoneType;
|
||||
toneResult.tonePlaybackDuration = [self dBHLToneAudiometryStep].toneDuration;
|
||||
@@ -265,7 +219,7 @@
|
||||
|
||||
- (void)start {
|
||||
[super start];
|
||||
[self estimatedBHLAndPlayToneWithFrequency:_freqLoopList[_indexOfFreqLoopList]];
|
||||
[self runTestTrial];
|
||||
}
|
||||
|
||||
- (void)stopAudio {
|
||||
@@ -277,71 +231,33 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)estimatedBHLAndPlayToneWithFrequency: (NSNumber *)freq {
|
||||
- (void)runTestTrial {
|
||||
[self stopAudio];
|
||||
if (_prevFreq != [freq doubleValue]) {
|
||||
CGFloat progress = 0.001 + (CGFloat)_indexOfFreqLoopList / _freqLoopList.count;
|
||||
[self.dBHLToneAudiometryContentView setProgress:progress
|
||||
animated:YES];
|
||||
|
||||
|
||||
[self.dBHLToneAudiometryContentView setProgress:self.audiometryEngine.progress animated:YES];
|
||||
|
||||
ORKAudiometryStimulus *stimulus = self.audiometryEngine.nextStimulus;
|
||||
if (!stimulus) {
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[self nextTrial];
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
_numberOfTransitionsPerFreq = 0;
|
||||
_currentdBHL = [self dBHLToneAudiometryStep].initialdBHLValue;
|
||||
_initialDescent = YES;
|
||||
_ackOnce = NO;
|
||||
_usingMissingList = YES;
|
||||
_indexOfStepUpMissingList = 0;
|
||||
_transitionsDictionary = nil;
|
||||
_transitionsDictionary = [NSMutableDictionary dictionary];
|
||||
if (_resultSample) {
|
||||
_resultSample.units = [_arrayOfResultUnits copy];
|
||||
}
|
||||
_arrayOfResultUnits = [NSMutableArray array];
|
||||
_prevFreq = [freq doubleValue];
|
||||
_resultSample = [ORKdBHLToneAudiometryFrequencySample new];
|
||||
_resultSample.channel = _audioChannel;
|
||||
_resultSample.frequency = [freq doubleValue];
|
||||
_resultSample.calculatedThreshold = ORKInvalidDBHLValue;
|
||||
[_arrayOfResultSamples addObject:_resultSample];
|
||||
} else {
|
||||
_numberOfTransitionsPerFreq += 1;
|
||||
if (_numberOfTransitionsPerFreq >= _maxNumberOfTransitionsPerFreq) {
|
||||
_indexOfFreqLoopList += 1;
|
||||
if (_indexOfFreqLoopList >= _freqLoopList.count) {
|
||||
_resultSample.units = [_arrayOfResultUnits copy];
|
||||
[self finish];
|
||||
return;
|
||||
} else {
|
||||
[self estimatedBHLAndPlayToneWithFrequency:_freqLoopList[_indexOfFreqLoopList]];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_resultUnit = [ORKdBHLToneAudiometryUnit new];
|
||||
_resultUnit.dBHLValue = _currentdBHL;
|
||||
_resultUnit.startOfUnitTimeStamp = self.runtime;
|
||||
[_arrayOfResultUnits addObject:_resultUnit];
|
||||
|
||||
ORKdBHLToneAudiometryTransitions *currentTransition = [_transitionsDictionary objectForKey:[NSNumber numberWithFloat:_currentdBHL]];
|
||||
if (!_initialDescent) {
|
||||
if (currentTransition) {
|
||||
currentTransition.userInitiated += 1;
|
||||
currentTransition.totalTransitions += 1;
|
||||
} else {
|
||||
currentTransition = [[ORKdBHLToneAudiometryTransitions alloc] init];
|
||||
[_transitionsDictionary setObject:currentTransition forKey:[NSNumber numberWithFloat:_currentdBHL]];
|
||||
}
|
||||
}
|
||||
const NSTimeInterval toneDuration = [self dBHLToneAudiometryStep].toneDuration;
|
||||
const NSTimeInterval postStimulusDelay = [self dBHLToneAudiometryStep].postStimulusDelay;
|
||||
|
||||
double delay1 = arc4random_uniform([self dBHLToneAudiometryStep].maxRandomPreStimulusDelay - 1);
|
||||
double delay2 = (double)arc4random_uniform(10)/10;
|
||||
double preStimulusDelay = delay1 + delay2 + 1;
|
||||
_resultUnit.preStimulusDelay = preStimulusDelay;
|
||||
[self.audiometryEngine registerPreStimulusDelay:preStimulusDelay];
|
||||
|
||||
_preStimulusDelayWorkBlock = dispatch_block_create(DISPATCH_BLOCK_INHERIT_QOS_CLASS, ^{
|
||||
[_audioGenerator playSoundAtFrequency:[freq floatValue] onChannel:_audioChannel dBHL:_currentdBHL];
|
||||
if ([[self audiometryEngine] respondsToSelector:@selector(registerStimulusPlayback)]) {
|
||||
[self.audiometryEngine registerStimulusPlayback];
|
||||
}
|
||||
[_audioGenerator playSoundAtFrequency:stimulus.frequency onChannel:stimulus.channel dBHL:stimulus.level];
|
||||
});
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(preStimulusDelay * NSEC_PER_SEC)), dispatch_get_main_queue(), _preStimulusDelayWorkBlock);
|
||||
|
||||
@@ -350,110 +266,38 @@
|
||||
});
|
||||
// adding 0.2 seconds to account for the fadeInDuration which is being set in ORKdBHLToneAudiometryAudioGenerator
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((preStimulusDelay + toneDuration + 0.2) * NSEC_PER_SEC)), dispatch_get_main_queue(), _pulseDurationWorkBlock);
|
||||
|
||||
ORKWeakTypeOf(self)weakSelf = self;
|
||||
|
||||
_postStimulusDelayWorkBlock = dispatch_block_create(DISPATCH_BLOCK_INHERIT_QOS_CLASS, ^{
|
||||
ORKStrongTypeOf(self) strongSelf = weakSelf;
|
||||
NSUInteger storedTestIndex = _currentTestIndex;
|
||||
if (_currentTestIndex == storedTestIndex) {
|
||||
if (_initialDescent && _ackOnce) {
|
||||
_initialDescent = NO;
|
||||
ORKdBHLToneAudiometryTransitions *newTransition = [[ORKdBHLToneAudiometryTransitions alloc] init];
|
||||
newTransition.userInitiated -= 1;
|
||||
[_transitionsDictionary setObject:newTransition forKey:[NSNumber numberWithFloat:_currentdBHL]];
|
||||
}
|
||||
if (_usingMissingList && (_indexOfStepUpMissingList < _stepUpMissingList.count)) {
|
||||
_currentdBHL = _currentdBHL + [_stepUpMissingList[_indexOfStepUpMissingList] doubleValue];
|
||||
_indexOfStepUpMissingList = _indexOfStepUpMissingList + 1;
|
||||
} else {
|
||||
_usingMissingList = NO;
|
||||
_currentdBHL = _currentdBHL + _dBHLStepUpSize;
|
||||
}
|
||||
if (currentTransition) {
|
||||
currentTransition.userInitiated -= 1;
|
||||
}
|
||||
_resultUnit.timeoutTimeStamp = self.runtime;
|
||||
_currentTestIndex += 1;
|
||||
[strongSelf estimatedBHLAndPlayToneWithFrequency:_freqLoopList[_indexOfFreqLoopList]];
|
||||
return;
|
||||
}
|
||||
[self.audiometryEngine registerResponse:NO];
|
||||
[self nextTrial];
|
||||
});
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((preStimulusDelay + toneDuration + postStimulusDelay) * NSEC_PER_SEC)), dispatch_get_main_queue(), _postStimulusDelayWorkBlock);
|
||||
}
|
||||
|
||||
- (void)nextTrial {
|
||||
if (self.audiometryEngine.testEnded) {
|
||||
[self finish];
|
||||
} else {
|
||||
[self runTestTrial];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)tapButtonPressed {
|
||||
[self animatedBHLButton];
|
||||
_ackOnce = YES;
|
||||
[_hapticFeedback impactOccurred];
|
||||
_currentTestIndex += 1;
|
||||
_resultUnit.userTapTimeStamp = self.runtime;
|
||||
[self stopAudio];
|
||||
BOOL falseResponseTap = (_resultUnit.userTapTimeStamp - _resultUnit.startOfUnitTimeStamp < _resultUnit.preStimulusDelay);
|
||||
if (falseResponseTap) {
|
||||
NSNumber *currentKey = [NSNumber numberWithFloat:_currentdBHL];
|
||||
ORKdBHLToneAudiometryTransitions *currentTransitionObject = [_transitionsDictionary objectForKey:currentKey];
|
||||
currentTransitionObject.userInitiated -= 1;
|
||||
} else if ([self validateResultFordBHL:_currentdBHL]) {
|
||||
_resultSample.calculatedThreshold = _currentdBHL;
|
||||
_indexOfFreqLoopList += 1;
|
||||
if (_indexOfFreqLoopList >= _freqLoopList.count) {
|
||||
_resultSample.units = [_arrayOfResultUnits copy];
|
||||
[self finish];
|
||||
return;
|
||||
} else {
|
||||
_currentdBHL = [self dBHLToneAudiometryStep].initialdBHLValue;
|
||||
[self estimatedBHLAndPlayToneWithFrequency:_freqLoopList[_indexOfFreqLoopList]];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ((_currentdBHL - _dBHLStepDownSize >= _dBHLMinimumThreshold) && !falseResponseTap) {
|
||||
_usingMissingList = NO;
|
||||
_currentdBHL = _currentdBHL - _dBHLStepDownSize;
|
||||
if (_preStimulusDelayWorkBlock && dispatch_block_testcancel(_preStimulusDelayWorkBlock) == 0) {
|
||||
[self.audiometryEngine registerResponse:YES];
|
||||
}
|
||||
|
||||
[self estimatedBHLAndPlayToneWithFrequency:_freqLoopList[_indexOfFreqLoopList]];
|
||||
return;
|
||||
}
|
||||
|
||||
- (BOOL)validateResultFordBHL:(float)dBHL {
|
||||
NSNumber *currentKey = [NSNumber numberWithFloat:_currentdBHL];
|
||||
ORKdBHLToneAudiometryTransitions *currentTransitionObject = [_transitionsDictionary objectForKey:currentKey];
|
||||
if ((currentTransitionObject.userInitiated/currentTransitionObject.totalTransitions >= 0.5) && currentTransitionObject.totalTransitions >= 2) {
|
||||
ORKdBHLToneAudiometryTransitions *previousTransitionObject = [_transitionsDictionary objectForKey:[NSNumber numberWithFloat:(dBHL - _dBHLStepUpSize)]];
|
||||
if ((previousTransitionObject.userInitiated/previousTransitionObject.totalTransitions <= 0.5) && (previousTransitionObject.totalTransitions >= 2)) {
|
||||
if (currentTransitionObject.totalTransitions == 2) {
|
||||
if (currentTransitionObject.userInitiated/currentTransitionObject.totalTransitions == 1.0) {
|
||||
_resultSample.calculatedThreshold = dBHL;
|
||||
return YES;
|
||||
} else {
|
||||
return NO;
|
||||
}
|
||||
} else {
|
||||
_resultSample.calculatedThreshold = dBHL;
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
[self nextTrial];
|
||||
}
|
||||
|
||||
- (void)toneWillStartClipping {
|
||||
if (_usingMissingList
|
||||
&& (_indexOfStepUpMissingList <= _stepUpMissingList.count)) {
|
||||
_usingMissingList = NO;
|
||||
_currentdBHL = _currentdBHL - [_stepUpMissingList[_indexOfStepUpMissingList - (_indexOfStepUpMissingList == 0 ? 0 : 1)] doubleValue] + _dBHLStepUpSize;
|
||||
[self estimatedBHLAndPlayToneWithFrequency:_freqLoopList[_indexOfFreqLoopList]];
|
||||
} else {
|
||||
_indexOfFreqLoopList += 1;
|
||||
if (_indexOfFreqLoopList >= _freqLoopList.count) {
|
||||
_resultSample.units = [_arrayOfResultUnits copy];
|
||||
[self finish];
|
||||
} else {
|
||||
[self estimatedBHLAndPlayToneWithFrequency:_freqLoopList[_indexOfFreqLoopList]];
|
||||
}
|
||||
if ([self.audiometryEngine respondsToSelector:@selector(signalClipped)]) {
|
||||
[self.audiometryEngine signalClipped];
|
||||
}
|
||||
[self nextTrial];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
@@ -21,7 +21,7 @@
|
||||
<key>4000</key>
|
||||
<string>89.76</string>
|
||||
<key>6000</key>
|
||||
<string>91.64</string>
|
||||
<string>86.64</string>
|
||||
<key>8000</key>
|
||||
<string>88.22</string>
|
||||
</dict>
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>125</key>
|
||||
<string>84.05</string>
|
||||
<key>250</key>
|
||||
<string>83.16</string>
|
||||
<key>500</key>
|
||||
<string>84.13</string>
|
||||
<key>750</key>
|
||||
<string>83.95</string>
|
||||
<key>1000</key>
|
||||
<string>83.67</string>
|
||||
<key>1500</key>
|
||||
<string>84.79</string>
|
||||
<key>2000</key>
|
||||
<string>86.52</string>
|
||||
<key>3000</key>
|
||||
<string>89.24</string>
|
||||
<key>4000</key>
|
||||
<string>86.64</string>
|
||||
<key>6000</key>
|
||||
<string>86.50</string>
|
||||
<key>8000</key>
|
||||
<string>90.11</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>125</key>
|
||||
<string>31</string>
|
||||
<key>250</key>
|
||||
<string>21</string>
|
||||
<key>500</key>
|
||||
<string>13</string>
|
||||
<key>750</key>
|
||||
<string>9</string>
|
||||
<key>1000</key>
|
||||
<string>7</string>
|
||||
<key>1500</key>
|
||||
<string>10</string>
|
||||
<key>2000</key>
|
||||
<string>12</string>
|
||||
<key>3000</key>
|
||||
<string>13.5</string>
|
||||
<key>4000</key>
|
||||
<string>16</string>
|
||||
<key>6000</key>
|
||||
<string>16</string>
|
||||
<key>8000</key>
|
||||
<string>15.5</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>1.0000</key>
|
||||
<string>0</string>
|
||||
<key>0.9375</key>
|
||||
<string>-3</string>
|
||||
<key>0.8750</key>
|
||||
<string>-6.5</string>
|
||||
<key>0.8125</key>
|
||||
<string>-10</string>
|
||||
<key>0.7500</key>
|
||||
<string>-13.5</string>
|
||||
<key>0.6875</key>
|
||||
<string>-17</string>
|
||||
<key>0.6250</key>
|
||||
<string>-21</string>
|
||||
<key>0.5625</key>
|
||||
<string>-25</string>
|
||||
<key>0.5000</key>
|
||||
<string>-29</string>
|
||||
<key>0.4375</key>
|
||||
<string>-33</string>
|
||||
<key>0.3750</key>
|
||||
<string>-37.5</string>
|
||||
<key>0.3125</key>
|
||||
<string>-42</string>
|
||||
<key>0.2500</key>
|
||||
<string>-47</string>
|
||||
<key>0.1875</key>
|
||||
<string>-52.5</string>
|
||||
<key>0.1250</key>
|
||||
<string>-58.5</string>
|
||||
<key>0.0625</key>
|
||||
<string>-65.5</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "Research-ladder.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "Research-ladder@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "Research-ladder@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 606 B |
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 822 B |
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 1.2 KiB |
@@ -368,7 +368,6 @@ ORK_CLASS_AVAILABLE
|
||||
/**
|
||||
The image that will be presented to the left of the text provided for the textChoice
|
||||
*/
|
||||
|
||||
@property (strong, nullable) UIImage *image;
|
||||
|
||||
/**
|
||||
@@ -429,12 +428,20 @@ ORK_CLASS_AVAILABLE
|
||||
maximumDate:(nullable NSDate *)maximumDate
|
||||
calendar:(nullable NSCalendar *)calendar;
|
||||
|
||||
+ (ORKDateAnswerFormat *)dateTimeAnswerFormatWithDaysBeforeCurrentDate:(NSInteger)daysBefore
|
||||
daysAfterCurrentDate:(NSInteger)daysAfter
|
||||
calendar:(nullable NSCalendar *)calendar;
|
||||
|
||||
+ (ORKDateAnswerFormat *)dateAnswerFormat;
|
||||
+ (ORKDateAnswerFormat *)dateAnswerFormatWithDefaultDate:(nullable NSDate *)defaultDate
|
||||
minimumDate:(nullable NSDate *)minimumDate
|
||||
maximumDate:(nullable NSDate *)maximumDate
|
||||
calendar:(nullable NSCalendar *)calendar;
|
||||
|
||||
+ (ORKDateAnswerFormat *)dateAnswerFormatWithDaysBeforeCurrentDate:(NSInteger)daysBefore
|
||||
daysAfterCurrentDate:(NSInteger)daysAfter
|
||||
calendar:(nullable NSCalendar *)calendar;
|
||||
|
||||
+ (ORKTextAnswerFormat *)textAnswerFormat;
|
||||
|
||||
+ (ORKTextAnswerFormat *)textAnswerFormatWithMaximumLength:(NSInteger)maximumLength;
|
||||
@@ -1117,7 +1124,6 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
The `ORKTextChoiceOther` class defines the choice option to describe an answer not
|
||||
included in provided choices.
|
||||
@@ -1135,7 +1141,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@param text The primary text that describes the choice in a localized string.
|
||||
@param detailText The detail text to display below the primary text, in a localized string.
|
||||
@param value The value to record in a result object when this item is selected.
|
||||
@param value The value to record in a result object when this item is selected. Only `NSString`, `NSNumber`, and `NSDate` values are supported.
|
||||
@param exclusive Whether this choice is to be considered exclusive within the set of choices.
|
||||
@param textViewPlaceholderText The placeholder text for the text view.
|
||||
|
||||
@@ -1143,7 +1149,7 @@ ORK_CLASS_AVAILABLE
|
||||
*/
|
||||
+ (instancetype)choiceWithText:(nullable NSString *)text
|
||||
detailText:(nullable NSString *)detailText
|
||||
value:(id<NSCopying, NSCoding, NSObject>)value
|
||||
value:(NSObject<NSCopying, NSSecureCoding> *)value
|
||||
exclusive:(BOOL)exclusive
|
||||
textViewPlaceholderText:(NSString *)textViewPlaceholderText;
|
||||
|
||||
@@ -1156,7 +1162,7 @@ ORK_CLASS_AVAILABLE
|
||||
@param primaryTextAttributedString The primary text that describes the choice in an attributed string. Setting this will override `text`.
|
||||
@param detailText The detail text to display below the primary text, in a localized string.
|
||||
@param detailTextAttributedString The detail text to display below the primary text, in an attributed string. Setting this will override `detailText`.
|
||||
@param value The value to record in a result object when this item is selected.
|
||||
@param value The value to record in a result object when this item is selected. Only `NSString`, `NSNumber`, and `NSDate` values are supported.
|
||||
@param exclusive Whether this choice is to be considered exclusive within the set of choices.
|
||||
@param textViewPlaceholderText The placeholder text for the text view.
|
||||
@param textViewInputOptional Whether the user is required to provide additional text when selecting this choice.
|
||||
@@ -1168,7 +1174,7 @@ ORK_CLASS_AVAILABLE
|
||||
primaryTextAttributedString:(nullable NSAttributedString *)primaryTextAttributedString
|
||||
detailText:(nullable NSString *)detailText
|
||||
detailTextAttributedString:(nullable NSAttributedString *)detailTextAttributedString
|
||||
value:(id<NSCopying, NSCoding, NSObject>)value
|
||||
value:(NSObject<NSCopying, NSSecureCoding> *)value
|
||||
exclusive:(BOOL)exclusive
|
||||
textViewPlaceholderText:(NSString *)textViewPlaceholderText
|
||||
textViewInputOptional:(BOOL)textViewInputOptional
|
||||
@@ -1344,11 +1350,9 @@ Returns an initialized numeric answer format using the specified style, unit des
|
||||
/**
|
||||
Returns an initialized numeric answer format using the specified style, unit designation, range
|
||||
values, and precision.
|
||||
|
||||
This method is the designated initializer.
|
||||
|
||||
@param style The style of the numeric answer (decimal or integer).
|
||||
@param unit A string that displays a localized version of the unit designation.
|
||||
|
||||
@param style The style of the numeric answer (decimal or integer).
|
||||
@param unit A string that displays a localized version of the unit designation.
|
||||
@param minimum The minimum value to apply, or `nil` if none is specified.
|
||||
@param maximum The maximum value to apply, or `nil` if none is specified.
|
||||
@param maximumFractionDigits The maximum fraction digits, or `nil` if no maximum is specified.
|
||||
@@ -1359,6 +1363,28 @@ Returns an initialized numeric answer format using the specified style, unit des
|
||||
unit:(nullable NSString *)unit
|
||||
minimum:(nullable NSNumber *)minimum
|
||||
maximum:(nullable NSNumber *)maximum
|
||||
maximumFractionDigits:(NSNumber *)maximumFractionDigits;
|
||||
|
||||
/**
|
||||
Returns an initialized numeric answer format using the specified style, unit designation, range
|
||||
values, and precision.
|
||||
|
||||
This method is the designated initializer.
|
||||
|
||||
@param style The style of the numeric answer (decimal or integer).
|
||||
@param unit A string that displays the unit designation in the results.
|
||||
@param displayUnit An string that displays a localized version of the unit designation.
|
||||
@param minimum The minimum value to apply, or `nil` if none is specified.
|
||||
@param maximum The maximum value to apply, or `nil` if none is specified.
|
||||
@param maximumFractionDigits The maximum fraction digits, or `nil` if no maximum is specified.
|
||||
|
||||
@return An initialized numeric answer format.
|
||||
*/
|
||||
- (instancetype)initWithStyle:(ORKNumericAnswerStyle)style
|
||||
unit:(nullable NSString *)unit
|
||||
displayUnit:(nullable NSString *)displayUnit
|
||||
minimum:(nullable NSNumber *)minimum
|
||||
maximum:(nullable NSNumber *)maximum
|
||||
maximumFractionDigits:(nullable NSNumber *)maximumFractionDigits NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/**
|
||||
@@ -1367,14 +1393,26 @@ Returns an initialized numeric answer format using the specified style, unit des
|
||||
@property (readonly) ORKNumericAnswerStyle style;
|
||||
|
||||
/**
|
||||
A string that displays a localized version of the unit designation next to the numeric value.
|
||||
A string that displays the unit designation next to the numeric value in the results.
|
||||
(read-only)
|
||||
|
||||
If displayUnit is not set, the answerFormat will display the unit instead
|
||||
|
||||
Examples of unit designations are days, lbs, and liters.
|
||||
The unit string is included in the `ORKNumericQuestionResult` object.
|
||||
*/
|
||||
@property (copy, readonly, nullable) NSString *unit;
|
||||
|
||||
/**
|
||||
A string that displays a localized version of the display unit designation next to the numeric value.
|
||||
This property will only be used for display UI purposes.
|
||||
If this property is not set, the answerFormat will display the unit instead
|
||||
(read-only)
|
||||
|
||||
Examples of unit designations are days, lbs, and liters.
|
||||
The displayUnit string is included in the `ORKNumericQuestionResult` object.
|
||||
*/
|
||||
@property (copy, readonly, nullable) NSString *displayUnit;
|
||||
|
||||
/**
|
||||
The minimum allowed value for the numeric answer.
|
||||
|
||||
@@ -1547,6 +1585,26 @@ When the value of this property is `nil`, there is no minimum.
|
||||
*/
|
||||
@property (copy, readonly, nullable) NSDate *maximumDate;
|
||||
|
||||
/**
|
||||
This number passed to this property will set the minimum date to the relative amount of days
|
||||
before the current date.
|
||||
*/
|
||||
@property (nonatomic) NSInteger daysBeforeCurrentDateToSetMinimumDate;
|
||||
|
||||
/**
|
||||
This number passed to this property will set the minimum date to the relative amount of days
|
||||
after the current date.
|
||||
*/
|
||||
@property (nonatomic) NSInteger daysAfterCurrentDateToSetMinimumDate;
|
||||
|
||||
|
||||
/**
|
||||
A boolean property that determines if the max date should be set to the current time or not
|
||||
|
||||
When the value of this property is `true`, the max date is set to the current time
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL isMaxDateCurrentTime;
|
||||
|
||||
/**
|
||||
The calendar to use in the picker.
|
||||
|
||||
@@ -1629,6 +1687,8 @@ ORK_CLASS_AVAILABLE
|
||||
*/
|
||||
@property NSInteger maximumLength;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
A Boolean value indicating whether to expect more than one line of input.
|
||||
|
||||
|
||||
@@ -365,6 +365,15 @@ static NSNumberFormatterStyle ORKNumberFormattingStyleConvert(ORKNumberFormattin
|
||||
calendar:calendar];
|
||||
}
|
||||
|
||||
+ (ORKDateAnswerFormat *)dateTimeAnswerFormatWithDaysBeforeCurrentDate:(NSInteger)daysBefore
|
||||
daysAfterCurrentDate:(NSInteger)daysAfter
|
||||
calendar:(nullable NSCalendar *)calendar {
|
||||
return [ORKDateAnswerFormat dateAnswerFormatWithStyle:ORKDateAnswerStyleDateAndTime
|
||||
daysBeforeCurrentDate:daysBefore
|
||||
daysAfterCurrentDate:daysAfter
|
||||
calendar:calendar];
|
||||
}
|
||||
|
||||
+ (ORKDateAnswerFormat *)dateAnswerFormat {
|
||||
return [[ORKDateAnswerFormat alloc] initWithStyle:ORKDateAnswerStyleDate];
|
||||
}
|
||||
@@ -379,6 +388,31 @@ static NSNumberFormatterStyle ORKNumberFormattingStyleConvert(ORKNumberFormattin
|
||||
calendar:calendar];
|
||||
}
|
||||
|
||||
+ (ORKDateAnswerFormat *)dateAnswerFormatWithDaysBeforeCurrentDate:(NSInteger)daysBefore
|
||||
daysAfterCurrentDate:(NSInteger)daysAfter
|
||||
calendar:(nullable NSCalendar *)calendar {
|
||||
return [ORKDateAnswerFormat dateAnswerFormatWithStyle:ORKDateAnswerStyleDate
|
||||
daysBeforeCurrentDate:daysBefore
|
||||
daysAfterCurrentDate:daysAfter
|
||||
calendar:calendar];
|
||||
}
|
||||
|
||||
+ (ORKDateAnswerFormat *)dateAnswerFormatWithStyle:(ORKDateAnswerStyle)style
|
||||
daysBeforeCurrentDate:(NSInteger)daysBefore
|
||||
daysAfterCurrentDate:(NSInteger)daysAfter
|
||||
calendar:(nullable NSCalendar *)calendar {
|
||||
NSDate *currentDate = [NSDate date];
|
||||
ORKDateAnswerFormat *answerFormat = [[ORKDateAnswerFormat alloc] initWithStyle:style
|
||||
defaultDate:currentDate
|
||||
minimumDate:nil
|
||||
maximumDate:nil
|
||||
calendar:calendar];
|
||||
[answerFormat setDaysBeforeCurrentDateToSetMinimumDate:daysBefore];
|
||||
[answerFormat setDaysAfterCurrentDateToSetMinimumDate:daysAfter];
|
||||
|
||||
return answerFormat;
|
||||
}
|
||||
|
||||
+ (ORKTextAnswerFormat *)textAnswerFormat {
|
||||
return [ORKTextAnswerFormat new];
|
||||
}
|
||||
@@ -1232,7 +1266,6 @@ NSArray<Class> *ORKAllowableValueClasses(void) {
|
||||
|
||||
|
||||
#pragma mark - ORKTextChoiceOther
|
||||
#if TARGET_OS_IOS
|
||||
@implementation ORKTextChoiceOther
|
||||
|
||||
+ (instancetype)new {
|
||||
@@ -1456,7 +1489,6 @@ NSArray<Class> *ORKAllowableValueClasses(void) {
|
||||
}
|
||||
|
||||
@end
|
||||
#endif
|
||||
|
||||
|
||||
#pragma mark - ORKBooleanAnswerFormat
|
||||
@@ -1664,6 +1696,35 @@ NSArray<Class> *ORKAllowableValueClasses(void) {
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setIsMaxDateCurrentTime:(BOOL)isMaxDateCurrentTime {
|
||||
_isMaxDateCurrentTime = isMaxDateCurrentTime;
|
||||
|
||||
if (isMaxDateCurrentTime) {
|
||||
_maximumDate = [NSDate date];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setDaysBeforeCurrentDateToSetMinimumDate:(NSInteger)daysBefore {
|
||||
_minimumDate = [self fetchDateBasedOnDays:daysBefore forBefore:YES];
|
||||
}
|
||||
|
||||
- (void)setDaysAfterCurrentDateToSetMinimumDate:(NSInteger)daysAfter {
|
||||
_maximumDate = [self fetchDateBasedOnDays:daysAfter forBefore:NO];
|
||||
}
|
||||
|
||||
- (NSDate *)fetchDateBasedOnDays:(NSInteger)days forBefore:(BOOL)forBefore {
|
||||
if (days < 0) {
|
||||
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"The value passed in for daysBeforeCurrentDateToSetMinimumDate must be greater than 0." userInfo:nil];
|
||||
}
|
||||
|
||||
NSDate *currentDate = [NSDate date];
|
||||
|
||||
NSDateComponents *dateComponents = [[NSDateComponents alloc] init];
|
||||
[dateComponents setDay:forBefore ? -days : days];
|
||||
|
||||
return [[NSCalendar currentCalendar] dateByAddingComponents:dateComponents toDate:currentDate options:0];
|
||||
}
|
||||
|
||||
- (void)validateParameters {
|
||||
[super validateParameters];
|
||||
|
||||
@@ -1690,6 +1751,7 @@ NSArray<Class> *ORKAllowableValueClasses(void) {
|
||||
ORKEqualObjects(self.defaultDate, castObject.defaultDate) &&
|
||||
ORKEqualObjects(self.minimumDate, castObject.minimumDate) &&
|
||||
ORKEqualObjects(self.maximumDate, castObject.maximumDate) &&
|
||||
self.isMaxDateCurrentTime == castObject.isMaxDateCurrentTime &&
|
||||
ORKEqualObjects(self.calendar, castObject.calendar) &&
|
||||
(self.minuteInterval == castObject.minuteInterval) &&
|
||||
(_style == castObject.style));
|
||||
@@ -1757,6 +1819,7 @@ NSArray<Class> *ORKAllowableValueClasses(void) {
|
||||
ORK_DECODE_OBJ_CLASS(aDecoder, minimumDate, NSDate);
|
||||
ORK_DECODE_OBJ_CLASS(aDecoder, maximumDate, NSDate);
|
||||
ORK_DECODE_OBJ_CLASS(aDecoder, defaultDate, NSDate);
|
||||
ORK_DECODE_BOOL(aDecoder, isMaxDateCurrentTime);
|
||||
ORK_DECODE_OBJ_CLASS(aDecoder, calendar, NSCalendar);
|
||||
ORK_DECODE_INTEGER(aDecoder, minuteInterval);
|
||||
}
|
||||
@@ -1769,6 +1832,7 @@ NSArray<Class> *ORKAllowableValueClasses(void) {
|
||||
ORK_ENCODE_OBJ(aCoder, minimumDate);
|
||||
ORK_ENCODE_OBJ(aCoder, maximumDate);
|
||||
ORK_ENCODE_OBJ(aCoder, defaultDate);
|
||||
ORK_ENCODE_BOOL(aCoder, isMaxDateCurrentTime);
|
||||
ORK_ENCODE_OBJ(aCoder, calendar);
|
||||
ORK_ENCODE_INTEGER(aCoder, minuteInterval);
|
||||
}
|
||||
@@ -1813,7 +1877,7 @@ NSArray<Class> *ORKAllowableValueClasses(void) {
|
||||
unit:(NSString *)unit
|
||||
minimum:(NSNumber *)minimum
|
||||
maximum:(NSNumber *)maximum {
|
||||
return [self initWithStyle:style unit:unit minimum:minimum maximum:maximum maximumFractionDigits:nil];
|
||||
return [self initWithStyle:style unit:unit displayUnit:unit minimum:minimum maximum:maximum maximumFractionDigits:nil];
|
||||
}
|
||||
|
||||
- (instancetype)initWithStyle:(ORKNumericAnswerStyle)style
|
||||
@@ -1821,10 +1885,20 @@ NSArray<Class> *ORKAllowableValueClasses(void) {
|
||||
minimum:(NSNumber *)minimum
|
||||
maximum:(NSNumber *)maximum
|
||||
maximumFractionDigits:(NSNumber *)maximumFractionDigits {
|
||||
return [self initWithStyle:style unit:unit displayUnit:unit minimum:minimum maximum:maximum maximumFractionDigits:maximumFractionDigits];
|
||||
}
|
||||
|
||||
- (instancetype)initWithStyle:(ORKNumericAnswerStyle)style
|
||||
unit:(NSString *)unit
|
||||
displayUnit:(NSString *)displayUnit
|
||||
minimum:(NSNumber *)minimum
|
||||
maximum:(NSNumber *)maximum
|
||||
maximumFractionDigits:(NSNumber *)maximumFractionDigits {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_style = style;
|
||||
_unit = [unit copy];
|
||||
_displayUnit = [displayUnit copy];
|
||||
_minimum = [minimum copy];
|
||||
_maximum = [maximum copy];
|
||||
_maximumFractionDigits = [maximumFractionDigits copy];
|
||||
@@ -1850,6 +1924,7 @@ NSArray<Class> *ORKAllowableValueClasses(void) {
|
||||
if (self) {
|
||||
ORK_DECODE_ENUM(aDecoder, style);
|
||||
ORK_DECODE_OBJ_CLASS(aDecoder, unit, NSString);
|
||||
ORK_DECODE_OBJ_CLASS(aDecoder, displayUnit, NSString);
|
||||
ORK_DECODE_OBJ_CLASS(aDecoder, minimum, NSNumber);
|
||||
ORK_DECODE_OBJ_CLASS(aDecoder, maximum, NSNumber);
|
||||
ORK_DECODE_OBJ_CLASS(aDecoder, defaultNumericAnswer, NSNumber);
|
||||
@@ -1864,6 +1939,7 @@ NSArray<Class> *ORKAllowableValueClasses(void) {
|
||||
[super encodeWithCoder:aCoder];
|
||||
ORK_ENCODE_ENUM(aCoder, style);
|
||||
ORK_ENCODE_OBJ(aCoder, unit);
|
||||
ORK_ENCODE_OBJ(aCoder, displayUnit);
|
||||
ORK_ENCODE_OBJ(aCoder, minimum);
|
||||
ORK_ENCODE_OBJ(aCoder, maximum);
|
||||
ORK_ENCODE_OBJ(aCoder, defaultNumericAnswer);
|
||||
@@ -1879,6 +1955,7 @@ NSArray<Class> *ORKAllowableValueClasses(void) {
|
||||
- (instancetype)copyWithZone:(NSZone *)zone {
|
||||
ORKNumericAnswerFormat *answerFormat = [[[self class] allocWithZone:zone] initWithStyle:_style
|
||||
unit:[_unit copy]
|
||||
displayUnit:[_displayUnit copy]
|
||||
minimum:[_minimum copy]
|
||||
maximum:[_maximum copy]
|
||||
maximumFractionDigits:[_maximumFractionDigits copy]];
|
||||
@@ -1898,6 +1975,7 @@ NSArray<Class> *ORKAllowableValueClasses(void) {
|
||||
return (isParentSame &&
|
||||
_style == castObject.style &&
|
||||
ORKEqualObjects(self.unit, castObject.unit) &&
|
||||
ORKEqualObjects(self.displayUnit, castObject.displayUnit) &&
|
||||
ORKEqualObjects(self.minimum, castObject.minimum) &&
|
||||
ORKEqualObjects(self.maximum, castObject.maximum) &&
|
||||
ORKEqualObjects(self.defaultNumericAnswer, castObject.defaultNumericAnswer) &&
|
||||
@@ -2397,8 +2475,8 @@ NSArray<Class> *ORKAllowableValueClasses(void) {
|
||||
- (void)validateParameters {
|
||||
[super validateParameters];
|
||||
|
||||
const double ORKScaleAnswerFormatValueLowerbound = -10000;
|
||||
const double ORKScaleAnswerFormatValueUpperbound = 10000;
|
||||
const double ORKScaleAnswerFormatValueLowerbound = -100000;
|
||||
const double ORKScaleAnswerFormatValueUpperbound = 100000;
|
||||
|
||||
// Just clamp maximumFractionDigits to be 0-4. This is all aimed at keeping the maximum
|
||||
// number of digits down to 6 or less.
|
||||
@@ -2887,7 +2965,6 @@ NSArray<Class> *ORKAllowableValueClasses(void) {
|
||||
answerFormat->_keyboardType = _keyboardType;
|
||||
answerFormat->_autocapitalizationType = _autocapitalizationType;
|
||||
answerFormat->_textContentType = _textContentType;
|
||||
|
||||
if (@available(iOS 12.0, *)) {
|
||||
answerFormat->_passwordRules = _passwordRules;
|
||||
}
|
||||
@@ -2995,9 +3072,17 @@ static NSString *const kSecureTextEntryEscapeString = @"*";
|
||||
if ([self isAnswerValid:answer]) {
|
||||
answerString = _secureTextEntry ? [@"" stringByPaddingToLength:((NSString *)answer).length withString:kSecureTextEntryEscapeString startingAtIndex:0] : answer;
|
||||
}
|
||||
|
||||
return answerString;
|
||||
}
|
||||
|
||||
|
||||
- (ORKQuestionResult *)resultWithIdentifier:(NSString *)identifier answer:(id)answer {
|
||||
ORKQuestionResult *questionResult = nil;
|
||||
questionResult = (ORKQuestionResult *)[super resultWithIdentifier:identifier answer:answer];
|
||||
return questionResult;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@@ -28,10 +28,7 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
#import <ResearchKit/ORKAnswerFormat.h>
|
||||
#endif
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@@ -111,7 +111,6 @@ static NSString *ORKBulletUnicode = @"\u2981";
|
||||
}
|
||||
|
||||
- (void)setupBodyStyleView {
|
||||
|
||||
if (_bodyItem.useCardStyle == YES) {
|
||||
_cardView = [[UIView alloc] init];
|
||||
_cardView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
@@ -575,7 +574,6 @@ static NSString *ORKBulletUnicode = @"\u2981";
|
||||
} else if (aboveStyle == ORKBodyItemStyleText) {
|
||||
return belowStyle == ORKBodyItemStyleText ? (_bodyItems[belowItemIndex].text ? ORKBodyToBodyParagraphPaddingStandard : ORKBodyToBodyPaddingStandard) : ORKBodyToBulletPaddingStandard;
|
||||
} else if (aboveStyle == ORKBodyItemStyleTag) {
|
||||
|
||||
if (belowStyle == ORKBodyItemStyleBulletPoint) {
|
||||
return ORKBulletToBulletPaddingStandard;
|
||||
} else {
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
#import "ORKHelpers_Internal.h"
|
||||
|
||||
@implementation ORKBodyItem
|
||||
|
||||
{
|
||||
// For our internal custom button type
|
||||
BOOL _isCustomButtonType;
|
||||
|
||||
@@ -122,7 +122,7 @@ static const CGFloat ORKBorderedButtonCornerRadii = 14.0;
|
||||
if (self.disabledButtonStyle == ORKBorderedButtonDisabledStyleDefault) {
|
||||
_disableTintColor = [normalTintColor colorWithAlphaComponent:0.3f];
|
||||
}
|
||||
|
||||
|
||||
[self updateBackgroundColor];
|
||||
}
|
||||
|
||||
|
||||
@@ -42,14 +42,15 @@ ORK_CLASS_AVAILABLE
|
||||
/// The file name of the resource, excluding the file extension.
|
||||
@property (nonatomic, copy) NSString *name;
|
||||
|
||||
/// The bundle identifier for the bundle that contains the asset.
|
||||
@property (nonatomic, copy) NSString *bundleIdentifier;
|
||||
/// The bundle identifier for the bundle that contains the asset. If this is not set,
|
||||
/// the main bundle identifier will be used.
|
||||
@property (nonatomic, copy, nullable) NSString *bundleIdentifier;
|
||||
|
||||
/// An optional file extension that may be used for disambiguation.
|
||||
@property (nonatomic, copy, nullable) NSString *fileExtension;
|
||||
|
||||
- (instancetype)initWithName:(NSString *) name
|
||||
bundleIdentifier:(NSString *) bundleIdentifier
|
||||
bundleIdentifier:(nullable NSString *) bundleIdentifier
|
||||
fileExtension:(nullable NSString *) fileExtension;
|
||||
|
||||
- (nullable NSURL*)url;
|
||||
|
||||
@@ -85,7 +85,10 @@
|
||||
}
|
||||
|
||||
- (nullable NSURL *)url {
|
||||
NSBundle *bundle = [NSBundle bundleWithIdentifier:self.bundleIdentifier];
|
||||
NSBundle *bundle = (self.bundleIdentifier) ?
|
||||
[NSBundle bundleWithIdentifier:self.bundleIdentifier] :
|
||||
[NSBundle mainBundle];
|
||||
|
||||
NSURL *url = [bundle URLForResource:self.name withExtension:self.fileExtension];
|
||||
return url;
|
||||
}
|
||||
|
||||
@@ -104,10 +104,10 @@ static const CGFloat CheckmarkViewBorderWidth = 2.0;
|
||||
|
||||
- (void)updateCheckView {
|
||||
if (_checked) {
|
||||
self.image = _checkedImage;
|
||||
self.image = _checkedImage;
|
||||
// FIXME: Need to be replaced.
|
||||
if (@available(iOS 13.0, *)) {
|
||||
self.tintColor = ORKWindowTintcolor(self.window) ? : [UIColor systemBlueColor];
|
||||
self.tintColor = ORKViewTintColor(self);
|
||||
} else {
|
||||
self.backgroundColor = [self tintColor];
|
||||
self.tintColor = UIColor.whiteColor;
|
||||
@@ -146,5 +146,10 @@ static const CGFloat CheckmarkViewBorderWidth = 2.0;
|
||||
[self updateCheckView];
|
||||
}
|
||||
|
||||
- (void)tintColorDidChange {
|
||||
[super tintColorDidChange];
|
||||
[self updateCheckView];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -139,7 +139,6 @@
|
||||
for (id answerValue in (NSArray *)answer) {
|
||||
id<ORKAnswerOption> matchedChoice = nil;
|
||||
for ( id<ORKAnswerOption> choice in _choices) {
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
if ([choice isKindOfClass:[ORKTextChoiceOther class]]) {
|
||||
ORKTextChoiceOther *textChoiceOther = (ORKTextChoiceOther *)choice;
|
||||
|
||||
@@ -202,7 +202,6 @@ static const CGFloat LabelCheckViewPadding = 10.0;
|
||||
|
||||
[_contentMaskLayer addSublayer:[self lineLayer]];
|
||||
|
||||
|
||||
[_containerView.layer insertSublayer:_contentMaskLayer atIndex:0];
|
||||
}
|
||||
}
|
||||
@@ -521,6 +520,7 @@ static const CGFloat LabelCheckViewPadding = 10.0;
|
||||
|
||||
- (void)updateCheckView {
|
||||
if (_checkView) {
|
||||
_checkView.tintColor = self.tintColor;
|
||||
[_checkView setChecked:_cellSelected];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,9 +28,7 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
#import <ResearchKit/ORKResult.h>
|
||||
#endif
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
|
||||
@@ -28,6 +28,10 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#if TARGET_OS_WATCH
|
||||
@import WatchKit;
|
||||
#endif
|
||||
|
||||
#import "ORKCollectionResult.h"
|
||||
|
||||
#import "ORKCollectionResult_Private.h"
|
||||
@@ -260,13 +264,11 @@
|
||||
}
|
||||
|
||||
- (void)updateEnabledAssistiveTechnology {
|
||||
#if TARGET_OS_IOS
|
||||
if (UIAccessibilityIsVoiceOverRunning()) {
|
||||
_enabledAssistiveTechnology = [UIAccessibilityNotificationVoiceOverIdentifier copy];
|
||||
} else if (UIAccessibilityIsSwitchControlRunning()) {
|
||||
_enabledAssistiveTechnology = [UIAccessibilityNotificationSwitchControlIdentifier copy];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
|
||||
@@ -29,10 +29,7 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
#import <ResearchKit/ORKCollectionResult.h>
|
||||
#endif
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@@ -86,10 +83,4 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@end
|
||||
|
||||
@interface ORKTaskResult ()
|
||||
|
||||
- (void)setTaskRunUUID:(NSUUID * _Nonnull)taskRunUUID;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
|
||||
- (void)setCheckmarkColor:(UIColor *)checkmarkColor {
|
||||
_checkmarkColor = [checkmarkColor copy];
|
||||
|
||||
|
||||
if (_completionCheckmarkView) {
|
||||
_completionCheckmarkView.tintColor = checkmarkColor;
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ static const CGFloat ContinueButtonHeight = 50.0;
|
||||
if (self) {
|
||||
[self setTitle:title forState:UIControlStateNormal];
|
||||
self.isDoneButton = isDoneButton;
|
||||
[self updateContentInsets:NSDirectionalEdgeInsetsMake(0, 6, 0, 6)];
|
||||
self.contentEdgeInsets = (UIEdgeInsets){.left = 6, .right = 6};
|
||||
|
||||
[self setUpConstraints];
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
|
||||
+ (instancetype)customStepWithIdentifier:(NSString *)identifier
|
||||
contentView:(UIView *)contentView {
|
||||
NSAssert(contentView != NULL, @"ORKCustomStep must be initialized with a contentView");
|
||||
ORKCustomStep *step = [[ORKCustomStep alloc] initWithIdentifier:identifier];
|
||||
step.contentView = contentView;
|
||||
return step;
|
||||
@@ -79,4 +80,8 @@
|
||||
return step;
|
||||
}
|
||||
|
||||
+ (Class)stepViewControllerClass {
|
||||
return [ORKCustomStepViewController class];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -127,11 +127,26 @@
|
||||
}
|
||||
|
||||
- (void)configureContainerView {
|
||||
[_containerView setStepTitle:self.customStep.title];
|
||||
[_containerView setStepText:self.customStep.text];
|
||||
[_containerView setStepDetailText:self.customStep.detailText];
|
||||
[_containerView setStepHeaderTextAlignment:self.customStep.headerTextAlignment];
|
||||
[_containerView setTitleIconImage:self.step.iconImage];
|
||||
|
||||
if (self.customStep.title){
|
||||
[_containerView setStepTitle:self.customStep.title];
|
||||
}
|
||||
|
||||
if (self.customStep.text) {
|
||||
[_containerView setStepText:self.customStep.text];
|
||||
}
|
||||
|
||||
if (self.customStep.detailText) {
|
||||
[_containerView setStepDetailText:self.customStep.detailText];
|
||||
}
|
||||
|
||||
if (self.customStep.headerTextAlignment) {
|
||||
[_containerView setStepHeaderTextAlignment:self.customStep.headerTextAlignment];
|
||||
}
|
||||
|
||||
if (self.step.iconImage){
|
||||
[_containerView setTitleIconImage:self.step.iconImage];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setStepHeaderTextAlignment:(NSTextAlignment)stepHeaderTextAlignment {
|
||||
|
||||
@@ -29,9 +29,7 @@
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#if TARGET_OS_IOS
|
||||
#import <ResearchKit/ORKDefines.h>
|
||||
#endif
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
|
||||
@@ -32,9 +32,7 @@
|
||||
#import "ORKDevice.h"
|
||||
#import "ORKHelpers_Internal.h"
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
#import <UIKit/UIDevice.h>
|
||||
#endif
|
||||
#import <sys/types.h>
|
||||
#import <sys/sysctl.h>
|
||||
#import <errno.h>
|
||||
@@ -126,10 +124,8 @@ static NSString * ORK_SYSCTL_DEBUG_STRING(int tl, int sl) {
|
||||
self->_product = [self _product];
|
||||
self->_osBuild = [self _osBuild];
|
||||
NSOperatingSystemVersion version = [[NSProcessInfo processInfo] operatingSystemVersion];
|
||||
#if TARGET_OS_IOS
|
||||
self->_platform = [[UIDevice currentDevice] systemName];
|
||||
self->_osVersion = [NSString stringWithFormat:@"%ld.%ld.%ld", (long)version.majorVersion, (long)version.minorVersion, (long)version.patchVersion];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (instancetype)initWithProduct:(NSString *)product
|
||||
|
||||
@@ -28,9 +28,7 @@
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
#import <ResearchKit/ORKDevice.h>
|
||||
#endif
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
|
||||
@@ -332,7 +332,7 @@ static const CGFloat CheckMarkImageTrailingPadding = 2.0;
|
||||
color = [UIColor grayColor];
|
||||
}
|
||||
} else {
|
||||
color = ORKWindowTintcolor(self.window) ? : [UIColor systemBlueColor];
|
||||
color = ORKViewTintColor(self);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
@@ -29,10 +29,7 @@
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
#import <ResearchKit/ORKDefines.h>
|
||||
#endif
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
|
||||
@@ -31,10 +31,7 @@
|
||||
|
||||
@import Foundation;
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
#import <ResearchKit/ORKDefines.h>
|
||||
#endif
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
|
||||
@@ -246,7 +246,6 @@ static const CGFloat InlineFormItemLabelToTextFieldPadding = 3.0;
|
||||
CGRect foreLayerBounds = CGRectMake(ORKCardDefaultBorderWidth, 0, self.containerView.bounds.size.width - 2 * ORKCardDefaultBorderWidth, self.containerView.bounds.size.height);
|
||||
foreLayer.path = [UIBezierPath bezierPathWithRect:foreLayerBounds].CGPath;
|
||||
_contentMaskLayer.path = [UIBezierPath bezierPathWithRect:self.containerView.bounds].CGPath;
|
||||
|
||||
CGRect lineBounds = CGRectMake(0.0, self.containerView.bounds.size.height - 1.0, self.containerView.bounds.size.width, 0.5);
|
||||
lineLayer.path = [UIBezierPath bezierPathWithRect:lineBounds].CGPath;
|
||||
lineLayer.zPosition = 0.0f;
|
||||
@@ -547,7 +546,6 @@ static const CGFloat InlineFormItemLabelToTextFieldPadding = 3.0;
|
||||
}
|
||||
|
||||
- (void)dontKnowButtonWasPressed {
|
||||
|
||||
if (![_dontKnowButton active]) {
|
||||
[_dontKnowButton setActive:YES];
|
||||
[_textFieldView.textField setText:nil];
|
||||
@@ -558,7 +556,6 @@ static const CGFloat InlineFormItemLabelToTextFieldPadding = 3.0;
|
||||
if (self.delegate) {
|
||||
[self.delegate formItemCellDidResignFirstResponder:self];
|
||||
}
|
||||
|
||||
} else {
|
||||
[self textFieldShouldClear:_textFieldView.textField];
|
||||
[_textFieldView.textField endEditing:YES];
|
||||
@@ -1044,7 +1041,7 @@ static const CGFloat InlineFormItemLabelToTextFieldPadding = 3.0;
|
||||
_defaultNumericAnswer = answerFormat.defaultNumericAnswer;
|
||||
|
||||
self.textField.manageUnitAndPlaceholder = YES;
|
||||
self.textField.unit = answerFormat.unit;
|
||||
self.textField.unit = answerFormat.displayUnit ?: answerFormat.unit;
|
||||
self.textField.placeholder = self.formItem.placeholder;
|
||||
|
||||
_numberFormatter = ORKDecimalNumberFormatter();
|
||||
@@ -1156,7 +1153,6 @@ static const CGFloat InlineFormItemLabelToTextFieldPadding = 3.0;
|
||||
_textView = [[ORKFormTextView alloc] init];
|
||||
_textView.keyboardDismissMode = UIScrollViewKeyboardDismissModeInteractive;
|
||||
_textView.delegate = self;
|
||||
_textView.contentInset = UIEdgeInsetsMake(-5.0, -4.0, -5.0, 0.0);
|
||||
_textView.textAlignment = NSTextAlignmentNatural;
|
||||
_textView.scrollEnabled = YES;
|
||||
_textView.placeholder = self.formItem.placeholder;
|
||||
@@ -1280,6 +1276,28 @@ static const CGFloat InlineFormItemLabelToTextFieldPadding = 3.0;
|
||||
views:views]];
|
||||
}
|
||||
|
||||
//TextView vertical constraints
|
||||
if (_maxLengthView || _shouldShowDontKnow) {
|
||||
[[_textView.topAnchor constraintEqualToAnchor:self.containerView.topAnchor constant:TextViewVerticalMargin] setActive:YES];
|
||||
[[_textView.heightAnchor constraintGreaterThanOrEqualToConstant:TextViewMinHeight] setActive:YES];
|
||||
|
||||
NSLayoutConstraint *constraint2 = [NSLayoutConstraint constraintWithItem:self.containerView
|
||||
attribute:NSLayoutAttributeBottom
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
toItem:bottomViewToConstrainTo
|
||||
attribute:NSLayoutAttributeBottom
|
||||
multiplier:1.0
|
||||
constant:DontKnowButtonTopBottomPadding];
|
||||
constraint2.priority = UILayoutPriorityRequired - 1;
|
||||
constraint2.active = YES;
|
||||
} else {
|
||||
[constraints addObjectsFromArray:
|
||||
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-vMargin-[textView]-vMargin-|"
|
||||
options:NSLayoutFormatDirectionLeadingToTrailing
|
||||
metrics:metrics
|
||||
views:views]];
|
||||
}
|
||||
|
||||
NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:self.contentView
|
||||
attribute:NSLayoutAttributeHeight
|
||||
relatedBy:NSLayoutRelationGreaterThanOrEqual
|
||||
@@ -1538,10 +1556,6 @@ static const CGFloat InlineFormItemLabelToTextFieldPadding = 3.0;
|
||||
}
|
||||
|
||||
- (void)textViewDidEndEditing:(UITextView *)textView {
|
||||
if (textView.text.length == 0) {
|
||||
textView.text = self.formItem.placeholder;
|
||||
textView.textColor = [self placeholderColor];
|
||||
}
|
||||
[self.delegate formItemCellDidResignFirstResponder:self];
|
||||
}
|
||||
|
||||
|
||||
@@ -31,10 +31,8 @@
|
||||
|
||||
@import Foundation;
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
#import <ResearchKit/ORKStep.h>
|
||||
#import <ResearchKit/ORKDefines.h>
|
||||
#endif
|
||||
|
||||
typedef NS_ENUM(NSInteger, ORKCardViewStyle) {
|
||||
ORKCardViewStyleDefault,
|
||||
|
||||
@@ -924,10 +924,10 @@ static const NSTimeInterval DelayBeforeAutoScroll = 0.25;
|
||||
systemTimeZone = _savedSystemTimeZones[item.identifier];
|
||||
NSAssert(answer == nil || answer == ORKNullAnswerValue() || systemTimeZone != nil, @"systemTimeZone NOT saved");
|
||||
}
|
||||
|
||||
|
||||
ORKQuestionResult *result = [item.answerFormat resultWithIdentifier:item.identifier answer:answer];
|
||||
ORKAnswerFormat *impliedAnswerFormat = [item impliedAnswerFormat];
|
||||
|
||||
|
||||
if ([impliedAnswerFormat isKindOfClass:[ORKDateAnswerFormat class]]) {
|
||||
ORKDateQuestionResult *dqr = (ORKDateQuestionResult *)result;
|
||||
if (dqr.dateAnswer) {
|
||||
@@ -939,6 +939,7 @@ static const NSTimeInterval DelayBeforeAutoScroll = 0.25;
|
||||
ORKNumericQuestionResult *nqr = (ORKNumericQuestionResult *)result;
|
||||
if (nqr.unit == nil) {
|
||||
nqr.unit = [(ORKNumericAnswerFormat *)impliedAnswerFormat unit];
|
||||
nqr.displayUnit = [(ORKNumericAnswerFormat *)impliedAnswerFormat displayUnit];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1157,6 +1158,7 @@ static const NSTimeInterval DelayBeforeAutoScroll = 0.25;
|
||||
ORKChoiceOtherViewCell *choiceOtherViewCell = (ORKChoiceOtherViewCell *)choiceViewCell;
|
||||
choiceOtherViewCell.delegate = self;
|
||||
}
|
||||
choiceViewCell.tintColor = ORKViewTintColor(self.view);
|
||||
choiceViewCell.useCardView = [self formStep].useCardView;
|
||||
choiceViewCell.cardViewStyle = [self formStep].cardViewStyle;
|
||||
choiceViewCell.isLastItem = isLastItem;
|
||||
@@ -1499,12 +1501,13 @@ static NSString *const _ORKAnsweredSectionsRestoreKey = @"answeredSections";
|
||||
- (void)decodeRestorableStateWithCoder:(NSCoder *)coder {
|
||||
[super decodeRestorableStateWithCoder:coder];
|
||||
|
||||
_savedAnswers = [coder decodeObjectOfClass:[NSMutableDictionary class] forKey:_ORKSavedAnswersRestoreKey];
|
||||
_savedAnswerDates = [coder decodeObjectOfClass:[NSMutableDictionary class] forKey:_ORKSavedAnswerDatesRestoreKey];
|
||||
_savedSystemCalendars = [coder decodeObjectOfClass:[NSMutableDictionary class] forKey:_ORKSavedSystemCalendarsRestoreKey];
|
||||
_savedSystemTimeZones = [coder decodeObjectOfClass:[NSMutableDictionary class] forKey:_ORKSavedSystemTimeZonesRestoreKey];
|
||||
_originalAnswers = [coder decodeObjectOfClass:[NSMutableDictionary class] forKey:_ORKOriginalAnswersRestoreKey];
|
||||
_answeredSections = [coder decodeObjectOfClass:[NSMutableSet class] forKey:_ORKAnsweredSectionsRestoreKey];
|
||||
NSSet *decodableAnswerTypes = [NSSet setWithObjects:NSMutableDictionary.self, NSString.self, NSNumber.self, NSDate.self, nil];
|
||||
_savedAnswers = [coder decodeObjectOfClasses:decodableAnswerTypes forKey:_ORKSavedAnswersRestoreKey];
|
||||
_savedAnswerDates = [coder decodeObjectOfClasses:[NSSet setWithArray:@[NSMutableDictionary.self, NSString.self, NSDate.self]] forKey:_ORKSavedAnswerDatesRestoreKey];
|
||||
_savedSystemCalendars = [coder decodeObjectOfClasses:[NSSet setWithArray:@[NSMutableDictionary.self, NSString.self, NSCalendar.self]] forKey:_ORKSavedSystemCalendarsRestoreKey];
|
||||
_savedSystemTimeZones = [coder decodeObjectOfClasses:[NSSet setWithArray:@[NSMutableDictionary.self, NSString.self, NSTimeZone.self]] forKey:_ORKSavedSystemTimeZonesRestoreKey];
|
||||
_originalAnswers = [coder decodeObjectOfClasses:decodableAnswerTypes forKey:_ORKOriginalAnswersRestoreKey];
|
||||
_answeredSections = [coder decodeObjectOfClasses:[NSSet setWithArray:@[NSMutableSet.self, NSNumber.self]] forKey:_ORKAnsweredSectionsRestoreKey];
|
||||
}
|
||||
|
||||
#pragma mark Rotate
|
||||
|
||||
@@ -95,6 +95,10 @@ static const CGFloat PickerMinimumHeight = 34.0;
|
||||
[self setAnswer:[self defaultAnswerValue]];
|
||||
return;
|
||||
}
|
||||
// need to add one if not optional
|
||||
if (![_pickerDelegate isOptional]) {
|
||||
index = index + 1;
|
||||
}
|
||||
[_pickerView selectRow:index inComponent:0 animated:NO];
|
||||
} else {
|
||||
double feet, inches;
|
||||
@@ -105,6 +109,12 @@ static const CGFloat PickerMinimumHeight = 34.0;
|
||||
[self setAnswer:[self defaultAnswerValue]];
|
||||
return;
|
||||
}
|
||||
// need to add one if not optional
|
||||
if (![_pickerDelegate isOptional]) {
|
||||
feetIndex = feetIndex + 1;
|
||||
inchesIndex = inchesIndex + 1;
|
||||
}
|
||||
|
||||
[_pickerView selectRow:feetIndex inComponent:0 animated:NO];
|
||||
[_pickerView selectRow:inchesIndex inComponent:1 animated:NO];
|
||||
}
|
||||
|
||||
@@ -200,6 +200,13 @@ UIColor *ORKWindowTintcolor(UIWindow *window) {
|
||||
return windowTintColor;
|
||||
}
|
||||
|
||||
UIColor *ORKViewTintColor(UIView *view) {
|
||||
UIColor *existingTintColor = view.tintColor ? : [UIColor systemBlueColor];
|
||||
UIColor *tintColor = ORKWindowTintcolor(view.window) ? : existingTintColor;
|
||||
|
||||
return tintColor;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
UIImage *ORKImageWithColor(UIColor *color) {
|
||||
|
||||
@@ -32,12 +32,9 @@
|
||||
|
||||
@import UIKit;
|
||||
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
#import <ResearchKit/ORKTypes.h>
|
||||
#import <ResearchKit/ORKHelpers_Private.h>
|
||||
#import <ResearchKit/ORKErrors.h>
|
||||
#endif
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <os/log.h>
|
||||
|
||||
@@ -86,10 +83,14 @@ ORK_EXTERN BOOL ORKLoggingEnabled;
|
||||
|
||||
#define ORK_DECODE_OBJ_CLASS(d,x,cl) _ ## x = (cl *)[d decodeObjectOfClass:[cl class] forKey:@ORK_STRINGIFY(x)]
|
||||
#define ORK_DECODE_OBJ_ARRAY(d,x,cl) _ ## x = (NSArray *)[d decodeObjectOfClasses:[NSSet setWithObjects:[NSArray class],[cl class], nil] forKey:@ORK_STRINGIFY(x)]
|
||||
#define ORK_DECODE_OBJ_DICTIONARY(d,x,kcl,cl) _ ## x = (NSDictionary *)[d decodeObjectOfClasses:[NSSet setWithObjects:[NSDictionary class],[kcl class],[cl class], nil] forKey:@ORK_STRINGIFY(x)]
|
||||
#define ORK_DECODE_OBJ_MUTABLE_ORDERED_SET(d,x,cl) _ ## x = [(NSOrderedSet *)[d decodeObjectOfClasses:[NSSet setWithObjects:[NSOrderedSet class],[cl class], nil] forKey:@ORK_STRINGIFY(x)] mutableCopy]
|
||||
#define ORK_DECODE_OBJ_MUTABLE_DICTIONARY(d,x,kcl,cl) _ ## x = [(NSDictionary *)[d decodeObjectOfClasses:[NSSet setWithObjects:[NSDictionary class],[kcl class],[cl class], nil] forKey:@ORK_STRINGIFY(x)] mutableCopy]
|
||||
#define ORK_DECODE_OBJ_ARRAY_PROPS(d,x) _ ## x = (NSArray *)[d decodeObjectOfClasses:[NSSet setWithObjects:[NSArray class],[NSDictionary class],[NSNumber class],[NSString class],[NSData class], nil] forKey:@ORK_STRINGIFY(x)]
|
||||
#define ORK_DECODE_OBJ_MUTABLE_DICTIONARY_PROPS(d,x,kcl) _ ## x = [(NSDictionary *)[d decodeObjectOfClasses:[NSSet setWithObjects:[kcl class],[NSDictionary class],[NSArray class],[NSNumber class],[NSString class],[NSData class],nil] forKey:@ORK_STRINGIFY(x)] mutableCopy]
|
||||
|
||||
#define ORK_DECODE_OBJ_CLASSES(d,x,clsArray) _ ## x = [d decodeObjectOfClasses:[NSSet setWithArray:clsArray] forKey:@ORK_STRINGIFY(x)]
|
||||
#define ORK_DECODE_OBJ_PLIST(d,x) _ ## x = [d decodePropertyListForKey:@ORK_STRINGIFY(x)]
|
||||
#define ORK_DECODE_OBJ_CLASSES_FOR_KEY(d,x,clsArray,k) _ ## x = [d decodeObjectOfClasses:[NSSet setWithArray:clsArray] forKey:@ORK_STRINGIFY(k)]
|
||||
|
||||
#define ORK_ENCODE_COND_OBJ(c,x) [c encodeConditionalObject:_ ## x forKey:@ORK_STRINGIFY(x)]
|
||||
@@ -178,6 +179,8 @@ CGFloat ORKExpectedLabelHeight(UILabel *label);
|
||||
|
||||
UIColor * _Nullable ORKWindowTintcolor(UIWindow *window);
|
||||
|
||||
UIColor * ORKViewTintColor(UIView *view);
|
||||
|
||||
#endif
|
||||
|
||||
// build a image with color
|
||||
|
||||
@@ -30,9 +30,8 @@
|
||||
|
||||
|
||||
@import Foundation;
|
||||
#if TARGET_OS_IOS
|
||||
|
||||
#import <ResearchKit/ORKDefines.h>
|
||||
#endif
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@@ -47,7 +47,3 @@ ORK_CLASS_AVAILABLE
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -31,9 +31,7 @@
|
||||
|
||||
@import UIKit;
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
#import <ResearchKit/ORKStep.h>
|
||||
#endif
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
|
||||
@@ -197,7 +197,7 @@
|
||||
/*
|
||||
In some cases we want to allow the parent application to intercept the learn more callback in learn more instruction steps. These
|
||||
should get handled the same way as other learn more callbacks at the task level. If the app responds to this delegate, it get's
|
||||
higher prioriy and it becomes the responsibility of the developer to handle all cases.
|
||||
higher priority and it becomes the responsibility of the developer to handle all cases.
|
||||
|
||||
If not implemented, default to showing the learnMore view controller for the the step.
|
||||
*/
|
||||
|
||||
@@ -66,7 +66,7 @@ ORK_CLASS_AVAILABLE
|
||||
|
||||
@return An object or `nil` if key is not valid.
|
||||
*/
|
||||
+ (nullable id<NSSecureCoding>)objectsOfClasses:objectClass forKey:(NSString *)key error:(NSError * __autoreleasing _Nullable *)errorOut;
|
||||
+ (nullable id<NSSecureCoding>)objectOfClass:(Class)objectClass forKey:(NSString *)key error:(NSError * __autoreleasing _Nullable *)errorOut;
|
||||
|
||||
/**
|
||||
Removes the object in the keychain for the provided key.
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#import "ORKKeychainWrapper.h"
|
||||
|
||||
#import "ORKHelpers_Internal.h"
|
||||
#import "ResearchKit/ResearchKit-Swift.h"
|
||||
|
||||
|
||||
static NSString *ORKKeychainWrapperDefaultService() {
|
||||
@@ -60,14 +61,14 @@ static NSString *ORKKeychainWrapperDefaultService() {
|
||||
error:errorOut];
|
||||
}
|
||||
|
||||
+ (id<NSSecureCoding>)objectsOfClasses:objectClasses forKey:(NSString *)key
|
||||
+ (id<NSSecureCoding>)objectOfClass:(Class)objectClass forKey:(NSString *)key
|
||||
error:(NSError **)errorOut {
|
||||
NSData *data = [self dataForKey:key
|
||||
service:ORKKeychainWrapperDefaultService()
|
||||
accessGroup:nil
|
||||
error:errorOut];
|
||||
|
||||
return data ? [NSKeyedUnarchiver unarchivedObjectOfClasses:objectClasses fromData:data error:errorOut] : nil;
|
||||
return data ? [NSKeyedUnarchiver unarchivedObjectOfClass:objectClass fromData:data error:errorOut] : nil;
|
||||
}
|
||||
|
||||
+ (BOOL)removeObjectForKey:(NSString *)key
|
||||
|
||||
@@ -162,7 +162,7 @@ static const CGFloat detailTextBottomSpacing = 16.0;
|
||||
}
|
||||
|
||||
- (void)didMoveToWindow {
|
||||
_appTintColor = ORKWindowTintcolor(self.window) ? : ORKColor(ORKBlueHighlightColorKey);
|
||||
_appTintColor = ORKViewTintColor(self);
|
||||
|
||||
_continueButton.normalTintColor = _appTintColor;
|
||||
_skipButton.normalTintColor = _appTintColor;
|
||||
@@ -389,7 +389,6 @@ static const CGFloat detailTextBottomSpacing = 16.0;
|
||||
attribute:NSLayoutAttributeTop
|
||||
multiplier:1.0
|
||||
constant:topSpacing],
|
||||
|
||||
[NSLayoutConstraint constraintWithItem:_detailTextLabel
|
||||
attribute:NSLayoutAttributeLeft
|
||||
relatedBy:NSLayoutRelationEqual
|
||||
@@ -559,4 +558,9 @@ static const CGFloat detailTextBottomSpacing = 16.0;
|
||||
[self setUpConstraints];
|
||||
}
|
||||
|
||||
- (void)tintColorDidChange {
|
||||
[super tintColorDidChange];
|
||||
[self didMoveToWindow];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -496,7 +496,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@return An active dBHL tone audiometry task that can be presented with an `ORKTaskViewController` object.
|
||||
*/
|
||||
+ (ORKOrderedTask *)dBHLToneAudiometryTaskWithIdentifier:(NSString *)identifier
|
||||
+ (ORKNavigableOrderedTask *)dBHLToneAudiometryTaskWithIdentifier:(NSString *)identifier
|
||||
intendedUseDescription:(nullable NSString *)intendedUseDescription
|
||||
options:(ORKPredefinedTaskOption)options;
|
||||
|
||||
@@ -554,7 +554,57 @@ 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.
|
||||
*/
|
||||
+ (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;
|
||||
|
||||
/**
|
||||
Returns a predefined task that consists of a Tower of Hanoi puzzle.
|
||||
|
||||
|
||||
@@ -940,7 +940,6 @@ NSString *const ORKSixMinuteWalkFatigueIdentifier = @"6mwt.fatigue";
|
||||
return task;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - shortWalkTask
|
||||
|
||||
NSString *const ORKShortWalkOutboundStepIdentifier = @"walking.outbound";
|
||||
@@ -1825,11 +1824,8 @@ NSString *const ORKToneAudiometryStepIdentifier = @"tone.audiometry";
|
||||
|
||||
#pragma mark - dBHLToneAudiometryTask
|
||||
|
||||
NSString *const ORKdBHLToneAudiometryStepIdentifier = @"dBHL.tone.audiometry";
|
||||
NSString *const ORKdBHLToneAudiometryStep0Identifier = @"dBHL0.tone.audiometry";
|
||||
NSString *const ORKdBHLToneAudiometryStep1Identifier = @"dBHL1.tone.audiometry";
|
||||
NSString *const ORKdBHLToneAudiometryStep2Identifier = @"dBHL2.tone.audiometry";
|
||||
NSString *const ORKdBHLToneAudiometryStep3Identifier = @"dBHL3.tone.audiometry";
|
||||
|
||||
|
||||
+ (ORKNavigableOrderedTask *)dBHLToneAudiometryTaskWithIdentifier:(NSString *)identifier
|
||||
@@ -1892,7 +1888,6 @@ NSString *const ORKdBHLToneAudiometryStep3Identifier = @"dBHL3.tone.audiometry";
|
||||
step.thresholdValue = 45;
|
||||
step.title = ORKLocalizedString(@"ENVIRONMENTSPL_TITLE_2", nil);
|
||||
step.text = ORKLocalizedString(@"ENVIRONMENTSPL_INTRO_TEXT_2", nil);
|
||||
|
||||
ORKStepArrayAddStep(steps, step);
|
||||
}
|
||||
|
||||
@@ -1906,8 +1901,8 @@ NSString *const ORKdBHLToneAudiometryStep3Identifier = @"dBHL3.tone.audiometry";
|
||||
ORKdBHLToneAudiometryStep *step = [[ORKdBHLToneAudiometryStep alloc] initWithIdentifier:ORKdBHLToneAudiometryStep1Identifier];
|
||||
step.title = ORKLocalizedString(@"dBHL_TONE_AUDIOMETRY_TASK_TITLE_2", nil);
|
||||
step.stepDuration = CGFLOAT_MAX;
|
||||
step.earPreference = ORKAudioChannelLeft;
|
||||
step.headphoneType = ORKHeadphoneTypeIdentifierAirPodsGen1;
|
||||
step.earPreference = ORKAudioChannelLeft;
|
||||
ORKStepArrayAddStep(steps, step);
|
||||
}
|
||||
|
||||
@@ -1915,7 +1910,6 @@ NSString *const ORKdBHLToneAudiometryStep3Identifier = @"dBHL3.tone.audiometry";
|
||||
ORKInstructionStep *step = [[ORKInstructionStep alloc] initWithIdentifier:ORKInstruction3StepIdentifier];
|
||||
step.title = ORKLocalizedString(@"dBHL_TONE_AUDIOMETRY_STEP_TITLE_RIGHT_EAR", nil);
|
||||
step.shouldTintImages = YES;
|
||||
|
||||
ORKStepArrayAddStep(steps, step);
|
||||
}
|
||||
|
||||
@@ -1923,8 +1917,8 @@ NSString *const ORKdBHLToneAudiometryStep3Identifier = @"dBHL3.tone.audiometry";
|
||||
ORKdBHLToneAudiometryStep *step = [[ORKdBHLToneAudiometryStep alloc] initWithIdentifier:ORKdBHLToneAudiometryStep2Identifier];
|
||||
step.title = ORKLocalizedString(@"dBHL_TONE_AUDIOMETRY_TASK_TITLE_2", nil);
|
||||
step.stepDuration = CGFLOAT_MAX;
|
||||
step.headphoneType = ORKHeadphoneTypeIdentifierAirPodsGen1;
|
||||
step.earPreference = ORKAudioChannelRight;
|
||||
step.headphoneType = ORKHeadphoneTypeIdentifierAirPodsGen1;
|
||||
ORKStepArrayAddStep(steps, step);
|
||||
}
|
||||
|
||||
@@ -1939,7 +1933,6 @@ NSString *const ORKdBHLToneAudiometryStep3Identifier = @"dBHL3.tone.audiometry";
|
||||
}
|
||||
|
||||
|
||||
|
||||
#pragma mark - towerOfHanoiTask
|
||||
|
||||
NSString *const ORKTowerOfHanoiStepIdentifier = @"towerOfHanoi";
|
||||
@@ -2062,6 +2055,74 @@ NSString *const ORKReactionTimeStepIdentifier = @"reactionTime";
|
||||
return task;
|
||||
}
|
||||
|
||||
#pragma mark - normalizedReactionTimeTask
|
||||
|
||||
NSString *const ORKNormalizedReactionTimeStepIdentifier = @"normalizedReactionTime";
|
||||
|
||||
+ (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 {
|
||||
|
||||
NSMutableArray *steps = [NSMutableArray array];
|
||||
|
||||
if (!(options & ORKPredefinedTaskOptionExcludeInstructions)) {
|
||||
{
|
||||
ORKInstructionStep *step = [[ORKInstructionStep alloc] initWithIdentifier:ORKInstruction0StepIdentifier];
|
||||
step.title = ORKLocalizedString(@"REACTION_TIME_TASK_TITLE", nil);
|
||||
step.text = intendedUseDescription;
|
||||
step.detailText = ORKLocalizedString(@"REACTION_TIME_TASK_INTENDED_USE", nil);
|
||||
step.image = [UIImage imageNamed:@"phoneshake" inBundle:[NSBundle bundleForClass:[self class]] compatibleWithTraitCollection:nil];
|
||||
step.imageContentMode = UIViewContentModeCenter;
|
||||
step.shouldTintImages = YES;
|
||||
|
||||
ORKStepArrayAddStep(steps, step);
|
||||
}
|
||||
|
||||
{
|
||||
ORKInstructionStep *step = [[ORKInstructionStep alloc] initWithIdentifier:ORKInstruction1StepIdentifier];
|
||||
step.title = ORKLocalizedString(@"REACTION_TIME_TASK_TITLE", nil);
|
||||
step.text = [NSString localizedStringWithFormat: ORKLocalizedString(@"REACTION_TIME_TASK_INTRO_TEXT_FORMAT", nil), numberOfAttempts];
|
||||
step.detailText = ORKLocalizedString(@"REACTION_TIME_TASK_CALL_TO_ACTION", nil);
|
||||
step.image = [UIImage imageNamed:@"phoneshakecircle" inBundle:[NSBundle bundleForClass:[self class]] compatibleWithTraitCollection:nil];
|
||||
step.imageContentMode = UIViewContentModeCenter;
|
||||
step.shouldTintImages = YES;
|
||||
|
||||
ORKStepArrayAddStep(steps, step);
|
||||
}
|
||||
}
|
||||
|
||||
ORKNormalizedReactionTimeStep *step = [[ORKNormalizedReactionTimeStep alloc] initWithIdentifier:ORKReactionTimeStepIdentifier];
|
||||
step.title = ORKLocalizedString(@"REACTION_TIME_TASK_TITLE", nil);
|
||||
step.maximumStimulusInterval = maximumStimulusInterval;
|
||||
step.minimumStimulusInterval = minimumStimulusInterval;
|
||||
step.thresholdAcceleration = thresholdAcceleration;
|
||||
step.numberOfAttempts = numberOfAttempts;
|
||||
step.timeout = timeout;
|
||||
step.successSound = successSoundID;
|
||||
step.timeoutSound = timeoutSoundID;
|
||||
step.failureSound = failureSoundID;
|
||||
step.recorderConfigurations = @[ [[ORKDeviceMotionRecorderConfiguration alloc] initWithIdentifier:ORKDeviceMotionRecorderIdentifier frequency: 100]];
|
||||
|
||||
ORKStepArrayAddStep(steps, step);
|
||||
|
||||
if (!(options & ORKPredefinedTaskOptionExcludeConclusion)) {
|
||||
ORKInstructionStep *completionStep = [self makeCompletionStep];
|
||||
ORKStepArrayAddStep(steps, completionStep);
|
||||
}
|
||||
|
||||
ORKOrderedTask *task = [[ORKOrderedTask alloc] initWithIdentifier:identifier steps:steps];
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
#pragma mark - timedWalkTask
|
||||
|
||||
NSString *const ORKTimedWalkFormStepIdentifier = @"timed.walk.form";
|
||||
|
||||
@@ -31,9 +31,7 @@
|
||||
|
||||
@import UIKit;
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
#import <ResearchKit/ORKTask.h>
|
||||
#endif
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@@ -50,6 +50,7 @@ FOUNDATION_EXPORT NSString *const ORKShortWalkOutboundStepIdentifier;
|
||||
FOUNDATION_EXPORT NSString *const ORKShortWalkReturnStepIdentifier;
|
||||
FOUNDATION_EXPORT NSString *const ORKShortWalkRestStepIdentifier;
|
||||
FOUNDATION_EXPORT NSString *const ORKSpatialSpanMemoryStepIdentifier;
|
||||
FOUNDATION_EXPORT NSString *const ORKSpeechRecognitionStepIdentifier;
|
||||
FOUNDATION_EXPORT NSString *const ORKStroopStepIdentifier;
|
||||
FOUNDATION_EXPORT NSString *const ORKToneAudiometryPracticeStepIdentifier;
|
||||
FOUNDATION_EXPORT NSString *const ORKToneAudiometryStepIdentifier;
|
||||
|
||||
@@ -71,6 +71,8 @@
|
||||
[self.view addSubview:_pdfView];
|
||||
[self setNavigationFooterView];
|
||||
[self setupConstraints];
|
||||
|
||||
[self.taskViewController setNavigationBarColor:[self.view backgroundColor]];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user