diff --git a/ORK1Kit/ORK1Kit/ActiveTasks/CEV1RangeOfMotionStepViewController.swift b/ORK1Kit/ORK1Kit/ActiveTasks/CEV1RangeOfMotionStepViewController.swift index c90e0befb..392a2b994 100644 --- a/ORK1Kit/ORK1Kit/ActiveTasks/CEV1RangeOfMotionStepViewController.swift +++ b/ORK1Kit/ORK1Kit/ActiveTasks/CEV1RangeOfMotionStepViewController.swift @@ -37,9 +37,8 @@ public class CEV1RangeOfMotionStepViewController: ORK1RangeOfMotionStepViewContr result.flexed = lowestAngle result.extended = highestAngle } - result.fileResult = self.fileResult - stepResult?.results = (self.addedResults ?? []) + [result] + stepResult?.results = (stepResult?.results ?? []) + [result] return stepResult } diff --git a/ORK1Kit/ORK1Kit/ActiveTasks/ORK1ActiveStep.h b/ORK1Kit/ORK1Kit/ActiveTasks/ORK1ActiveStep.h index 56a5836c1..5109fdfdd 100644 --- a/ORK1Kit/ORK1Kit/ActiveTasks/ORK1ActiveStep.h +++ b/ORK1Kit/ORK1Kit/ActiveTasks/ORK1ActiveStep.h @@ -219,6 +219,12 @@ The default value of this property is `NO`. */ @property (nonatomic, copy, nullable) NSArray *recorderConfigurations; +/** + A Boolean value that indicates whether the step must wait for all recorders to complete, returning a result + or error before allowing the step to proceed. + */ +@property (nonatomic) BOOL recordersMustCompleteBeforeAdvancingStep; + @end NS_ASSUME_NONNULL_END diff --git a/ORK1Kit/ORK1Kit/ActiveTasks/ORK1ActiveStep.m b/ORK1Kit/ORK1Kit/ActiveTasks/ORK1ActiveStep.m index 290139d82..f12f9fde1 100644 --- a/ORK1Kit/ORK1Kit/ActiveTasks/ORK1ActiveStep.m +++ b/ORK1Kit/ORK1Kit/ActiveTasks/ORK1ActiveStep.m @@ -102,6 +102,7 @@ - (instancetype)copyWithZone:(NSZone *)zone { step.spokenInstruction = self.spokenInstruction; step.finishedSpokenInstruction = self.finishedSpokenInstruction; step.recorderConfigurations = [self.recorderConfigurations copy]; + step.recordersMustCompleteBeforeAdvancingStep = self.recordersMustCompleteBeforeAdvancingStep; step.image = self.image; step.imageAltText = self.imageAltText; return step; @@ -126,6 +127,7 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder { ORK1_DECODE_IMAGE(aDecoder, image); ORK1_DECODE_OBJ_CLASS(aDecoder, imageAltText, NSString); ORK1_DECODE_OBJ_ARRAY(aDecoder, recorderConfigurations, ORK1RecorderConfiguration); + ORK1_DECODE_BOOL(aDecoder, recordersMustCompleteBeforeAdvancingStep); } return self; } @@ -148,6 +150,7 @@ - (void)encodeWithCoder:(NSCoder *)aCoder { ORK1_ENCODE_OBJ(aCoder, spokenInstruction); ORK1_ENCODE_OBJ(aCoder, finishedSpokenInstruction); ORK1_ENCODE_OBJ(aCoder, recorderConfigurations); + ORK1_ENCODE_BOOL(aCoder, recordersMustCompleteBeforeAdvancingStep); } - (BOOL)isEqual:(id)object { @@ -170,7 +173,8 @@ - (BOOL)isEqual:(id)object { (self.shouldVibrateOnStart == castObject.shouldVibrateOnStart) && (self.shouldVibrateOnFinish == castObject.shouldVibrateOnFinish) && (self.shouldContinueOnFinish == castObject.shouldContinueOnFinish) && - (self.shouldUseNextAsSkipButton == castObject.shouldUseNextAsSkipButton)); + (self.shouldUseNextAsSkipButton == castObject.shouldUseNextAsSkipButton) && + (self.recordersMustCompleteBeforeAdvancingStep == castObject.recordersMustCompleteBeforeAdvancingStep)); } - (NSSet *)requestedHealthKitTypesForReading { diff --git a/ORK1Kit/ORK1Kit/ActiveTasks/ORK1ActiveStepViewController.h b/ORK1Kit/ORK1Kit/ActiveTasks/ORK1ActiveStepViewController.h index 1845408a2..a96864d37 100644 --- a/ORK1Kit/ORK1Kit/ActiveTasks/ORK1ActiveStepViewController.h +++ b/ORK1Kit/ORK1Kit/ActiveTasks/ORK1ActiveStepViewController.h @@ -211,6 +211,12 @@ ORK1_CLASS_AVAILABLE */ - (void)finish; +/** + Triggers the step to advance once the recorders have completed; + - Parameter stepDidFinishOnly: do not explicity call goForward on self when recorders are done (for steps that goForward without calling finish) + */ +- (void)goForwardOnceRecordersHaveCompleted:(BOOL)stepDidFinishOnly; + /// @name Recorder life cycle /** diff --git a/ORK1Kit/ORK1Kit/ActiveTasks/ORK1ActiveStepViewController.m b/ORK1Kit/ORK1Kit/ActiveTasks/ORK1ActiveStepViewController.m index dd2190508..0b57d49c7 100644 --- a/ORK1Kit/ORK1Kit/ActiveTasks/ORK1ActiveStepViewController.m +++ b/ORK1Kit/ORK1Kit/ActiveTasks/ORK1ActiveStepViewController.m @@ -59,6 +59,9 @@ @interface ORK1ActiveStepViewController () { NSArray *_recorderResults; + NSMutableSet *_recordersRunning; // only accessed on the main queue + NSInteger _recordersCheckLoopCount; // only accessed on the main queue + SystemSoundID _alertSound; NSURL *_alertSoundURL; BOOL _hasSpokenHalfwayCountdown; @@ -76,6 +79,8 @@ - (instancetype)initWithStep:(ORK1Step *)step { self = [super initWithStep:step]; if (self) { _recorderResults = [NSArray new]; + _recordersRunning = [NSMutableSet new]; + _recordersCheckLoopCount = 0; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillResignActive:) name:UIApplicationWillResignActiveNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil]; @@ -292,6 +297,9 @@ - (void)startRecorders { for (ORK1Recorder *recorder in self.recorders) { [recorder viewController:self willStartStepWithView:self.customViewContainer]; [recorder start]; + dispatch_async(dispatch_get_main_queue(), ^{ + [_recordersRunning addObject:recorder]; + }); } } @@ -381,12 +389,39 @@ - (void)finish { } if (!self.activeStep.startsFinished) { if (self.activeStep.shouldContinueOnFinish) { - [self goForward]; + if (self.activeStep.recordersMustCompleteBeforeAdvancingStep) { + [self goForwardOnceRecordersHaveCompleted:NO]; + } else { + [self goForward]; + } } } [self stepDidFinish]; } +- (void)goForwardOnceRecordersHaveCompleted:(BOOL)stepDidFinishOnly { + dispatch_async(dispatch_get_main_queue(), ^{ + if ([_recordersRunning count] == 0) { + if (!stepDidFinishOnly) { + [self goForward]; + } + [self stepDidFinish]; + } else if (_recordersCheckLoopCount > 10) { + NSLog(@"Recorders are still not complete after 10 wait cycles, data may be lost in final result."); + if (!stepDidFinishOnly) { + [self goForward]; + } + [self stepDidFinish]; + } else { + _recordersCheckLoopCount += 1; + NSLog(@"Waiting for [%lu] recorders to complete...", [_recordersRunning count]); + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [self goForwardOnceRecordersHaveCompleted:stepDidFinishOnly]; + }); + } + }); +} + - (void)dealloc { AudioServicesDisposeSystemSoundID(_alertSound); NSNotificationCenter *nfc = [NSNotificationCenter defaultCenter]; @@ -470,6 +505,9 @@ - (void)stepDidFinish { - (void)recorder:(ORK1Recorder *)recorder didCompleteWithResult:(ORK1Result *)result { _recorderResults = [_recorderResults arrayByAddingObject:result]; + dispatch_async(dispatch_get_main_queue(), ^{ + [_recordersRunning removeObject:recorder]; + }); [self notifyDelegateOnResultChange]; } @@ -488,6 +526,9 @@ - (void)recorder:(ORK1Recorder *)recorder didFailWithError:(NSError *)error { self.outputDirectory == nil) { [strongDelegate stepViewControllerDidFail:self withError:error]; } + dispatch_async(dispatch_get_main_queue(), ^{ + [_recordersRunning removeObject:recorder]; + }); } } diff --git a/ORK1Kit/ORK1Kit/ActiveTasks/ORK1RangeOfMotionStep.m b/ORK1Kit/ORK1Kit/ActiveTasks/ORK1RangeOfMotionStep.m index 23ca315f1..3d6441fa3 100644 --- a/ORK1Kit/ORK1Kit/ActiveTasks/ORK1RangeOfMotionStep.m +++ b/ORK1Kit/ORK1Kit/ActiveTasks/ORK1RangeOfMotionStep.m @@ -50,6 +50,7 @@ - (instancetype)initWithIdentifier:(NSString *)identifier limbOption:(ORK1Predef self.shouldContinueOnFinish = YES; self.shouldStartTimerAutomatically = YES; self.limbOption = limbOption; + self.recordersMustCompleteBeforeAdvancingStep = YES; } return self; } diff --git a/ORK1Kit/ORK1Kit/ActiveTasks/ORK1RangeOfMotionStepViewController.m b/ORK1Kit/ORK1Kit/ActiveTasks/ORK1RangeOfMotionStepViewController.m index 5c4fd3671..212be3802 100644 --- a/ORK1Kit/ORK1Kit/ActiveTasks/ORK1RangeOfMotionStepViewController.m +++ b/ORK1Kit/ORK1Kit/ActiveTasks/ORK1RangeOfMotionStepViewController.m @@ -175,12 +175,6 @@ - (void)deviceMotionRecorderDidUpdateWithMotion:(CMDeviceMotion *)motion { _lastAngle = angle; } -#pragma mark - ORK1RecorderDelegate - -- (void)recorder:(ORK1Recorder *)recorder didCompleteWithResult:(ORK1Result *)result { - self.fileResult = (ORK1FileResult *)result; -} - /* When the device is in Portrait mode, we need to get the attitude's pitch to determine the device's angle. attitude.pitch doesn't return all @@ -209,14 +203,12 @@ - (double)getDeviceAngleInDegreesFromAttitude:(CMAttitude *)attitude { - (ORK1Result *)result { ORK1StepResult *stepResult = [super result]; - - ORK1RangeOfMotionResult *result = [[ORK1RangeOfMotionResult alloc] initWithIdentifier:self.step.identifier]; - result.flexed = _flexedAngle; - result.extended = result.flexed - _rangeOfMotionAngle; - result.fileResult = _fileResult; - - stepResult.results = [self.addedResults arrayByAddingObject:result] ? : @[result]; - + NSMutableArray *results = [NSMutableArray arrayWithArray:stepResult.results]; + ORK1RangeOfMotionResult *rangeOfMotionResult = [[ORK1RangeOfMotionResult alloc] initWithIdentifier:self.step.identifier]; + rangeOfMotionResult.flexed = _flexedAngle; + rangeOfMotionResult.extended = rangeOfMotionResult.flexed - _rangeOfMotionAngle; + [results addObject:rangeOfMotionResult]; + stepResult.results = [results copy]; return stepResult; } diff --git a/ORK1Kit/ORK1Kit/ActiveTasks/ORK1ShoulderRangeOfMotionStepViewController.m b/ORK1Kit/ORK1Kit/ActiveTasks/ORK1ShoulderRangeOfMotionStepViewController.m index 7170de494..0c797c81e 100644 --- a/ORK1Kit/ORK1Kit/ActiveTasks/ORK1ShoulderRangeOfMotionStepViewController.m +++ b/ORK1Kit/ORK1Kit/ActiveTasks/ORK1ShoulderRangeOfMotionStepViewController.m @@ -40,11 +40,14 @@ @implementation ORK1ShoulderRangeOfMotionStepViewController - (ORK1Result *)result { ORK1StepResult *stepResult = [super result]; - ORK1RangeOfMotionResult *result = [[ORK1RangeOfMotionResult alloc] initWithIdentifier:self.step.identifier]; - result.flexed = 90.0 - _flexedAngle; - result.extended = result.flexed + _rangeOfMotionAngle; + NSMutableArray *results = [NSMutableArray arrayWithArray:stepResult.results]; - stepResult.results = [self.addedResults arrayByAddingObject:result] ? : @[result]; + ORK1RangeOfMotionResult *rangeOfMotionResult = [[ORK1RangeOfMotionResult alloc] initWithIdentifier:self.step.identifier]; + rangeOfMotionResult.flexed = 90.0 - _flexedAngle; + rangeOfMotionResult.extended = rangeOfMotionResult.flexed + _rangeOfMotionAngle; + [results addObject:rangeOfMotionResult]; + + stepResult.results = [results copy]; return stepResult; } diff --git a/ORK1Kit/ORK1Kit/ActiveTasks/ORK1TimedWalkStepViewController.m b/ORK1Kit/ORK1Kit/ActiveTasks/ORK1TimedWalkStepViewController.m index 05fa4b773..ff61e2d05 100644 --- a/ORK1Kit/ORK1Kit/ActiveTasks/ORK1TimedWalkStepViewController.m +++ b/ORK1Kit/ORK1Kit/ActiveTasks/ORK1TimedWalkStepViewController.m @@ -90,10 +90,15 @@ - (void)viewDidLoad { self.timerUpdateInterval = 0.1f; } -- (void)finish { +// The ORK1TimedWalkStepViewController expects the Next button to trigger goForward and doesn't call finish explicitly +- (void)goForward { + [super stopRecorders]; + [super goForwardOnceRecordersHaveCompleted:YES]; +} + +- (void)stepDidFinish { [super finish]; - - [self goForward]; + [super goForward]; } - (void)countDownTimerFired:(ORK1ActiveStepTimer *)timer finished:(BOOL)finished { diff --git a/ORK1Kit/ORK1Kit/Common/ORK1Result.h b/ORK1Kit/ORK1Kit/Common/ORK1Result.h index 524dadc53..c0b529b88 100644 --- a/ORK1Kit/ORK1Kit/Common/ORK1Result.h +++ b/ORK1Kit/ORK1Kit/Common/ORK1Result.h @@ -327,11 +327,6 @@ ORK1_CLASS_AVAILABLE */ @property (nonatomic, assign) double extended; -/** - The recorder output - usually the device motion recorder. - */ -@property (nonatomic, strong) ORK1FileResult *fileResult; - @end diff --git a/ORK1Kit/ORK1Kit/Common/ORK1Result.m b/ORK1Kit/ORK1Kit/Common/ORK1Result.m index 9b0edc614..73b77a4a8 100644 --- a/ORK1Kit/ORK1Kit/Common/ORK1Result.m +++ b/ORK1Kit/ORK1Kit/Common/ORK1Result.m @@ -258,7 +258,6 @@ - (void)encodeWithCoder:(NSCoder *)aCoder { [super encodeWithCoder:aCoder]; ORK1_ENCODE_DOUBLE(aCoder, flexed); ORK1_ENCODE_DOUBLE(aCoder, extended); - ORK1_ENCODE_OBJ(aCoder, fileResult); } - (id)initWithCoder:(NSCoder *)aDecoder { @@ -266,7 +265,6 @@ - (id)initWithCoder:(NSCoder *)aDecoder { if (self) { ORK1_DECODE_DOUBLE(aDecoder, flexed); ORK1_DECODE_DOUBLE(aDecoder, extended); - ORK1_DECODE_OBJ_CLASS(aDecoder, fileResult, ORK1FileResult); } return self; } @@ -280,24 +278,18 @@ - (BOOL)isEqual:(id)object { __typeof(self) castObject = object; return (isParentSame && self.flexed == castObject.flexed && - self.extended == castObject.extended && - ORK1EqualObjects(self.fileResult, castObject.fileResult)); -} - -- (NSUInteger)hash { - return super.hash ^ self.fileResult.hash; + self.extended == castObject.extended); } - (instancetype)copyWithZone:(NSZone *)zone { ORK1RangeOfMotionResult *result = [super copyWithZone:zone]; result.flexed = self.flexed; result.extended = self.extended; - result.fileResult = self.fileResult; return result; } - (NSString *)descriptionWithNumberOfPaddingSpaces:(NSUInteger)numberOfPaddingSpaces { - return [NSString stringWithFormat:@"<%@: flexion: %f; extension: %f; fileResult: %@>", self.class.description, self.flexed, self.extended, self.fileResult.description]; + return [NSString stringWithFormat:@"<%@: flexion: %f; extension: %f>", self.class.description, self.flexed, self.extended]; } @end diff --git a/ResearchKit/ActiveTasks/CEVRangeOfMotionStepViewController.swift b/ResearchKit/ActiveTasks/CEVRangeOfMotionStepViewController.swift index f810dfdeb..b0d1534f3 100644 --- a/ResearchKit/ActiveTasks/CEVRangeOfMotionStepViewController.swift +++ b/ResearchKit/ActiveTasks/CEVRangeOfMotionStepViewController.swift @@ -37,9 +37,8 @@ public class CEVRangeOfMotionStepViewController: ORKRangeOfMotionStepViewControl result.flexed = lowestAngle result.extended = highestAngle } - result.fileResult = self.fileResult - stepResult?.results = (self.addedResults ?? []) + [result] + stepResult?.results = (stepResult?.results ?? []) + [result] return stepResult } diff --git a/ResearchKit/ActiveTasks/ORKActiveStep.h b/ResearchKit/ActiveTasks/ORKActiveStep.h index dca07f07f..4a4312711 100644 --- a/ResearchKit/ActiveTasks/ORKActiveStep.h +++ b/ResearchKit/ActiveTasks/ORKActiveStep.h @@ -228,6 +228,12 @@ The default value of this property is `NO`. */ @property (nonatomic, assign) BOOL isPractice; +/** + A Boolean value that indicates whether the step must wait for all recorders to complete, returning a result + or error before allowing the step to proceed. + */ +@property (nonatomic) BOOL recordersMustCompleteBeforeAdvancingStep; + @end NS_ASSUME_NONNULL_END diff --git a/ResearchKit/ActiveTasks/ORKActiveStep.m b/ResearchKit/ActiveTasks/ORKActiveStep.m index 918600dd2..ca3f5605d 100644 --- a/ResearchKit/ActiveTasks/ORKActiveStep.m +++ b/ResearchKit/ActiveTasks/ORKActiveStep.m @@ -102,6 +102,7 @@ - (instancetype)copyWithZone:(NSZone *)zone { step.spokenInstruction = self.spokenInstruction; step.finishedSpokenInstruction = self.finishedSpokenInstruction; step.recorderConfigurations = [self.recorderConfigurations copy]; + step.recordersMustCompleteBeforeAdvancingStep = self.recordersMustCompleteBeforeAdvancingStep; step.image = self.image; step.imageAltText = self.imageAltText; step.isPractice = self.isPractice; @@ -128,6 +129,7 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder { ORK_DECODE_OBJ_CLASS(aDecoder, imageAltText, NSString); ORK_DECODE_OBJ_ARRAY(aDecoder, recorderConfigurations, ORKRecorderConfiguration); ORK_DECODE_BOOL(aDecoder, isPractice); + ORK_DECODE_BOOL(aDecoder, recordersMustCompleteBeforeAdvancingStep); } return self; } @@ -151,6 +153,7 @@ - (void)encodeWithCoder:(NSCoder *)aCoder { ORK_ENCODE_OBJ(aCoder, finishedSpokenInstruction); ORK_ENCODE_OBJ(aCoder, recorderConfigurations); ORK_ENCODE_BOOL(aCoder, isPractice); + ORK_ENCODE_BOOL(aCoder, recordersMustCompleteBeforeAdvancingStep); } - (BOOL)isEqual:(id)object { @@ -174,7 +177,8 @@ - (BOOL)isEqual:(id)object { (self.shouldVibrateOnFinish == castObject.shouldVibrateOnFinish) && (self.shouldContinueOnFinish == castObject.shouldContinueOnFinish) && (self.shouldUseNextAsSkipButton == castObject.shouldUseNextAsSkipButton) && - (self.isPractice == castObject.isPractice)); + (self.isPractice == castObject.isPractice) && + (self.recordersMustCompleteBeforeAdvancingStep == castObject.recordersMustCompleteBeforeAdvancingStep)); } - (NSSet *)requestedHealthKitTypesForReading { diff --git a/ResearchKit/ActiveTasks/ORKActiveStepViewController.h b/ResearchKit/ActiveTasks/ORKActiveStepViewController.h index 38c397cb2..5ef804ada 100644 --- a/ResearchKit/ActiveTasks/ORKActiveStepViewController.h +++ b/ResearchKit/ActiveTasks/ORKActiveStepViewController.h @@ -211,6 +211,12 @@ ORK_CLASS_AVAILABLE */ - (void)finish; +/** + Triggers the step to advance once the recorders have completed; + - Parameter stepDidFinishOnly: do not explicity call goForward on self when recorders are done (for steps that goForward without calling finish) + */ +- (void)goForwardOnceRecordersHaveCompleted:(BOOL)stepDidFinishOnly; + /// @name Recorder life cycle /** diff --git a/ResearchKit/ActiveTasks/ORKActiveStepViewController.m b/ResearchKit/ActiveTasks/ORKActiveStepViewController.m index 8353724e5..a7bd893be 100644 --- a/ResearchKit/ActiveTasks/ORKActiveStepViewController.m +++ b/ResearchKit/ActiveTasks/ORKActiveStepViewController.m @@ -59,6 +59,8 @@ @interface ORKActiveStepViewController () { ORKActiveStepTimer *_activeStepTimer; NSArray *_recorderResults; + NSMutableSet *_recordersRunning; + NSInteger _recordersCheckLoopCount; SystemSoundID _alertSound; NSURL *_alertSoundURL; @@ -78,6 +80,8 @@ - (instancetype)initWithStep:(ORKStep *)step { self = [super initWithStep:step]; if (self) { _recorderResults = [NSArray new]; + _recordersRunning = [NSMutableSet new]; + _recordersCheckLoopCount = 0; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillResignActive:) name:UIApplicationWillResignActiveNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil]; @@ -367,6 +371,9 @@ - (void)startRecorders { for (ORKRecorder *recorder in self.recorders) { [recorder viewController:self willStartStepWithView:self.customViewContainer]; [recorder start]; + dispatch_async(dispatch_get_main_queue(), ^{ + [_recordersRunning addObject:recorder]; + }); } } @@ -457,11 +464,39 @@ - (void)finish { if (!self.activeStep.startsFinished) { if (self.activeStep.shouldContinueOnFinish) { [self goForward]; + if (self.activeStep.recordersMustCompleteBeforeAdvancingStep) { + [self goForwardOnceRecordersHaveCompleted:NO]; + } else { + [self goForward]; + } } } [self stepDidFinish]; } +- (void)goForwardOnceRecordersHaveCompleted:(BOOL)stepDidFinishOnly { + dispatch_async(dispatch_get_main_queue(), ^{ + if ([_recordersRunning count] == 0) { + if (!stepDidFinishOnly) { + [self goForward]; + } + [self stepDidFinish]; + } else if (_recordersCheckLoopCount > 10) { + NSLog(@"Recorders are still not complete after 10 wait cycles, data may be lost in final result."); + if (!stepDidFinishOnly) { + [self goForward]; + } + [self stepDidFinish]; + } else { + _recordersCheckLoopCount += 1; + NSLog(@"Waiting for [%lu] recorders to complete...", [_recordersRunning count]); + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [self goForwardOnceRecordersHaveCompleted:stepDidFinishOnly]; + }); + } + }); +} + - (void)dealloc { AudioServicesDisposeSystemSoundID(_alertSound); NSNotificationCenter *nfc = [NSNotificationCenter defaultCenter]; @@ -553,6 +588,9 @@ - (void)stepDidFinish { - (void)recorder:(ORKRecorder *)recorder didCompleteWithResult:(ORKResult *)result { _recorderResults = [_recorderResults arrayByAddingObject:result]; + dispatch_async(dispatch_get_main_queue(), ^{ + [_recordersRunning removeObject:recorder]; + }); [self notifyDelegateOnResultChange]; } @@ -571,6 +609,9 @@ - (void)recorder:(ORKRecorder *)recorder didFailWithError:(NSError *)error { self.outputDirectory == nil) { [strongDelegate stepViewControllerDidFail:self withError:error]; } + dispatch_async(dispatch_get_main_queue(), ^{ + [_recordersRunning removeObject:recorder]; + }); } } diff --git a/ResearchKit/ActiveTasks/ORKRangeOfMotionResult.h b/ResearchKit/ActiveTasks/ORKRangeOfMotionResult.h index 76268509b..d06d32f7b 100644 --- a/ResearchKit/ActiveTasks/ORKRangeOfMotionResult.h +++ b/ResearchKit/ActiveTasks/ORKRangeOfMotionResult.h @@ -54,11 +54,6 @@ ORK_CLASS_AVAILABLE */ @property (nonatomic, assign) double extended; -/** - The recorder output - usually the device motion recorder. - */ -@property (nonatomic, strong) ORKFileResult *fileResult; - @end NS_ASSUME_NONNULL_END diff --git a/ResearchKit/ActiveTasks/ORKRangeOfMotionResult.m b/ResearchKit/ActiveTasks/ORKRangeOfMotionResult.m index ba464de63..66c7f7baf 100644 --- a/ResearchKit/ActiveTasks/ORKRangeOfMotionResult.m +++ b/ResearchKit/ActiveTasks/ORKRangeOfMotionResult.m @@ -42,7 +42,6 @@ - (void)encodeWithCoder:(NSCoder *)aCoder { [super encodeWithCoder:aCoder]; ORK_ENCODE_DOUBLE(aCoder, flexed); ORK_ENCODE_DOUBLE(aCoder, extended); - ORK_ENCODE_OBJ(aCoder, fileResult); } - (id)initWithCoder:(NSCoder *)aDecoder { @@ -50,7 +49,6 @@ - (id)initWithCoder:(NSCoder *)aDecoder { if (self) { ORK_DECODE_DOUBLE(aDecoder, flexed); ORK_DECODE_DOUBLE(aDecoder, extended); - ORK_DECODE_OBJ_CLASS(aDecoder, fileResult, ORKFileResult); } return self; } @@ -64,24 +62,18 @@ - (BOOL)isEqual:(id)object { __typeof(self) castObject = object; return (isParentSame && self.flexed == castObject.flexed && - self.extended == castObject.extended && - ORKEqualObjects(self.fileResult, castObject.fileResult)); -} - -- (NSUInteger)hash { - return super.hash ^ self.fileResult.hash; + self.extended == castObject.extended); } - (instancetype)copyWithZone:(NSZone *)zone { ORKRangeOfMotionResult *result = [super copyWithZone:zone]; result.flexed = self.flexed; result.extended = self.extended; - result.fileResult = self.fileResult; return result; } - (NSString *)descriptionWithNumberOfPaddingSpaces:(NSUInteger)numberOfPaddingSpaces { - return [NSString stringWithFormat:@"<%@: flexion: %f; extension: %f; fileResult: %@>", self.class.description, self.flexed, self.extended, self.fileResult.description]; + return [NSString stringWithFormat:@"<%@: flexion: %f; extension: %f>", self.class.description, self.flexed, self.extended]; } @end diff --git a/ResearchKit/ActiveTasks/ORKRangeOfMotionStep.m b/ResearchKit/ActiveTasks/ORKRangeOfMotionStep.m index 35a1f6f8e..d60fb306d 100644 --- a/ResearchKit/ActiveTasks/ORKRangeOfMotionStep.m +++ b/ResearchKit/ActiveTasks/ORKRangeOfMotionStep.m @@ -50,6 +50,7 @@ - (instancetype)initWithIdentifier:(NSString *)identifier limbOption:(ORKPredefi self.shouldContinueOnFinish = YES; self.shouldStartTimerAutomatically = YES; self.limbOption = limbOption; + self.recordersMustCompleteBeforeAdvancingStep = YES; } return self; } diff --git a/ResearchKit/ActiveTasks/ORKRangeOfMotionStepViewController.m b/ResearchKit/ActiveTasks/ORKRangeOfMotionStepViewController.m index 0ccdf3c92..d4810a13b 100644 --- a/ResearchKit/ActiveTasks/ORKRangeOfMotionStepViewController.m +++ b/ResearchKit/ActiveTasks/ORKRangeOfMotionStepViewController.m @@ -177,12 +177,6 @@ - (void)deviceMotionRecorderDidUpdateWithMotion:(CMDeviceMotion *)motion { _lastAngle = angle; } -#pragma mark - ORKRecorderDelegate - -- (void)recorder:(ORKRecorder *)recorder didCompleteWithResult:(ORKResult *)result { - self.fileResult = (ORKFileResult *)result; -} - /* When the device is in Portrait mode, we need to get the attitude's pitch to determine the device's angle. attitude.pitch doesn't return all @@ -211,14 +205,12 @@ - (double)getDeviceAngleInDegreesFromAttitude:(CMAttitude *)attitude { - (ORKResult *)result { ORKStepResult *stepResult = [super result]; - - ORKRangeOfMotionResult *result = [[ORKRangeOfMotionResult alloc] initWithIdentifier:self.step.identifier]; - result.flexed = _flexedAngle; - result.extended = result.flexed - _rangeOfMotionAngle; - result.fileResult = _fileResult; - - stepResult.results = [self.addedResults arrayByAddingObject:result] ? : @[result]; - + NSMutableArray *results = [NSMutableArray arrayWithArray:stepResult.results]; + ORKRangeOfMotionResult *rangeOfMotionResult = [[ORKRangeOfMotionResult alloc] initWithIdentifier:self.step.identifier]; + rangeOfMotionResult.flexed = _flexedAngle; + rangeOfMotionResult.extended = rangeOfMotionResult.flexed - _rangeOfMotionAngle; + [results addObject:rangeOfMotionResult]; + stepResult.results = [results copy]; return stepResult; } diff --git a/ResearchKit/ActiveTasks/ORKShoulderRangeOfMotionStepViewController.m b/ResearchKit/ActiveTasks/ORKShoulderRangeOfMotionStepViewController.m index 7c9c1a80a..8a9e62cc2 100644 --- a/ResearchKit/ActiveTasks/ORKShoulderRangeOfMotionStepViewController.m +++ b/ResearchKit/ActiveTasks/ORKShoulderRangeOfMotionStepViewController.m @@ -43,11 +43,14 @@ @implementation ORKShoulderRangeOfMotionStepViewController - (ORKResult *)result { ORKStepResult *stepResult = [super result]; - ORKRangeOfMotionResult *result = [[ORKRangeOfMotionResult alloc] initWithIdentifier:self.step.identifier]; - result.flexed = 90.0 - _flexedAngle; - result.extended = result.flexed + _rangeOfMotionAngle; + NSMutableArray *results = [NSMutableArray arrayWithArray:stepResult.results]; - stepResult.results = [self.addedResults arrayByAddingObject:result] ? : @[result]; + ORKRangeOfMotionResult *rangeOfMotionResult = [[ORKRangeOfMotionResult alloc] initWithIdentifier:self.step.identifier]; + rangeOfMotionResult.flexed = 90.0 - _flexedAngle; + rangeOfMotionResult.extended = rangeOfMotionResult.flexed + _rangeOfMotionAngle; + [results addObject:rangeOfMotionResult]; + + stepResult.results = [results copy]; return stepResult; } diff --git a/ResearchKit/ActiveTasks/ORKTimedWalkStepViewController.m b/ResearchKit/ActiveTasks/ORKTimedWalkStepViewController.m index 3d0008da2..577d73a97 100644 --- a/ResearchKit/ActiveTasks/ORKTimedWalkStepViewController.m +++ b/ResearchKit/ActiveTasks/ORKTimedWalkStepViewController.m @@ -91,10 +91,15 @@ - (void)viewDidLoad { self.timerUpdateInterval = 0.1f; } -- (void)finish { +// The ORKTimedWalkStepViewController expects the Next button to trigger goForward and doesn't call finish explicitly +- (void)goForward { + [super stopRecorders]; + [super goForwardOnceRecordersHaveCompleted:YES]; +} + +- (void)stepDidFinish { [super finish]; - - [self goForward]; + [super goForward]; } - (void)countDownTimerFired:(ORKActiveStepTimer *)timer finished:(BOOL)finished {