Skip to content

Commit e42bb76

Browse files
committed
Merge pull request ResearchKit#126 from dephillipsmichael/feature/FPHS-583
feature/FPHS-583
2 parents 1979bd5 + f57a52e commit e42bb76

File tree

4 files changed

+218
-15
lines changed

4 files changed

+218
-15
lines changed

APCAppCore/APCAppCore/DataSubstrate/Model/APCUser.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@ typedef NS_ENUM(NSInteger, APCUserConsentSharingScope) {
110110
@property (nonatomic) HKBiologicalSex biologicalSex;
111111
@property (nonatomic) HKBloodType bloodType;
112112

113+
// @return YES if birthdate property comes from healthkit, NO if comes from core data
114+
- (BOOL) hasBirthDateInHealthKit;
115+
- (BOOL) hasBiologicalSexInHealthKit;
113116

114117
@property (nonatomic, strong, nullable) HKQuantity * height;
115118
@property (nonatomic, strong, nullable) HKQuantity * weight;

APCAppCore/APCAppCore/DataSubstrate/Model/APCUser.m

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,14 @@ - (void)setBirthDate:(NSDate *)birthDate
528528
[self updateStoredProperty:kBirthDatePropertyName withValue:birthDate];
529529
}
530530

531+
- (BOOL) hasBirthDateInHealthKit
532+
{
533+
NSError *error;
534+
NSDate *dateOfBirth = [self.healthStore dateOfBirthWithError:&error];
535+
APCLogError2 (error);
536+
return dateOfBirth != nil;
537+
}
538+
531539
- (HKBiologicalSex)biologicalSex
532540
{
533541
NSError *error;
@@ -542,6 +550,14 @@ - (void)setBiologicalSex:(HKBiologicalSex)biologicalSex
542550
[self updateStoredProperty:kBiologicalSexPropertyName withValue:@(biologicalSex)];
543551
}
544552

