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

Range of Motion Tasks - added support for all device orientations #1358

Open
wants to merge 146 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
146 commits
Select commit Hold shift + click to select a range
0c11921
Merge pull request #2 from ResearchKit/master
davwillev Dec 9, 2018
2980f32
Fixed typo
davwillev Dec 9, 2018
53514bd
Added new spinal 'range of motion' tasks
davwillev Dec 9, 2018
98ca114
Added references to new spinal RoM active tasks
davwillev Dec 9, 2018
6d1e570
Added instructions for new spinal RoM active tasks
davwillev Dec 9, 2018
ccc582b
Added instructions for new spinal RoM active tasks
davwillev Dec 9, 2018
bcb570f
Added instructions for new spinal RoM active tasks
davwillev Dec 9, 2018
908ae8c
Added new spinal 'range of motion' tasks
davwillev Dec 9, 2018
d35c396
Added image assets for new spinal RoM active tasks
davwillev Dec 9, 2018
136aaf4
Adding new spinal RoM active tasks
davwillev Dec 9, 2018
4b34136
Update TaskListRow.swift
davwillev Dec 9, 2018
7235e9e
Merge pull request #3 from ResearchKit/master
davwillev Feb 21, 2019
c46c207
Merge pull request #4 from davwillev/master
davwillev Feb 21, 2019
3becf7b
Merge pull request #8 from ResearchKit/master
davwillev Mar 18, 2020
527f318
First commit for supporting all orientations
davwillev Mar 18, 2020
bc44d8c
Update towards capturing initial orientation
davwillev Mar 18, 2020
31ea97d
Update for all orientations
davwillev Mar 19, 2020
64c8643
Update variable type
davwillev Mar 19, 2020
86914fd
Tidy up comments
davwillev Mar 19, 2020
1b474c8
Update ORKRangeOfMotionResult.m
davwillev Mar 19, 2020
15dcc5e
Support for all orientations
davwillev Mar 19, 2020
cc97f7d
Support for all orientations
davwillev Mar 19, 2020
102b596
Add 'orientation' result
davwillev Mar 19, 2020
01bb68e
Add 'orientation' result to ROM task
davwillev Mar 19, 2020
3f61b74
Update Shoulder ROM task to support all orientations
davwillev Mar 19, 2020
30a34d5
Update ORKRangeOfMotionStepViewController.m
davwillev Mar 19, 2020
3d48eee
Remove declaration for _orientation
davwillev Mar 20, 2020
3ab02eb
Add declaration for _orientation
davwillev Mar 20, 2020
eaebe93
Override super class
davwillev Mar 20, 2020
7b44835
Remove comment
davwillev Mar 20, 2020
1228d34
Merge branch 'Back-RoM-tasks' into All_orientations
davwillev Mar 20, 2020
3f13ba2
Improve comment
davwillev Mar 21, 2020
9cb240a
Delete ORKForwardBendingRangeOfMotionStep.h
davwillev Mar 21, 2020
e1bca2c
Delete ORKForwardBendingRangeOfMotionStep.m
davwillev Mar 21, 2020
b220b28
Delete ORKForwardBendingRangeOfMotionStepViewController.h
davwillev Mar 21, 2020
fa8db46
Delete ORKForwardBendingRangeOfMotionStepViewController.m
davwillev Mar 21, 2020
2f2af72
Delete ORKNeckExtensionRangeOfMotionStep.h
davwillev Mar 21, 2020
8a7831a
Delete ORKNeckExtensionRangeOfMotionStep.m
davwillev Mar 21, 2020
06b3d55
Delete ORKNeckExtensionRangeOfMotionStepViewController.h
davwillev Mar 21, 2020
e3d04a2
Delete ORKNeckExtensionRangeOfMotionStepViewController.m
davwillev Mar 21, 2020
72a6be1
Delete ORKNeckFlexionRangeOfMotionStep.h
davwillev Mar 21, 2020
c126024
Delete ORKNeckFlexionRangeOfMotionStep.m
davwillev Mar 21, 2020
b0e622c
Delete ORKNeckFlexionRangeOfMotionStepViewController.h
davwillev Mar 21, 2020
4a850de
Delete ORKNeckFlexionRangeOfMotionStepViewController.m
davwillev Mar 21, 2020
8c0a916
Delete ORKNeckRotationRangeOfMotionStep.h
davwillev Mar 21, 2020
943cc6b
Delete ORKNeckRotationRangeOfMotionStep.m
davwillev Mar 21, 2020
666d91e
Delete ORKNeckRotationRangeOfMotionStepViewController.h
davwillev Mar 21, 2020
36f5d47
Delete ORKNeckRotationRangeOfMotionStepViewController.m
davwillev Mar 21, 2020
ff44e2c
Delete ORKNeckSideBendingRangeOfMotionStep.h
davwillev Mar 21, 2020
4ce0cf1
Delete ORKNeckSideBendingRangeOfMotionStep.m
davwillev Mar 21, 2020
d640299
Delete ORKNeckSideBendingRangeOfMotionStepViewController.h
davwillev Mar 21, 2020
fb44cca
Delete ORKNeckSideBendingRangeOfMotionStepViewController.m
davwillev Mar 21, 2020
d53f42e
Delete ORKSideBendingRangeOfMotionStep.h
davwillev Mar 21, 2020
19b6a8e
Delete ORKSideBendingRangeOfMotionStep.m
davwillev Mar 21, 2020
62f9262
Delete ORKSideBendingRangeOfMotionStepViewController.h
davwillev Mar 21, 2020
b30e5a6
Delete ORKSideBendingRangeOfMotionStepViewController.m
davwillev Mar 21, 2020
6943f63
Delete ORKTrunkRotationRangeOfMotionStep.h
davwillev Mar 21, 2020
00d0e55
Delete ORKTrunkRotationRangeOfMotionStep.m
davwillev Mar 21, 2020
9fffb6d
Delete ORKTrunkRotationRangeOfMotionStepViewController.h
davwillev Mar 21, 2020
2e31e80
Delete ORKTrunkRotationRangeOfMotionStepViewController.m
davwillev Mar 21, 2020
535c921
Delete Contents.json
davwillev Mar 21, 2020
d71bb60
Delete forward_bending_maximum@2x.png
davwillev Mar 21, 2020
9dab457
Delete forward_bending_maximum@2x~ipad.png
davwillev Mar 21, 2020
dc25c3d
Delete forward_bending_maximum@3x.png
davwillev Mar 21, 2020
5b592fc
Delete Contents.json
davwillev Mar 21, 2020
43cf54d
Delete forward_bending_maximum@2x.png
davwillev Mar 21, 2020
d36897f
Delete forward_bending_maximum@2x~ipad.png
davwillev Mar 21, 2020
58e91ae
Delete forward_bending_maximum@3x.png
davwillev Mar 21, 2020
8b8be44
Delete Contents.json
davwillev Mar 21, 2020
c83fb5f
Delete trunk_rotation_maximum@2x.png
davwillev Mar 21, 2020
2d488c3
Delete trunk_rotation_maximum@2x~ipad.png
davwillev Mar 21, 2020
afea95f
Delete trunk_rotation_maximum@3x.png
davwillev Mar 21, 2020
639cbcc
Delete Contents.json
davwillev Mar 21, 2020
dcfa000
Delete trunk_rotation_maximum@2x.png
davwillev Mar 21, 2020
99b04cd
Delete trunk_rotation_maximum@2x~ipad.png
davwillev Mar 21, 2020
b19d1f7
Delete trunk_rotation_maximum@3x.png
davwillev Mar 21, 2020
25cea93
Delete Contents.json
davwillev Mar 21, 2020
bd720eb
Delete trunk_rotation_start@2x.png
davwillev Mar 21, 2020
ec62df1
Delete trunk_rotation_start@2x~ipad.png
davwillev Mar 21, 2020
5030c00
Delete trunk_rotation_start@3x.png
davwillev Mar 21, 2020
028dd3b
Delete Contents.json
davwillev Mar 21, 2020
2965571
Delete trunk_rotation_start@2x.png
davwillev Mar 21, 2020
7901aca
Delete trunk_rotation_start@2x~ipad.png
davwillev Mar 21, 2020
c6ac91e
Delete trunk_rotation_start@3x.png
davwillev Mar 21, 2020
771f060
Delete Contents.json
davwillev Mar 21, 2020
fd8ac65
Delete neck_extension_maximum@2x.png
davwillev Mar 21, 2020
60a5fb9
Delete neck_extension_maximum@2x~ipad.png
davwillev Mar 21, 2020
4fd7ee7
Delete neck_extension_maximum@3x.png
davwillev Mar 21, 2020
b4f131d
Delete Contents.json
davwillev Mar 21, 2020
a552a28
Delete neck_extension_maximum@2x.png
davwillev Mar 21, 2020
471f38c
Delete neck_extension_maximum@2x~ipad.png
davwillev Mar 21, 2020
b4fc010
Delete neck_extension_maximum@3x.png
davwillev Mar 21, 2020
7f7d04a
Delete Contents.json
davwillev Mar 21, 2020
c79fd5c
Delete neck_rotation_start@2x.png
davwillev Mar 21, 2020
6bc9a3a
Delete neck_rotation_start@2x~ipad.png
davwillev Mar 21, 2020
550b9f4
Delete neck_rotation_start@3x.png
davwillev Mar 21, 2020
36e99a3
Delete Contents.json
davwillev Mar 21, 2020
2cd4f04
Delete neck_rotation_start@2x.png
davwillev Mar 21, 2020
8d20339
Delete neck_rotation_start@2x~ipad.png
davwillev Mar 21, 2020
77b923e
Delete neck_rotation_start@3x.png
davwillev Mar 21, 2020
40222be
Delete Contents.json
davwillev Mar 21, 2020
4816b15
Delete neck_flexion_maximum@2x.png
davwillev Mar 21, 2020
2526cda
Delete neck_flexion_maximum@2x~ipad.png
davwillev Mar 21, 2020
0482619
Delete neck_flexion_maximum@3x.png
davwillev Mar 21, 2020
8d5a8e4
Delete Contents.json
davwillev Mar 21, 2020
15cc52c
Delete neck_rotation_maximum@2x.png
davwillev Mar 21, 2020
d9baf0a
Delete neck_rotation_maximum@2x~ipad.png
davwillev Mar 21, 2020
ad44d74
Delete neck_rotation_maximum@3x.png
davwillev Mar 21, 2020
dbc15a5
Delete Contents.json
davwillev Mar 21, 2020
c699083
Delete neck_rotation_maximum@2x.png
davwillev Mar 21, 2020
3b3e2a3
Delete neck_rotation_maximum@2x~ipad.png
davwillev Mar 21, 2020
a232414
Delete neck_rotation_maximum@3x.png
davwillev Mar 21, 2020
8e28fdb
Delete Contents.json
davwillev Mar 21, 2020
c16d3b0
Delete neck_rotation_start@2x.png
davwillev Mar 21, 2020
0b442fc
Delete neck_rotation_start@2x~ipad.png
davwillev Mar 21, 2020
e744525
Delete neck_rotation_start@3x.png
davwillev Mar 21, 2020
75d4e8f
Delete Contents.json
davwillev Mar 21, 2020
4a70534
Delete neck_rotation_start@2x.png
davwillev Mar 21, 2020
55833ca
Delete neck_rotation_start@2x~ipad.png
davwillev Mar 21, 2020
ab35717
Delete neck_rotation_start@3x.png
davwillev Mar 21, 2020
eac7d6a
Delete Contents.json
davwillev Mar 21, 2020
7e4d162
Delete neck_side_bending_maximum@2x.png
davwillev Mar 21, 2020
744982b
Delete neck_side_bending_maximum@2x~ipad.png
davwillev Mar 21, 2020
dc7a731
Delete neck_side_bending_maximum@3x.png
davwillev Mar 21, 2020
abb1981
Delete Contents.json
davwillev Mar 21, 2020
a33ab19
Delete neck_side_bending_maximum@2x.png
davwillev Mar 21, 2020
b498440
Delete neck_side_bending_maximum@2x~ipad.png
davwillev Mar 21, 2020
b323d78
Delete neck_side_bending_maximum@3x.png
davwillev Mar 21, 2020
eddbb21
Delete Contents.json
davwillev Mar 21, 2020
ba39950
Delete neck_rotation_start@2x.png
davwillev Mar 21, 2020
f85cd55
Delete neck_rotation_start@2x~ipad.png
davwillev Mar 21, 2020
7ce9235
Delete neck_rotation_start@3x.png
davwillev Mar 21, 2020
19208a5
Delete Contents.json
davwillev Mar 21, 2020
b258452
Delete neck_rotation_start@2x.png
davwillev Mar 21, 2020
b20380f
Delete neck_rotation_start@2x~ipad.png
davwillev Mar 21, 2020
3c0172c
Delete neck_rotation_start@3x.png
davwillev Mar 21, 2020
82c0e1b
Revert "Delete neck_rotation_start@3x.png"
davwillev Mar 21, 2020
6a7477b
Revert "Revert "Delete neck_rotation_start@3x.png""
davwillev Mar 21, 2020
3b10fff
Revert "Revert "Revert "Delete neck_rotation_start@3x.png"""
davwillev Mar 21, 2020
96c644c
Revert "Revert "Revert "Revert "Delete neck_rotation_start@3x.png""""
davwillev Mar 21, 2020
90c1f6b
Revert "Revert "Revert "Revert "Revert "Delete neck_rotation_start@3x…
davwillev Mar 21, 2020
1a5a34d
Revert "Revert "Revert "Revert "Revert "Revert "Delete neck_rotation_…
davwillev Mar 21, 2020
4949574
Revert "Revert "Revert "Revert "Revert "Revert "Revert "Delete neck_r…
davwillev Mar 21, 2020
b4707b4
Revert "Revert "Revert "Revert "Revert "Revert "Revert "Revert "Delet…
davwillev Mar 21, 2020
35ecebf
Revert "Merge branch 'Back-RoM-tasks' into All_orientations"
davwillev Mar 21, 2020
2b95c99
Correct date in copyright notice
davwillev Jul 23, 2020
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
5 changes: 5 additions & 0 deletions ResearchKit/ActiveTasks/ORKRangeOfMotionResult.h
Expand Up @@ -42,6 +42,11 @@ NS_ASSUME_NONNULL_BEGIN
ORK_CLASS_AVAILABLE
@interface ORKRangeOfMotionResult : ORKResult

/**
The physical orientation of the device at the start position (the commencement of recording).
*/
@property (nonatomic, assign) NSInteger orientation;

/**
The angle (degrees) from the device reference position at the start position.
*/
Expand Down
6 changes: 5 additions & 1 deletion ResearchKit/ActiveTasks/ORKRangeOfMotionResult.m
Expand Up @@ -39,6 +39,7 @@ @implementation ORKRangeOfMotionResult

- (void)encodeWithCoder:(NSCoder *)aCoder {
[super encodeWithCoder:aCoder];
ORK_ENCODE_INTEGER(aCoder, orientation);
ORK_ENCODE_DOUBLE(aCoder, start);
ORK_ENCODE_DOUBLE(aCoder, finish);
ORK_ENCODE_DOUBLE(aCoder, minimum);
Expand All @@ -49,6 +50,7 @@ - (void)encodeWithCoder:(NSCoder *)aCoder {
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
ORK_DECODE_INTEGER(aDecoder, orientation);
ORK_DECODE_DOUBLE(aDecoder, start);
ORK_DECODE_DOUBLE(aDecoder, finish);
ORK_DECODE_DOUBLE(aDecoder, minimum);
Expand All @@ -66,6 +68,7 @@ - (BOOL)isEqual:(id)object {
BOOL isParentSame = [super isEqual:object];
__typeof(self) castObject = object;
return isParentSame &&
self.orientation == castObject.orientation &&
self.start == castObject.start &&
self.finish == castObject.finish &&
self.minimum == castObject.minimum &&
Expand All @@ -79,6 +82,7 @@ - (NSUInteger)hash {

- (instancetype)copyWithZone:(NSZone *)zone {
ORKRangeOfMotionResult *result = [super copyWithZone:zone];
result.orientation = self.orientation;
result.start = self.start;
result.finish = self.finish;
result.minimum = self.minimum;
Expand All @@ -88,7 +92,7 @@ - (instancetype)copyWithZone:(NSZone *)zone {
}

- (NSString *)descriptionWithNumberOfPaddingSpaces:(NSUInteger)numberOfPaddingSpaces {
return [NSString stringWithFormat:@"<%@: start: %f; finish: %f; minimum: %f; maximum: %f; range: %f>", self.class.description, self.start, self.finish, self.minimum, self.maximum, self.range];
return [NSString stringWithFormat:@"<%@: orientation: %li; start: %f; finish: %f; minimum: %f; maximum: %f; range: %f>", self.class.description, self.orientation, self.start, self.finish, self.minimum, self.maximum, self.range];
}

@end
Expand Up @@ -41,6 +41,7 @@ NS_ASSUME_NONNULL_BEGIN
*/
ORK_CLASS_AVAILABLE
@interface ORKRangeOfMotionStepViewController : ORKActiveStepViewController {
UIDeviceOrientation _orientation;
double _startAngle;
double _newAngle;
double _minAngle;
Expand Down
128 changes: 100 additions & 28 deletions ResearchKit/ActiveTasks/ORKRangeOfMotionStepViewController.m
@@ -1,5 +1,6 @@
/*
Copyright (c) 2016, Darren Levy. All rights reserved.
Copyright (c) 2020, 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 @@ -124,7 +125,6 @@ @interface ORKRangeOfMotionStepViewController () <ORKDeviceMotionRecorderDelegat
ORKRangeOfMotionContentView *_contentView;
UITapGestureRecognizer *_gestureRecognizer;
CMAttitude *_referenceAttitude;
UIInterfaceOrientation _orientation;
}

@end
Expand All @@ -134,13 +134,29 @@ @implementation ORKRangeOfMotionStepViewController

- (void)viewDidLoad {
[super viewDidLoad];

_contentView = [ORKRangeOfMotionContentView new];
_contentView.translatesAutoresizingMaskIntoConstraints = NO;
self.activeStepView.activeCustomView = _contentView;
_gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)];
[self.activeStepView addGestureRecognizer:_gestureRecognizer];

// Initiate orientation notifications
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
_orientation = [[UIDevice currentDevice] orientation]; // captures the initial device orientation
}

- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];

// Ends orientation notifications
[[NSNotificationCenter defaultCenter] removeObserver:self];
if ([[UIDevice currentDevice] isGeneratingDeviceOrientationNotifications]) {
[[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
}
}
//This function records the angle of the device when the screen is tapped

//Records the angle of the device when the screen is tapped
- (void)handleTap:(UIGestureRecognizer *)sender {
[self calculateAndSetAngles];
[self finish];
Expand All @@ -149,56 +165,74 @@ - (void)handleTap:(UIGestureRecognizer *)sender {
- (void)calculateAndSetAngles {
_startAngle = ([self getDeviceAngleInDegreesFromAttitude:_referenceAttitude]);

//This function calculates maximum and minimum angles recorded by the device
// Calculate maximum and minimum angles recorded by the device
if (_newAngle > _maxAngle) {
_maxAngle = _newAngle;
}
if (_minAngle == 0.0 || _newAngle < _minAngle) {
if (_newAngle < _minAngle) {
_minAngle = _newAngle;
}
}


#pragma mark - ORKDeviceMotionRecorderDelegate

- (void)deviceMotionRecorderDidUpdateWithMotion:(CMDeviceMotion *)motion {
if (!_referenceAttitude) {
_referenceAttitude = motion.attitude;
}
CMAttitude *currentAttitude = [motion.attitude copy];

[currentAttitude multiplyByInverseOfAttitude:_referenceAttitude];

double angle = [self getDeviceAngleInDegreesFromAttitude:currentAttitude];

//This function shifts the range of angles reported by the device from +/-180 degrees to -90 to +270 degrees, which should be sufficient to cover all ahievable knee and shoulder ranges of motion
BOOL shiftAngleRange = angle > 90 && angle <= 180;
if (shiftAngleRange) {
_newAngle = fabs(angle) - 360;
} else {
_newAngle = angle;
//These shift the range of angles reported by the device from +/-180 degrees to -90 to +270 degrees, which should be sufficient to cover all ahievable knee and shoulder ranges of motion
if (UIDeviceOrientationLandscapeLeft == _orientation) {
BOOL shiftAngleRange = angle < -90 && angle >= -180;
if (shiftAngleRange) {
_newAngle = 360 - fabs(angle);
} else {
_newAngle = angle;
}
} else if (UIDeviceOrientationPortrait == _orientation) {
BOOL shiftAngleRange = angle > 90 && angle <= 180;
if (shiftAngleRange) {
_newAngle = fabs(angle) - 360;
} else {
_newAngle = angle;
}
} else if (UIDeviceOrientationLandscapeRight == _orientation) {
BOOL shiftAngleRange = angle > 90 && angle <= 180;
if (shiftAngleRange) {
_newAngle = fabs(angle) - 360;
} else {
_newAngle = angle;
}
} else if (UIDeviceOrientationPortraitUpsideDown == _orientation) {
BOOL shiftAngleRange = angle < -90 && angle >= -180;
if (shiftAngleRange) {
_newAngle = 360 - fabs(angle);
} else {
_newAngle = angle;
}
}

[self calculateAndSetAngles];
}

/*
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
orientations, so we use the attitude's quaternion to calculate the
angle.
When the device is in Portrait mode, we need to get the attitude's pitch to determine the device's
angle; whereas, when the device is in Landscape, we need the attitude's roll. We can use the
quaternion that represents the device's attitude to calculate the angle around each axis.
*/
- (double)getDeviceAngleInDegreesFromAttitude:(CMAttitude *)attitude {
if (!_orientation) {
_orientation = [UIApplication sharedApplication].statusBarOrientation;
}
double angle;
if (UIInterfaceOrientationIsLandscape(_orientation)) {
double angle = 0.0;
if (UIDeviceOrientationIsLandscape(_orientation)) {
double x = attitude.quaternion.x;
double w = attitude.quaternion.w;
double y = attitude.quaternion.y;
double z = attitude.quaternion.z;
angle = radiansToDegrees(allOrientationsForRoll(x, w, y, z));
} else {
} else if (UIDeviceOrientationIsPortrait(_orientation)) {
double x = attitude.quaternion.x;
double w = attitude.quaternion.w;
double y = attitude.quaternion.y;
Expand All @@ -216,13 +250,51 @@ - (ORKResult *)result {

ORKRangeOfMotionResult *result = [[ORKRangeOfMotionResult alloc] initWithIdentifier:self.step.identifier];

result.start = 90.0 - _startAngle;
result.finish = result.start - _newAngle;
//Because the task uses pitch in the direction opposite to the original CoreMotion device axes (i.e. right hand rule), maximum and minimum angles are reported the 'wrong' way around for the knee and shoulder tasks
result.minimum = result.start - _maxAngle;
result.maximum = result.start - _minAngle;
result.range = fabs(result.maximum - result.minimum);
int ORIENTATION_UNSPECIFIED = -1;
int ORIENTATION_LANDSCAPE_LEFT = 0; // equivalent to LANDSCAPE in Android
int ORIENTATION_PORTRAIT = 1;
int ORIENTATION_LANDSCAPE_RIGHT = 2; // equivalent to REVERSE_LANDSCAPE in Android
int ORIENTATION_PORTRAIT_UPSIDE_DOWN = 3; // equivalent to REVERSE_PORTRAIT in Android

if (UIDeviceOrientationLandscapeLeft == _orientation) {
result.orientation = ORIENTATION_LANDSCAPE_LEFT;
result.start = 90.0 + _startAngle;
result.finish = result.start + _newAngle;
result.minimum = result.start + _minAngle;
result.maximum = result.start + _maxAngle;
result.range = fabs(result.maximum - result.minimum);
} else if (UIDeviceOrientationPortrait == _orientation) {
result.orientation = ORIENTATION_PORTRAIT;
result.start = 90.0 - _startAngle;
result.finish = result.start - _newAngle;
// In Portrait device orientation, the task uses pitch in the direction opposite to the original CoreMotion device axes (i.e. right hand rule). Therefore, maximum and minimum angles are reported the 'wrong' way around for the knee and shoulder tasks.
result.minimum = result.start - _maxAngle;
result.maximum = result.start - _minAngle;
result.range = fabs(result.maximum - result.minimum);
} else if (UIDeviceOrientationLandscapeRight == _orientation) {
result.orientation = ORIENTATION_LANDSCAPE_RIGHT;
result.start = 90.0 - _startAngle;
result.finish = result.start - _newAngle;
// In Landscape Right device orientation, the task uses roll in the direction opposite to the original CoreMotion device axes.
result.minimum = result.start - _maxAngle;
result.maximum = result.start - _minAngle;
result.range = fabs(result.maximum - result.minimum);
} else if (UIDeviceOrientationPortraitUpsideDown == _orientation) {
result.orientation = ORIENTATION_PORTRAIT_UPSIDE_DOWN;
result.start = -90 - _startAngle;
result.finish = result.start + _newAngle;
result.minimum = result.start + _minAngle;
result.maximum = result.start + _maxAngle;
result.range = fabs(result.maximum - result.minimum);
} else if (!UIDeviceOrientationIsValidInterfaceOrientation(_orientation)) {
result.orientation = ORIENTATION_UNSPECIFIED;
result.start = NAN;
result.finish = NAN;
result.minimum = NAN;
result.maximum = NAN;
result.range = NAN;
}

stepResult.results = [self.addedResults arrayByAddingObject:result] ? : @[result];

return stepResult;
Expand Down
@@ -1,5 +1,6 @@
/*
Copyright (c) 2016, Darren Levy. All rights reserved.
Copyright (c) 2020, 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 @@ -35,21 +36,61 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
#import "ORKStepViewController_Internal.h"


@implementation ORKShoulderRangeOfMotionStepViewController
@implementation ORKShoulderRangeOfMotionStepViewController: ORKRangeOfMotionStepViewController

#pragma mark - ORKActiveTaskViewController

- (ORKResult *)result {
ORKStepResult *stepResult = [super result];

ORKRangeOfMotionResult *result = [[ORKRangeOfMotionResult alloc] initWithIdentifier:self.step.identifier];
result.start = 90.0 - _startAngle;
result.finish = result.start - _newAngle;
//Because the task uses pitch in the direction opposite to the original CoreMotion device axes (i.e. right hand rule), maximum and minimum angles are reported the 'wrong' way around for the knee and shoulder tasks
result.minimum = result.start - _maxAngle;
result.maximum = result.start - _minAngle;
result.range = fabs(result.maximum - result.minimum);


int ORIENTATION_UNSPECIFIED = -1;
int ORIENTATION_LANDSCAPE_LEFT = 0; // equivalent to LANDSCAPE in Android
int ORIENTATION_PORTRAIT = 1;
int ORIENTATION_LANDSCAPE_RIGHT = 2; // equivalent to REVERSE_LANDSCAPE in Android
int ORIENTATION_PORTRAIT_UPSIDE_DOWN = 3; // equivalent to REVERSE_PORTRAIT in Android

if (UIDeviceOrientationLandscapeLeft == _orientation) {
result.orientation = ORIENTATION_LANDSCAPE_LEFT;
result.start = 90.0 + _startAngle;
result.finish = result.start + _newAngle;
result.minimum = result.start + _minAngle;
result.maximum = result.start + _maxAngle;
result.range = fabs(result.maximum - result.minimum);
} else if (UIDeviceOrientationPortrait == _orientation) {
result.orientation = ORIENTATION_PORTRAIT;
result.start = 90.0 - _startAngle;
result.finish = result.start - _newAngle;
// In Portrait device orientation, the task uses pitch in the direction opposite to the original CoreMotion device axes (i.e. right hand rule). Therefore, maximum and minimum angles are reported the 'wrong' way around for the knee and shoulder tasks.
result.minimum = result.start - _maxAngle;
result.maximum = result.start - _minAngle;
result.range = fabs(result.maximum - result.minimum);
} else if (UIDeviceOrientationLandscapeRight == _orientation) {
result.orientation = ORIENTATION_LANDSCAPE_RIGHT;
result.start = 90.0 - _startAngle;
result.finish = result.start - _newAngle;
// In Landscape Right device orientation, the task uses roll in the direction opposite to the original CoreMotion device axes.
result.minimum = result.start - _maxAngle;
result.maximum = result.start - _minAngle;
result.range = fabs(result.maximum - result.minimum);
} else if (UIDeviceOrientationPortraitUpsideDown == _orientation) {
result.orientation = ORIENTATION_PORTRAIT_UPSIDE_DOWN;
result.start = -90 - _startAngle;
result.finish = result.start + _newAngle;
result.minimum = result.start + _minAngle;
result.maximum = result.start + _maxAngle;
result.range = fabs(result.maximum - result.minimum);
//} else if (UIDeviceOrientationFaceUp == _orientation || UIDeviceOrientationFaceDown == _orientation) {
} else if (!UIDeviceOrientationIsValidInterfaceOrientation(_orientation)) {
result.orientation = ORIENTATION_UNSPECIFIED;
result.start = NAN;
result.finish = NAN;
result.minimum = NAN;
result.maximum = NAN;
result.range = NAN;
}

stepResult.results = [self.addedResults arrayByAddingObject:result] ? : @[result];

return stepResult;
Expand Down
Expand Up @@ -200,7 +200,7 @@ enum ResultRow {
storyboard.
*/
enum TableViewCellIdentifier: String {
case `default` = "Default"
case `default` = "Default"
case noResultSet = "NoResultSet"
case noChildResults = "NoChildResults"
case textImage = "TextImage"
Expand Down Expand Up @@ -958,6 +958,7 @@ class RangeOfMotionResultTableViewProvider: ResultTableViewProvider {
let rangeOfMotionResult = result as! ORKRangeOfMotionResult
let rows = super.resultRowsForSection(section)
return rows + [
ResultRow(text: "orientation", detail: rangeOfMotionResult.orientation),
ResultRow(text: "start", detail: rangeOfMotionResult.start),
ResultRow(text: "finish", detail: rangeOfMotionResult.finish),
ResultRow(text: "minimum", detail: rangeOfMotionResult.minimum),
Expand Down