Compare commits

...

9 Commits

Author SHA1 Message Date
Pariece McKinney b8f155974c Public release/2.2.12 (#1555)
- Fixed `ORKHealthKitQuantityTypeAnswerFormat`issues
- Improved `ORKReviewStep` initialization
- Introduced a new property called `shouldAutomaticallyAdjustImageTintColor` on `ORKStep` to automatically adopt dark mode version of an image
- Added `ORKSESQuestionResult` to Table View Providers
- ORKCatalog Improvements
- ORKImageSelectionView improvements
- Bumped version to 2.2.12
2023-11-01 18:18:20 -04:00
Pariece McKinney 2fb22a6256 Public release/2.2.10 (#1548)
Fix ORKNavigationContainerView so that it does not call didMoveToWindow from tintColorDidChange
Bumped version to 2.2.10
2023-05-10 17:40:25 -04:00
ronzilla-apple bd60d945bb Public release/2.2.9 (#1541)
* Address issue where there was no option to "Save Results" if task is cancelled (Issue #1536/1540)
* Address an issue where ORKTaskViewController setting tintColor on stepViewController views broke ORKStepViewController's addResult:

* Fixed order issue of tone audiometry steps
* Include missing ORKSpeechRecognition header export (Issue #1534)
* Switch to contentEdgeInsets API in ORKContinueButton to fix an issue where button title wouldn't always fit onscreen (on iOS 16 during long press) (Issue #1533)
* Remove min/max values for ORKCatalog "Numeric with Display Unit"

* xcconfig file updates for version and driving ORKCatalog bundleID
* Addressing compiler warnings
* Added unit tests for init'ing taskViewController with restoration data
* Adding task restoration feature to ORKCatalog's taskViewController presentation
2023-02-13 11:03:48 -08:00
Pariece McKinney 8f58410af9 Release 2.2.8
-Bug fixes and enhancements
-New helper method (ORKViewTintColor) for setting tintColor
-New “displayUnit” property added to ORKAnswerFormat
-Updates in ORKNormalizedReactionTimeResult’s secure coding (breaks compatibility with previous archives).
2022-12-20 11:14:59 -08:00
Louie fc67cc944c Merge pull request #1519 from cbaker6/fixTintColor
Propagate tintColor if window not available
2022-11-29 20:22:37 -08:00
Corey Baker 64a068596b make suggested changes 2022-11-28 15:25:59 -05:00
Corey Baker c10693930d Fix tintColor for ORKWebViewStepViewController buttons 2022-11-15 20:48:25 -05:00
Corey Baker c0f309edf3 only check for tintColor once in viewControllerForStep 2022-11-15 09:16:44 -05:00
Corey Baker b1cc631748 propagate tintColor if window not available 2022-11-14 14:46:02 -05:00
355 changed files with 5064 additions and 6884 deletions
+14
View File
@@ -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]
-2
View File
@@ -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
-9
View File
@@ -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
+1 -8
View File
@@ -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
@@ -45,6 +45,9 @@
#import "ORKTaskViewController_Internal.h"
#import "ORKRecorder_Internal.h"
#import "ORKStepView_Private.h"
#import "ORKStepContentView.h"
#import "ORKActiveStep_Internal.h"
#import "ORKCollectionResult_Private.h"
#import "ORKResult.h"
@@ -125,6 +128,7 @@
if (_customView) {
_activeStepView.customContentView = _customView;
}
_activeStepView.stepContentView.shouldAutomaticallyAdjustImageTintColor = YES;
[self.view addSubview:_activeStepView];
}
@@ -295,8 +299,9 @@
outputDirectory:self.outputDirectory];
recorder.configuration = provider;
recorder.delegate = self;
[recorders addObject:recorder];
if (recorder) {
[recorders addObject:recorder];
}
}
self.recorders = recorders;
@@ -566,7 +571,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;
}
+1 -1
View File
@@ -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;
}
+1 -1
View File
@@ -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
+14 -1
View File
@@ -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
@@ -51,7 +51,6 @@
- (void)validateParameters {
[super validateParameters];
// TODO:
}
- (BOOL)startsFinished {
@@ -28,7 +28,6 @@
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
@import UIKit;
NS_ASSUME_NONNULL_BEGIN
@@ -49,7 +49,6 @@
- (void)validateParameters {
[super validateParameters];
// TODO:
}
- (BOOL)startsFinished {
@@ -49,7 +49,6 @@
- (void)validateParameters {
[super validateParameters];
// TODO:
}
- (BOOL)startsFinished {
@@ -50,7 +50,6 @@
- (void)validateParameters {
[super validateParameters];
// TODO:
}
- (BOOL)startsFinished {
@@ -51,7 +51,6 @@
- (void)validateParameters {
[super validateParameters];
// TODO:
}
- (BOOL)startsFinished {
@@ -51,7 +51,6 @@
- (void)validateParameters {
[super validateParameters];
// TODO:
}
- (BOOL)startsFinished {
@@ -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
@@ -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];
@@ -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];
}
@@ -30,6 +30,7 @@
#import <ResearchKit/ORKResult.h>
#import <ResearchKit/ORKTypes.h>
NS_ASSUME_NONNULL_BEGIN
@@ -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;
}
@@ -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
@@ -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,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"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 606 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 822 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

+73 -13
View File
@@ -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.
+91 -6
View File
@@ -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 {
-1
View File
@@ -35,7 +35,6 @@
#import "ORKHelpers_Internal.h"
@implementation ORKBodyItem
{
// For our internal custom button type
BOOL _isCustomButtonType;
+1 -1
View File
@@ -122,7 +122,7 @@ static const CGFloat ORKBorderedButtonCornerRadii = 14.0;
if (self.disabledButtonStyle == ORKBorderedButtonDisabledStyleDefault) {
_disableTintColor = [normalTintColor colorWithAlphaComponent:0.3f];
}
[self updateBackgroundColor];
}
+4 -3
View File
@@ -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;
+4 -1
View File
@@ -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;
}
+7 -2
View File
@@ -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;
+1 -1
View File
@@ -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];
}
}
-2
View File
@@ -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
+4 -2
View File
@@ -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;
}
+1 -1
View File
@@ -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];
}
+5
View File
@@ -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 {
-2
View File
@@ -29,9 +29,7 @@
*/
#import <Foundation/Foundation.h>
#if TARGET_OS_IOS
#import <ResearchKit/ORKDefines.h>
#endif
NS_ASSUME_NONNULL_BEGIN
-4
View File
@@ -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
-2
View File
@@ -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
+1 -1
View File
@@ -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
-3
View File
@@ -31,10 +31,7 @@
@import Foundation;
#if TARGET_OS_IOS
#import <ResearchKit/ORKDefines.h>
#endif
NS_ASSUME_NONNULL_BEGIN
+23 -9
View File
@@ -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];
}
-2
View File
@@ -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,
+11 -8
View File
@@ -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
+12 -1
View File
@@ -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];
}
@@ -163,7 +173,8 @@ static const CGFloat PickerMinimumHeight = 34.0;
- (void)pickerWillAppear {
// Report current value, since ORKHeightPicker always has a value
[self pickerView];
[self valueDidChange:self];
// only do the assignment that's done in `valueDidChange`
_answer = [self selectedAnswerValue];
[self accessibilityFocusOnPickerElement];
}
+7
View File
@@ -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) {
+6 -3
View File
@@ -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
+1 -2
View File
@@ -30,9 +30,8 @@
@import Foundation;
#if TARGET_OS_IOS
#import <ResearchKit/ORKDefines.h>
#endif
NS_ASSUME_NONNULL_BEGIN
-4
View File
@@ -47,7 +47,3 @@ ORK_CLASS_AVAILABLE
@end
NS_ASSUME_NONNULL_END
+63 -10
View File
@@ -37,7 +37,8 @@
#import "ORKHelpers_Internal.h"
#import "ORKSkin.h"
#import "UIImageView+ResearchKit.h"
#import "UIImage+ResearchKit.h"
@interface ORKChoiceButtonView : UIView
@@ -45,6 +46,7 @@
@property (nonatomic, strong) UIButton *button;
@property (nonatomic, copy) NSString *labelText;
@property (nonatomic, copy) ORKImageChoice *choice;
@end
@@ -54,17 +56,12 @@
- (instancetype)initWithImageChoice:(ORKImageChoice *)choice {
self = [super init];
if (self) {
_choice = [choice copy];
_labelText = choice.text.length > 0 ? choice.text: @" ";
self.button = [UIButton buttonWithType:UIButtonTypeCustom];
_button.exclusiveTouch = YES;
if (choice.selectedStateImage) {
[_button setImage:choice.selectedStateImage forState:UIControlStateSelected];
}
[_button setImage:choice.normalStateImage forState:UIControlStateNormal];
[self setupButtonImagesFromImageChoice:choice];
_button.imageView.contentMode = UIViewContentModeScaleAspectFit;
[self addSubview:_button];
@@ -78,10 +75,46 @@
} else {
self.button.accessibilityLabel = self.labelText;
}
[self updateViewColors];
}
return self;
}
- (void)setupButtonImagesFromImageChoice:(ORKImageChoice *)choice {
if ([UITraitCollection currentTraitCollection].userInterfaceStyle == UIUserInterfaceStyleDark) {
[_button setImage:[_button.imageView.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] forState:UIControlStateNormal];
} else {
[_button setImage:[_button.imageView.image imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal] forState:UIControlStateNormal];
}
if (choice.selectedStateImage) {
UIImage *selectedStateImage = choice.selectedStateImage;
if (@available(iOS 12.0, *)) {
selectedStateImage = [choice.selectedStateImage ork_imageWithRenderingModeForUserInterfaceStyle:self.traitCollection.userInterfaceStyle];
}
[_button setImage:selectedStateImage forState:UIControlStateSelected];
}
UIImage *normalStateImage = choice.normalStateImage;
if (@available(iOS 12.0, *)) {
normalStateImage = [choice.normalStateImage ork_imageWithRenderingModeForUserInterfaceStyle:self.traitCollection.userInterfaceStyle];
}
[_button setImage:normalStateImage forState:UIControlStateNormal];
}
- (void)updateViewColors {
if (@available(iOS 12.0, *)) {
[_button.imageView updateRenderingModeForUserInterfaceStyle:self.traitCollection.userInterfaceStyle];
_button.imageView.tintColor = self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark ? [UIColor whiteColor] : nil;
}
}
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
[super traitCollectionDidChange:previousTraitCollection];
[self setupButtonImagesFromImageChoice:_choice];
}
- (void)setUpConstraints {
NSMutableArray *constraints = [NSMutableArray new];
@@ -188,6 +221,7 @@ static const CGFloat SpacerHeight = 5.0;
ORKChoiceButtonView *buttonView = [[ORKChoiceButtonView alloc] initWithImageChoice:imageChoice];
[buttonView.button addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];
buttonView.button.imageView.layer.cornerRadius = ORKImageChoiceButtonCornerRadii;
[buttonViews addObject:buttonView];
[self addSubview:buttonView];
}
@@ -370,7 +404,16 @@ static const CGFloat SpacerHeight = 5.0;
- (void)resetLabelText {
_placeHolderLabel.hidden = NO;
_choiceLabel.hidden = !_placeHolderLabel.hidden;
}
- (void)resetButtonSelection:(UIButton *)button {
[_buttonViews enumerateObjectsUsingBlock:^(ORKChoiceButtonView *buttonView, NSUInteger idx, BOOL *stop) {
if (_singleChoice) {
buttonView.button.imageView.backgroundColor = nil;
} else if ([buttonView.button isEqual: button]) {
buttonView.button.imageView.backgroundColor = nil;
}
}];
}
- (void)setLabelText:(NSString *)text {
@@ -398,6 +441,7 @@ static const CGFloat SpacerHeight = 5.0;
if (buttonView.button != button) {
if (_singleChoice) {
buttonView.button.selected = NO;
buttonView.button.imageView.backgroundColor = nil;
}
} else {
if (_singleChoice) {
@@ -405,6 +449,7 @@ static const CGFloat SpacerHeight = 5.0;
} else {
[self setLabelText:[_helper labelForChoiceAnswer:[_helper answerForSelectedIndexes:[self selectedIndexes]]]];
}
buttonView.button.imageView.backgroundColor = [UIColor lightGrayColor];
}
}];
@@ -415,6 +460,7 @@ static const CGFloat SpacerHeight = 5.0;
} else {
[self setLabelText:[_helper labelForChoiceAnswer:[_helper answerForSelectedIndexes:[self selectedIndexes]]]];
}
[self resetButtonSelection:button];
}
_answer = [_helper answerForSelectedIndexes:[self selectedIndexes]];
@@ -447,9 +493,16 @@ static const CGFloat SpacerHeight = 5.0;
if (number.unsignedIntegerValue < _buttonViews.count) {
ORKChoiceButtonView *buttonView = _buttonViews[number.unsignedIntegerValue];
[buttonView button].selected = YES;
[self setLabelText:buttonView.labelText];
buttonView.button.imageView.backgroundColor = [UIColor lightGrayColor];
if (_singleChoice) {
[self setLabelText:buttonView.labelText];
}
}
}];
if (!_singleChoice) {
[self setLabelText:[_helper labelForChoiceAnswer:[_helper answerForSelectedIndexes:[self selectedIndexes]]]];
}
}
- (BOOL)isAccessibilityElement {
-2
View File
@@ -31,9 +31,7 @@
@import UIKit;
#if TARGET_OS_IOS
#import <ResearchKit/ORKStep.h>
#endif
NS_ASSUME_NONNULL_BEGIN
@@ -71,6 +71,7 @@
if (self.step && [self isViewLoaded]) {
self.stepView = [[ORKInstructionStepContainerView alloc] initWithInstructionStep:[self instructionStep]];
_stepView.delegate = self;
_stepView.stepContentView.shouldAutomaticallyAdjustImageTintColor = [self instructionStep].shouldAutomaticallyAdjustImageTintColor;
_stepView.stepContentView.bodyContainerView.bodyItemDelegate = self;
[self.view addSubview:self.stepView];
[self setNavigationFooterView];
@@ -197,7 +198,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.
*/
+1 -1
View File
@@ -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.

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