Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance Touch Anywhere and Range of Motion steps for multiple taps and touches #1482

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 5 additions & 1 deletion ResearchKit/ActiveTasks/ORKRangeOfMotionStep.h
Expand Up @@ -45,7 +45,11 @@ ORK_CLASS_AVAILABLE

@property (nonatomic, assign) ORKPredefinedTaskLimbOption limbOption; //The left and/or right limb to be tested during the task

- (instancetype)initWithIdentifier:(NSString *)identifier limbOption:(ORKPredefinedTaskLimbOption)limbOption;
@property (nonatomic, assign) NSInteger numberOfTaps; // The number of taps required to begin and end the range of motion step (default is 1)

@property (nonatomic, assign) NSInteger numberOfTouches; // The number of touches (fingers) required to tap the screen to begin and end the range of motion step (default is 1)

- (instancetype)initWithIdentifier:(NSString *)identifier instructionText:(NSString *)instructionText limbOption:(ORKPredefinedTaskLimbOption)limbOption numberOfTaps:(NSInteger)numberOfTaps numberOfTouches:(NSInteger)numberOfTouches;

@end

Expand Down
45 changes: 43 additions & 2 deletions ResearchKit/ActiveTasks/ORKRangeOfMotionStep.m
Expand Up @@ -40,7 +40,7 @@ + (Class)stepViewControllerClass {
return [ORKRangeOfMotionStepViewController class];
}