553+
- (BOOL) hasBiologicalSexInHealthKit
554+
{
555+
NSError *error;
556+
HKBiologicalSexObject * sexObject = [self.healthStore biologicalSexWithError:&error];
557+
APCLogError2 (error);
558+
return sexObject.biologicalSex != HKBiologicalSexNotSet;
559+
}
560+
545561
- (HKBloodType)bloodType
546562
{
547563
NSError *error;

APCAppCore/APCAppCore/UI/TabBarControllers/Profile/APCProfileViewController.h

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737

3838
@protocol APCProfileViewControllerDelegate;
3939

40-
@interface APCProfileViewController : APCUserInfoViewController <APCPickerTableViewCellDelegate, APCTextFieldTableViewCellDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate,UITextFieldDelegate, APCSwitchTableViewCellDelegate>
40+
@interface APCProfileViewController : APCUserInfoViewController <APCPickerTableViewCellDelegate, APCTextFieldTableViewCellDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate,UITextFieldDelegate, APCSwitchTableViewCellDelegate, APCSegmentedTableViewCellDelegate>
4141

4242
@property (nonatomic, strong) APCUser *user;
4343

@@ -73,6 +73,18 @@
7373

7474
@property (nonatomic, strong) UIImage *profileImage;
7575

76+
/*
77+
* If set to YES, birthdate will be a Date Picker Cell, and can be edited when isEditing is also set to YES
78+
* If set to NO, birthdate will be a Default Cell, with its value locked no matter what
79+
*/
80+
@property (nonatomic) BOOL canEditBirthDate;
81+
82+
/*
83+
* If set to YES, biological sex will be a Segment Cell, and can be edited when isEditing is also set to YES
84+
* If set to NO, biological sex will be a Default Cell, with its value locked no matter what
85+
*/
86+
@property (nonatomic) BOOL canEditBiologicalSex;
87+
7688

7789
- (IBAction)leaveStudy:(id)sender;
7890

@@ -111,4 +123,13 @@
111123
- (void)hasStartedEditing;
112124

113125
- (void)hasFinishedEditing;
126+
127+
/*
128+
* If canEditBirthDate or canEditBiologicalSex are set to YES,
129+
* This will be called if the user tries to edit either of them when we are
130+
* loading them from HealthKit, but writing to HealthKit is not valid
131+
* Dev should launch a dialog when this is called saying to change in the Health App
132+
*/
133+
- (void) editingFailedForHealthKitType:(NSString*) healthKitIdentifier;
134+
114135
@end

APCAppCore/APCAppCore/UI/TabBarControllers/Profile/APCProfileViewController.m

Lines changed: 177 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@
8282
static NSString * const kAPCBasicTableViewCellIdentifier = @"APCBasicTableViewCell";
8383
static NSString * const kAPCRightDetailTableViewCellIdentifier = @"APCRightDetailTableViewCell";
8484

85+
@interface APCUserInfoViewController()
86+
- (void) setEditing:(BOOL) isEditing;
87+
@end
88+
8589
@interface APCProfileViewController () <ORKTaskViewControllerDelegate>
8690

8791
@property (weak, nonatomic) IBOutlet UILabel *versionLabel;
@@ -409,7 +413,7 @@ - (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(
409413
APCSegmentedTableViewCell *segmentedCell = (APCSegmentedTableViewCell *)cell;
410414
segmentedCell.delegate = self;
411415
segmentedCell.selectedSegmentIndex = segmentPickerField.selectedIndex;
412-
segmentedCell.userInteractionEnabled = segmentPickerField.editable;
416+
segmentedCell.userInteractionEnabled = segmentPickerField.editable && self.isEditing;
413417

414418
}
415419
else if ([field isKindOfClass:[APCTableViewSwitchItem class]]) {
@@ -436,6 +440,14 @@ - (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(
436440
return cell;
437441
}
438442

443+
- (void) setEditing:(BOOL) isEditing
444+
{
445+
[super setEditing:isEditing];
446+
447+
// Reload the table so that all the field items and cells are in the correct state for new "isEditing" property
448+
[self.tableView reloadData];
449+
}
450+
439451
#pragma mark - Prepare Content
440452

441453
- (NSArray *)prepareContent
@@ -452,15 +464,38 @@ - (NSArray *)prepareContent
452464
APCUserInfoItemType itemType = type.integerValue;
453465

454466
switch (itemType) {
467+
455468
case kAPCUserInfoItemTypeBiologicalSex:
456469
{
457-
APCTableViewItem *field = [APCTableViewItem new];
458-
field.caption = NSLocalizedStringWithDefaultValue(@"Sex", @"APCAppCore", APCBundle(), @"Sex", @"");
459-
field.reuseIdentifier = kAPCDefaultTableViewCellIdentifier;
460-
field.editable = NO;
461-
field.textAlignnment = NSTextAlignmentRight;
462-
field.detailText = [APCUser stringValueFromSexType:self.user.biologicalSex];
463-
field.selectionStyle = self.isEditing ? UITableViewCellSelectionStyleGray : UITableViewCellSelectionStyleNone;
470+
APCTableViewItem *field = nil;
471+
472+
// If we can edit biological sex, change the UI to be a segment control
473+
if (self.canEditBiologicalSex)
474+
{
475+
APCTableViewSegmentItem *segmentField = [APCTableViewSegmentItem new];
476+
segmentField.style = UITableViewCellStyleValue1;
477+
segmentField.segments = [APCUser sexTypesInStringValue];
478+
segmentField.reuseIdentifier = kAPCSegmentedTableViewCellIdentifier;
479+
480+
if (self.user.biologicalSex) {
481+
segmentField.selectedIndex = [APCUser stringIndexFromSexType:self.user.biologicalSex];
482+
segmentField.editable = self.canEditBiologicalSex;
483+
} else {
484+
segmentField.editable = YES; // make it always editable, since user hasnt provided a valid value yet
485+
}
486+
segmentField.selectionStyle = self.isEditing && field.editable ? UITableViewCellSelectionStyleGray : UITableViewCellSelectionStyleNone;
487+
field = segmentField;
488+
}
489+
else // if we cant edit sex, show a simple default table view cell
490+
{
491+
field = [APCTableViewItem new];
492+
field.caption = NSLocalizedStringWithDefaultValue(@"Sex", @"APCAppCore", APCBundle(), @"Sex", @"");
493+
field.reuseIdentifier = kAPCDefaultTableViewCellIdentifier;
494+
field.editable = NO;
495+
field.textAlignnment = NSTextAlignmentRight;
496+
field.detailText = [APCUser stringValueFromSexType:self.user.biologicalSex];
497+
field.selectionStyle = UITableViewCellSelectionStyleNone;
498+
}
464499

465500
APCTableViewRow *row = [APCTableViewRow new];
466501
row.item = field;
@@ -471,13 +506,45 @@ - (NSArray *)prepareContent
471506

472507
case kAPCUserInfoItemTypeDateOfBirth:
473508
{
474-
APCTableViewItem *field = [APCTableViewItem new];
509+
APCTableViewItem *field = nil;
510+
511+
if (self.canEditBirthDate) // If we can edit birthdate, change the UI to be a date picker
512+
{
513+
APCTableViewDatePickerItem *dateField = [APCTableViewDatePickerItem new];
514+
dateField.datePickerMode = UIDatePickerModeDate;
515+
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier: NSCalendarIdentifierGregorian];
516+
NSDate *currentDate = [[NSDate date] startOfDay];
517+
NSDateComponents * comps = [[NSDateComponents alloc] init];
518+
[comps setYear: -18];
519+
NSDate *maxDate = [gregorian dateByAddingComponents: comps toDate: currentDate options: 0];
520+
dateField.maximumDate = maxDate;
521+
522+
if (self.user.birthDate) {
523+
dateField.date = self.user.birthDate;
524+
dateField.detailText = [dateField.date toStringWithFormat:dateField.dateFormat];
525+
dateField.editable = self.canEditBirthDate;
526+
} else {
527+
dateField.editable = YES; // make it always editable, since user hasnt provided a valid value yet
528+
dateField.date = nil;
529+
dateField.detailText = @"";
530+
}
531+
field = dateField;
532+
}
533+
else // if we cant edit birthdate, show a simple default table view cell
534+
{
535+
field = [APCTableViewItem new];
536+
field.editable = NO;
537+
field.textAlignnment = NSTextAlignmentRight;
538+
field.detailText = [self.user.birthDate toStringWithFormat:NSDateDefaultDateFormat];
539+
field.selectionStyle = self.isEditing ? UITableViewCellSelectionStyleGray : UITableViewCellSelectionStyleNone;
540+
}
541+
475542
field.caption = NSLocalizedStringWithDefaultValue(@"Birthdate", @"APCAppCore", APCBundle(), @"Birthdate", @"");
543+
544+
field.style = UITableViewCellStyleValue1;
476545
field.reuseIdentifier = kAPCDefaultTableViewCellIdentifier;
477-
field.editable = NO;
478-
field.textAlignnment = NSTextAlignmentRight;
479-
field.detailText = [self.user.birthDate toStringWithFormat:NSDateDefaultDateFormat];
480-
field.selectionStyle = self.isEditing ? UITableViewCellSelectionStyleGray : UITableViewCellSelectionStyleNone;
546+
field.textAlignnment = NSTextAlignmentRight;
547+
field.selectionStyle = self.isEditing && field.editable ? UITableViewCellSelectionStyleGray : UITableViewCellSelectionStyleNone;
481548

482549
APCTableViewRow *row = [APCTableViewRow new];
483550
row.item = field;
@@ -1141,7 +1208,36 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath
11411208
}
11421209
}
11431210
break;
1144-
1211+
1212+
case kAPCUserInfoItemTypeDateOfBirth:
1213+
{
1214+
if (self.canEditBirthDate)
1215+
{
1216+
APCTableViewItem *field = [self itemForIndexPath:indexPath];
1217+
// Check for if we grabbed this value from HealthKit, because if we did, it can't be edited from our app
1218+
if (![self isEditingAllowedForHealthKitProperty:HKCharacteristicTypeIdentifierDateOfBirth])
1219+
{
1220+
[self showEditingNotAllowedAlertForHealthKitProperty:HKCharacteristicTypeIdentifierDateOfBirth];
1221+
[tableView deselectRowAtIndexPath:indexPath animated:YES];
1222+
}
1223+
// only allow to be selected if we are in edit mode
1224+
else if (self.isEditing && field.isEditable)
1225+
{
1226+
[super tableView:tableView didSelectRowAtIndexPath:indexPath];
1227+
}
1228+
// if we cant edit it, simply deselect the row
1229+
else
1230+
{
1231+
[tableView deselectRowAtIndexPath:indexPath animated:YES];
1232+
}
1233+
}
1234+
else
1235+
{
1236+
[super tableView:tableView didSelectRowAtIndexPath:indexPath];
1237+
}
1238+
}
1239+
break;
1240+
11451241
default:{
11461242
[super tableView:tableView didSelectRowAtIndexPath:indexPath];
11471243
}
@@ -1233,6 +1329,59 @@ - (void)pickerTableViewCell:(APCPickerTableViewCell *)cell pickerViewDidSelectIn
12331329
}
12341330
}
12351331

1332+
#pragma mark APCSegmentedTableViewCellDelegate methods
1333+
1334+
- (void)segmentedTableViewCell:(APCSegmentedTableViewCell *) __unused cell
1335+
didSelectSegmentAtIndex:(NSInteger) __unused index
1336+
{
1337+
[super segmentedTableViewCell:cell didSelectSegmentAtIndex:index];
1338+
1339+
if (self.canEditBiologicalSex &&
1340+
![self isEditingAllowedForHealthKitProperty:HKCharacteristicTypeIdentifierBiologicalSex])
1341+
{
1342+
[self showEditingNotAllowedAlertForHealthKitProperty:HKCharacteristicTypeIdentifierBiologicalSex];
1343+
// Revert back to whatever the index was originally
1344+
cell.selectedSegmentIndex = [APCUser stringIndexFromSexType:self.user.biologicalSex];
1345+
}
1346+
}
1347+
1348+
- (void) showEditingNotAllowedAlertForHealthKitProperty:(NSString*)healthKitPropertyIdentifier
1349+
{
1350+
if ([self.delegate respondsToSelector:@selector(editingFailedForHealthKitType:)])
1351+
{
1352+
[self.delegate editingFailedForHealthKitType:healthKitPropertyIdentifier];
1353+
}
1354+
else // Since there is no delegate, show our own alert view
1355+
{
1356+
NSString* title = NSLocalizedStringWithDefaultValue(@"Edit in Health App", @"APCAppCore", APCBundle(), @"Edit in Health App", @"");
1357+
NSString* msg = NSLocalizedStringWithDefaultValue(@"Since we are accessing this information through Health Kit, you can only change it through your device's Health App", @"APCAppCore", APCBundle(), @"Since we are accessing this information through Health Kit, you can only change it through your device's Health App", @"");
1358+
UIAlertController *alertView = [UIAlertController alertControllerWithTitle:title
1359+
message:msg
1360+
preferredStyle:UIAlertControllerStyleAlert];
1361+
1362+
UIAlertAction* okAction = [UIAlertAction actionWithTitle:NSLocalizedStringWithDefaultValue(@"Okay", @"APCAppCore", APCBundle(), @"Okay", @"") style:UIAlertActionStyleDefault
1363+
handler:^(UIAlertAction * __unused action) {}];
1364+
1365+
[alertView addAction:okAction];
1366+
[self presentViewController:alertView animated:YES completion:nil];
1367+
}
1368+
}
1369+
1370+
- (BOOL) isEditingAllowedForHealthKitProperty:(NSString*) healthKitPropertyIdentifier
1371+
{
1372+
if (healthKitPropertyIdentifier == HKCharacteristicTypeIdentifierBiologicalSex &&
1373+
[self.user hasBiologicalSexInHealthKit])
1374+
{
1375+
return NO;
1376+
}
1377+
else if (healthKitPropertyIdentifier == HKCharacteristicTypeIdentifierDateOfBirth &&
1378+
[self.user hasBirthDateInHealthKit])
1379+
{
1380+
return NO;
1381+
}
1382+
return YES;
1383+
}
1384+
12361385
#pragma mark - UIImagePickerControllerDelegate
12371386

12381387
- (void) imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
@@ -1372,6 +1521,20 @@ - (void)loadProfileValuesInModel
13721521

13731522
case kAPCSettingsItemTypeSharingOptions:
13741523
break;
1524+
1525+
case kAPCUserInfoItemTypeDateOfBirth:
1526+
if (self.canEditBirthDate) // otherwise default item will be used
1527+
{
1528+
self.user.birthDate = [(APCTableViewDatePickerItem *)item date];
1529+
}
1530+
break;
1531+
1532+
case kAPCUserInfoItemTypeBiologicalSex:
1533+
if (self.canEditBiologicalSex) // otherwise default item will be used
1534+
{
1535+
self.user.biologicalSex = [APCUser sexTypeForIndex:((APCTableViewSegmentItem *)item).selectedIndex];
1536+
}
1537+
break;
13751538

13761539
default:
13771540
break;

0 commit comments

Comments
 (0)