- (instancetype)initWithIdentifier:(NSString *)identifier limbOption:(ORKPredefinedTaskLimbOption)limbOption {
- (instancetype)initWithIdentifier:(NSString *)identifier instructionText:(NSString *)instructionText limbOption:(ORKPredefinedTaskLimbOption)limbOption numberOfTaps:(NSInteger)numberOfTaps numberOfTouches:(NSInteger)numberOfTouches {
self = [super initWithIdentifier:identifier];
if (self) {
self.shouldVibrateOnStart = YES;
Expand All @@ -50,13 +50,22 @@ - (instancetype)initWithIdentifier:(NSString *)identifier limbOption:(ORKPredefi
self.shouldContinueOnFinish = YES;
self.shouldStartTimerAutomatically = YES;
self.limbOption = limbOption;
self.numberOfTaps = numberOfTaps;
self.numberOfTouches = numberOfTouches;
self.text = [NSString stringWithFormat:@"%@\n\n%@", instructionText, [self getInstructionText]];
}
return self;
}

- (void)validateParameters {
[super validateParameters];

// limit required number of touches (fingers) to between 1 and 4
if (self.numberOfTouches < 1 || self.numberOfTouches > 4) {
@throw [NSException exceptionWithName:NSInvalidArgumentException
reason:ORKLocalizedString(@"TOUCH_ANYWHERE_NUMBER_OF_TOUCHES_ERROR", nil)
userInfo:nil];
}
if (self.limbOption != ORKPredefinedTaskLimbOptionLeft && self.limbOption != ORKPredefinedTaskLimbOptionRight) {
@throw [NSException exceptionWithName:NSInvalidArgumentException
reason:ORKLocalizedString(@"LIMB_OPTION_LEFT_OR_RIGHT_ERROR", nil)
Expand All @@ -75,27 +84,59 @@ + (BOOL)supportsSecureCoding {
- (instancetype)copyWithZone:(NSZone *)zone {
ORKRangeOfMotionStep *step = [super copyWithZone:zone];
step.limbOption = self.limbOption;
step.numberOfTaps = self.numberOfTaps;
step.numberOfTouches = self.numberOfTouches;
return step;
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
ORK_DECODE_ENUM(aDecoder, limbOption);
ORK_DECODE_INTEGER(aDecoder, numberOfTaps);
ORK_DECODE_INTEGER(aDecoder, numberOfTouches);
}
return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder {
[super encodeWithCoder:aCoder];
ORK_ENCODE_ENUM(aCoder, limbOption);
ORK_DECODE_INTEGER(aCoder, numberOfTaps);
ORK_DECODE_INTEGER(aCoder, numberOfTouches);
}

- (BOOL)isEqual:(id)object {
BOOL isParentSame = [super isEqual:object];

__typeof(self) castObject = object;
return (isParentSame && (self.limbOption == castObject.limbOption));
return (isParentSame && (self.limbOption == castObject.limbOption) &&
(self.numberOfTaps == castObject.numberOfTaps) &&
(self.numberOfTouches == castObject.numberOfTouches));
}

- (NSString *)getInstructionText {
NSString *instructionText;
if(self.numberOfTaps <= 1) { // default
if (self.numberOfTouches > 1) { // number of fingers
instructionText = [NSString localizedStringWithFormat:ORKLocalizedString(@"TOUCH_ANYWHERE_FINISH", nil), [NSString localizedStringWithFormat:ORKLocalizedString(@"TOUCH_ANYWHERE_MULTIPLE_FINGERS", nil), @(self.numberOfTouches)]];
} else { // number of fingers is 1 or 0
instructionText = [NSString localizedStringWithFormat:ORKLocalizedString(@"TOUCH_ANYWHERE_FINISH", nil), @""];
}
} else if (self.numberOfTaps == 2) {
if (self.numberOfTouches > 1) {
instructionText = [NSString localizedStringWithFormat:ORKLocalizedString(@"TOUCH_ANYWHERE_DOUBLE_TAP_FINISH", nil), [NSString localizedStringWithFormat:ORKLocalizedString(@"TOUCH_ANYWHERE_MULTIPLE_FINGERS", nil), (@(self.numberOfTouches))]];
} else { // number of fingers is 1
instructionText = [NSString localizedStringWithFormat:ORKLocalizedString(@"TOUCH_ANYWHERE_DOUBLE_TAP_FINISH", nil), @""];
}
} else { // number of taps required is more than two
if (self.numberOfTouches > 1) {
instructionText = [NSString localizedStringWithFormat:ORKLocalizedString(@"TOUCH_ANYWHERE_FINISH", nil), [NSString stringWithFormat:@"%@%@", [NSString localizedStringWithFormat:ORKLocalizedString(@"TOUCH_ANYWHERE_MULTIPLE_FINGERS", nil), (@(self.numberOfTouches))], [NSString localizedStringWithFormat:ORKLocalizedString(@"TOUCH_ANYWHERE_NUMBER_OF_TOUCHES", nil), @(self.numberOfTaps)]]];
} else { // number of fingers is 1
instructionText = [NSString localizedStringWithFormat:ORKLocalizedString(@"TOUCH_ANYWHERE_FINISH", nil), [NSString localizedStringWithFormat:ORKLocalizedString(@"TOUCH_ANYWHERE_NUMBER_OF_TOUCHES", nil), @(self.numberOfTaps)]];
}
}
return instructionText;
}

@end
13 changes: 13 additions & 0 deletions ResearchKit/ActiveTasks/ORKRangeOfMotionStepViewController.m
Expand Up @@ -30,6 +30,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE


#import "ORKRangeOfMotionStepViewController.h"
#import "ORKRangeOfMotionStep.h"

#import "ORKCustomStepView_Internal.h"
#import "ORKHelpers_Internal.h"
Expand Down Expand Up @@ -132,12 +133,24 @@ @interface ORKRangeOfMotionStepViewController () <ORKDeviceMotionRecorderDelegat

@implementation ORKRangeOfMotionStepViewController

- (ORKRangeOfMotionStep *)rangeOfMotionStep {
return (ORKRangeOfMotionStep *)self.step;
}

- (void)viewDidLoad {
[super viewDidLoad];
_contentView = [ORKRangeOfMotionContentView new];
_contentView.translatesAutoresizingMaskIntoConstraints = NO;
self.activeStepView.activeCustomView = _contentView;

// set number of taps and number of touches (i.e. number of fingers tapping) to end the task
_gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)];
if (self.rangeOfMotionStep.numberOfTaps > 0) {
_gestureRecognizer.numberOfTapsRequired = self.rangeOfMotionStep.numberOfTaps;
}
if (self.rangeOfMotionStep.numberOfTouches > 0) {
_gestureRecognizer.numberOfTouchesRequired = self.rangeOfMotionStep.numberOfTouches;
}
[self.activeStepView addGestureRecognizer:_gestureRecognizer];
}
//This function records the angle of the device when the screen is tapped
Expand Down
6 changes: 5 additions & 1 deletion ResearchKit/ActiveTasks/ORKTouchAnywhereStep.h
Expand Up @@ -45,13 +45,17 @@ NS_ASSUME_NONNULL_BEGIN
ORK_CLASS_AVAILABLE
@interface ORKTouchAnywhereStep : ORKActiveStep

@property (nonatomic, assign) NSInteger numberOfTaps;

@property (nonatomic, assign) NSInteger numberOfTouches;

- (instancetype)init NS_UNAVAILABLE;

/**
Init with an identifier and instruction text. Instruction text should be used to tell the user
where to place the device before touching anywhere.
*/
- (instancetype)initWithIdentifier:(NSString *)identifier instructionText:(NSString *)instructionText;
- (instancetype)initWithIdentifier:(NSString *)identifier instructionText:(NSString *)instructionText numberOfTaps:(NSInteger)numberOfTaps numberOfTouches:(NSInteger)numberOfTouches;

@end

Expand Down
77 changes: 75 additions & 2 deletions ResearchKit/ActiveTasks/ORKTouchAnywhereStep.m
@@ -1,5 +1,6 @@
/*
Copyright (c) 2016, Darren Levy. All rights reserved.
Copyright (c) 2021, Dr David W. Evans. All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
Expand Down Expand Up @@ -40,13 +41,85 @@ + (Class)stepViewControllerClass {
return [ORKTouchAnywhereStepViewController class];
}

- (instancetype)initWithIdentifier:(NSString *)identifier instructionText:(NSString *)instructionText {
+ (BOOL)supportsSecureCoding {
return YES;
}

- (instancetype)initWithIdentifier:(NSString *)identifier instructionText:(NSString *)instructionText numberOfTaps:(NSInteger)numberOfTaps numberOfTouches:(NSInteger)numberOfTouches {
self = [super initWithIdentifier:identifier];
if (self) {
self.shouldStartTimerAutomatically = YES;
self.text = [instructionText stringByAppendingString:[@"\n" stringByAppendingString:ORKLocalizedString(@"TOUCH_ANYWHERE_LABEL", nil)]];
self.numberOfTaps = numberOfTaps;
self.numberOfTouches = numberOfTouches;
self.text = [NSString stringWithFormat:@"%@\n\n%@", instructionText, [self getInstructionText]];
}
return self;
}

- (void)validateParameters {
[super validateParameters];

// limit required number of touches (fingers) to between 1 and 4
if (self.numberOfTouches < 1 || self.numberOfTouches > 4) {
@throw [NSException exceptionWithName:NSInvalidArgumentException
reason:ORKLocalizedString(@"TOUCH_ANYWHERE_NUMBER_OF_TOUCHES_ERROR", nil)
userInfo:nil];
}
}

- (instancetype)copyWithZone:(NSZone *)zone {
ORKTouchAnywhereStep *step = [super copyWithZone:zone];
step.numberOfTaps = self.numberOfTaps;
step.numberOfTouches = self.numberOfTouches;
return step;
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self ) {
ORK_DECODE_INTEGER(aDecoder, numberOfTaps);
ORK_DECODE_INTEGER(aDecoder, numberOfTouches);
}
return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder {
[super encodeWithCoder:aCoder];
ORK_ENCODE_INTEGER(aCoder, numberOfTaps);
ORK_ENCODE_INTEGER(aCoder, numberOfTouches);
}

- (BOOL)isEqual:(id)object {
BOOL isParentSame = [super isEqual:object];

__typeof(self) castObject = object;
return (isParentSame &&
(self.numberOfTaps == castObject.numberOfTaps) &&
(self.numberOfTouches == castObject.numberOfTouches));
}

- (NSString *)getInstructionText {
NSString *instructionText;
if(self.numberOfTaps <= 1) { // default
if (self.numberOfTouches > 1) { // number of fingers
instructionText = [NSString localizedStringWithFormat:ORKLocalizedString(@"TOUCH_ANYWHERE_BEGIN", nil), [NSString localizedStringWithFormat:ORKLocalizedString(@"TOUCH_ANYWHERE_MULTIPLE_FINGERS", nil), @(self.numberOfTouches)]];
} else { // number of fingers is 1 or 0
instructionText = [NSString localizedStringWithFormat:ORKLocalizedString(@"TOUCH_ANYWHERE_BEGIN", nil), @""];
}
} else if (self.numberOfTaps == 2) {
if (self.numberOfTouches > 1) {
instructionText = [NSString localizedStringWithFormat:ORKLocalizedString(@"TOUCH_ANYWHERE_DOUBLE_TAP_BEGIN", nil), [NSString localizedStringWithFormat:ORKLocalizedString(@"TOUCH_ANYWHERE_MULTIPLE_FINGERS", nil), @(self.numberOfTouches)]];
} else { // number of fingers is 1
instructionText = [NSString localizedStringWithFormat:ORKLocalizedString(@"TOUCH_ANYWHERE_DOUBLE_TAP_BEGIN", nil), @""];
}
} else { // number of taps required is more than two
if (self.numberOfTouches > 1) {
instructionText = [NSString localizedStringWithFormat:ORKLocalizedString(@"TOUCH_ANYWHERE_BEGIN", nil), [NSString stringWithFormat:@"%@%@", [NSString localizedStringWithFormat:ORKLocalizedString(@"TOUCH_ANYWHERE_MULTIPLE_FINGERS", nil), @(self.numberOfTouches)], [NSString localizedStringWithFormat:ORKLocalizedString(@"TOUCH_ANYWHERE_NUMBER_OF_TOUCHES", nil), @(self.numberOfTaps)]]];
} else { // number of fingers is 1
instructionText = [NSString localizedStringWithFormat:ORKLocalizedString(@"TOUCH_ANYWHERE_BEGIN", nil), [NSString localizedStringWithFormat:ORKLocalizedString(@"TOUCH_ANYWHERE_NUMBER_OF_TOUCHES", nil), @(self.numberOfTaps)]];
}
}
return instructionText;
}

@end
13 changes: 13 additions & 0 deletions ResearchKit/ActiveTasks/ORKTouchAnywhereStepViewController.m
Expand Up @@ -30,6 +30,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE


#import "ORKTouchAnywhereStepViewController.h"
#import "ORKTouchAnywhereStep.h"
#import "ORKActiveStepViewController_Internal.h"
#import "ORKStepViewController_Internal.h"
#import "ORKCustomStepView_Internal.h"
Expand Down Expand Up @@ -112,13 +113,25 @@ @interface ORKTouchAnywhereStepViewController ()

@implementation ORKTouchAnywhereStepViewController


- (ORKTouchAnywhereStep *)touchAnywhereStep {
return (ORKTouchAnywhereStep *)self.step;
}

- (void)viewDidLoad {
[super viewDidLoad];
_touchAnywhereView = [[ORKTouchAnywhereView alloc] init];
_touchAnywhereView.translatesAutoresizingMaskIntoConstraints = NO;
self.activeStepView.activeCustomView = _touchAnywhereView;

// set number of taps and number of touches (i.e. number of fingers tapping) to end the task. Default value of both is 1.
_gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)];
if (self.touchAnywhereStep.numberOfTaps > 0) {
_gestureRecognizer.numberOfTapsRequired = self.touchAnywhereStep.numberOfTaps;
}
if (self.touchAnywhereStep.numberOfTouches > 0) {
_gestureRecognizer.numberOfTouchesRequired = self.touchAnywhereStep.numberOfTouches;
}
[self.activeStepView addGestureRecognizer:_gestureRecognizer];
self.internalContinueButtonItem = nil;
}
Expand Down
8 changes: 8 additions & 0 deletions ResearchKit/Common/ORKOrderedTask+ORKPredefinedActiveTask.h
Expand Up @@ -197,11 +197,15 @@ NS_ASSUME_NONNULL_BEGIN

@param identifier The task identifier to use for this task, appropriate to the study.
@param limbOption Which knee is being measured.
@param numberOfTaps The number of taps required to begin and end the range of motion step
@param numberOfTouches The number of touches (fingers) required to tap the screen to begin and end the range of motion step (between 1 and 4)
@param intendedUseDescription A localized string describing the intended use of the data collected. If the value of this parameter is `nil`, default localized text is used.
@param options Options that affect the features of the predefined task.
*/
+ (ORKOrderedTask *)kneeRangeOfMotionTaskWithIdentifier:(NSString *)identifier
limbOption:(ORKPredefinedTaskLimbOption)limbOption
numberOfTaps: (NSInteger)numberOfTaps
numberOfTouches: (NSInteger)numberOfTouches
intendedUseDescription:(nullable NSString *)intendedUseDescription
options:(ORKPredefinedTaskOption)options;

Expand All @@ -211,11 +215,15 @@ NS_ASSUME_NONNULL_BEGIN

@param identifier The task identifier to use for this task, appropriate to the study.
@param limbOption Which shoulder is being measured.
@param numberOfTaps The number of taps required to begin and end the range of motion step
@param numberOfTouches The number of touches (fingers) required to tap the screen to begin and end the range of motion step (between 1 and 4)
@param intendedUseDescription A localized string describing the intended use of the data collected. If the value of this parameter is `nil`, default localized text is used.
@param options Options that affect the features of the predefined task.
*/
+ (ORKOrderedTask *)shoulderRangeOfMotionTaskWithIdentifier:(NSString *)identifier
limbOption:(ORKPredefinedTaskLimbOption)limbOption
numberOfTaps: (NSInteger)numberOfTaps
numberOfTouches: (NSInteger)numberOfTouches
intendedUseDescription:(nullable NSString *)intendedUseDescription
options:(ORKPredefinedTaskOption)options;

Expand Down