/ SharedWebCredentialViewService / SWCViewController.m
SWCViewController.m
1 // 2 // SWCViewController.m 3 // SharedWebCredentialViewService 4 // 5 // Copyright (c) 2014 Apple Inc. All Rights Reserved. 6 // 7 8 #import <Foundation/NSXPCConnection.h> 9 #import "SWCViewController.h" 10 #import <UIKit/UIViewController_Private.h> 11 #import <UIKit/UIFont_Private.h> 12 #import <UIKit/UIImage_SymbolImagePrivate.h> 13 #import <UIKit/UIAlertController_Private.h> 14 #import <UIKit/UITableViewCell_Private.h> 15 16 #include <bsm/libbsm.h> 17 #include <ipc/securityd_client.h> 18 19 #include "SharedWebCredential/swcagent_client.h" 20 21 #import "SWCViewController.h" 22 23 const NSString* SWC_PASSWORD_KEY = @"spwd"; 24 const NSString* SWC_ACCOUNT_KEY = @"acct"; 25 const NSString* SWC_SERVER_KEY = @"srvr"; 26 27 // 28 // SWCDictionaryAdditions 29 // 30 31 @interface NSDictionary (SWCDictionaryAdditions) 32 - (NSComparisonResult) compareCredentialDictionaryAscending:(NSDictionary *)other; 33 @end 34 35 @implementation NSDictionary (SWCDictionaryAdditions) 36 - (NSComparisonResult)compareCredentialDictionaryAscending:(NSDictionary *)other 37 { 38 NSComparisonResult result; 39 NSString *str1 = [self objectForKey:SWC_ACCOUNT_KEY], *str2 = [other objectForKey:SWC_ACCOUNT_KEY]; 40 if (!str1) str1 = @""; 41 if (!str2) str2 = @""; 42 43 // primary sort by account name 44 result = [str1 localizedCaseInsensitiveCompare:str2]; 45 if (result == NSOrderedSame) { 46 // secondary sort by domain name 47 NSString *str3 = [self objectForKey:SWC_SERVER_KEY], *str4 = [other objectForKey:SWC_SERVER_KEY]; 48 if (!str3) str3 = @""; 49 if (!str4) str4 = @""; 50 51 result = [str3 localizedCaseInsensitiveCompare:str4]; 52 } 53 54 return result; 55 } 56 @end 57 58 59 // 60 // SWCItemCell 61 // 62 @interface SWCItemCell : UITableViewCell 63 { 64 NSDictionary *_dict; 65 BOOL _isTicked; 66 UIView *_bottomLine; 67 UIView *_bottomLineSelected; 68 UIView *_topLine; 69 UIView *_topLineSelected; 70 BOOL _showSeparator; 71 BOOL _showTopSeparator; 72 } 73 74 - (id)initWithDictionary:(NSDictionary *)dict; 75 @property (nonatomic, readonly) id userInfo; 76 @property (nonatomic, assign) BOOL showSeparator; 77 @end 78 79 @implementation SWCItemCell 80 81 @synthesize showSeparator = _showSeparator; 82 83 - (id)initWithDictionary:(NSDictionary *)dict 84 { 85 if ((self = [super initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:nil])) 86 { 87 _dict = dict; 88 89 self.selectionStyle = UITableViewCellSelectionStyleNone; 90 91 self.backgroundColor = [UIColor systemBackgroundColor]; 92 93 self.textLabel.textColor = [UIColor labelColor]; 94 self.textLabel.textAlignment = NSTextAlignmentLeft; 95 self.textLabel.adjustsFontSizeToFitWidth = YES; 96 self.textLabel.baselineAdjustment = UIBaselineAdjustmentAlignCenters; 97 98 NSString *title = [dict objectForKey:SWC_ACCOUNT_KEY]; 99 self.textLabel.text = title ? title : NSLocalizedString(@"--", nil); 100 101 self.detailTextLabel.textColor = [UIColor secondaryLabelColor]; 102 self.detailTextLabel.textAlignment = NSTextAlignmentLeft; 103 self.detailTextLabel.adjustsFontSizeToFitWidth = YES; 104 self.detailTextLabel.baselineAdjustment = UIBaselineAdjustmentAlignCenters; 105 106 NSString *subtitle = [dict objectForKey:SWC_SERVER_KEY]; 107 self.detailTextLabel.text = subtitle ? subtitle : NSLocalizedString(@"--", nil); 108 109 self.backgroundView = [[UIView alloc] init]; 110 self.backgroundView.backgroundColor = [UIColor systemBackgroundColor]; 111 112 self.imageView.image = [UIImage systemImageNamed:@"checkmark"]; 113 self.imageView.hidden = YES; 114 } 115 116 return self; 117 } 118 119 - (void)setTicked: (BOOL) selected 120 { 121 _isTicked = selected; 122 } 123 124 - (void)layoutSubviews 125 { 126 127 if (_bottomLine) { 128 CGFloat scale = [[UIScreen mainScreen] scale]; 129 [_bottomLine setFrame:CGRectMake(0, self.frame.size.height - (1 / scale), self.frame.size.width, 1 / scale)]; 130 } 131 132 if (_bottomLineSelected) { 133 CGFloat scale = [[UIScreen mainScreen] scale]; 134 [_bottomLineSelected setFrame:CGRectMake(0, self.frame.size.height - (1 / scale), self.frame.size.width, 1 / scale)]; 135 } 136 137 if (_topLine) { 138 CGFloat scale = [[UIScreen mainScreen] scale]; 139 [_topLine setFrame:CGRectMake(0, 0, self.frame.size.width, 1 / scale)]; 140 } 141 142 if (_topLineSelected) { 143 CGFloat scale = [[UIScreen mainScreen] scale]; 144 [_topLineSelected setFrame:CGRectMake(0, 0, self.frame.size.width, 1 / scale)]; 145 } 146 147 if (_isTicked) 148 { 149 self.imageView.hidden = NO; 150 } else { 151 self.imageView.hidden = YES; 152 } 153 154 [super layoutSubviews]; 155 156 } 157 158 - (void)setShowSeparator:(BOOL)showSeparator { 159 if (_showSeparator != showSeparator) { 160 _showSeparator = showSeparator; 161 162 if (_showSeparator) { 163 if (!_bottomLine) { 164 CGRect rectZero = CGRectMake(0, 0, 0, 0); 165 _bottomLine = [[UIView alloc] initWithFrame:rectZero]; 166 _bottomLine.backgroundColor = [UIColor separatorColor]; 167 [self.backgroundView addSubview:_bottomLine]; 168 } 169 if (!_bottomLineSelected) { 170 CGRect rectZero = CGRectMake(0, 0, 0, 0); 171 _bottomLineSelected = [[UIView alloc] initWithFrame:rectZero]; 172 _bottomLineSelected.backgroundColor = [UIColor separatorColor]; 173 [self.selectedBackgroundView addSubview: _bottomLineSelected]; 174 } 175 176 } else { 177 if (_bottomLine) { 178 [_bottomLine removeFromSuperview]; 179 _bottomLine = nil; 180 } 181 if (_bottomLineSelected) { 182 [_bottomLineSelected removeFromSuperview]; 183 _bottomLineSelected = nil; 184 } 185 } 186 } 187 } 188 189 - (void)setShowTopSeparator:(BOOL)showTopSeparator { 190 if (_showTopSeparator != showTopSeparator) { 191 _showTopSeparator = showTopSeparator; 192 193 if (_showTopSeparator) { 194 if (!_topLine) { 195 CGRect rectZero = CGRectMake(0, 0, 0, 0); 196 _topLine = [[UIView alloc] initWithFrame:rectZero]; 197 _topLine.backgroundColor = [UIColor separatorColor]; 198 [self.backgroundView addSubview:_topLine]; 199 } 200 if (!_topLineSelected) { 201 CGRect rectZero = CGRectMake(0, 0, 0, 0); 202 _topLineSelected = [[UIView alloc] initWithFrame:rectZero]; 203 _topLineSelected.backgroundColor = [UIColor separatorColor]; 204 [self.selectedBackgroundView addSubview: _topLineSelected]; 205 } 206 207 } else { 208 if (_topLine) { 209 [_topLine removeFromSuperview]; 210 _topLine = nil; 211 } 212 if (_topLineSelected) { 213 [_topLineSelected removeFromSuperview]; 214 _topLineSelected = nil; 215 } 216 } 217 } 218 } 219 220 @end 221 222 223 // 224 // SWCViewController 225 // 226 227 @interface SWCViewController () 228 { 229 NSMutableArray *_credentials; // array of NSDictionary 230 UILabel *_topLabel; 231 UILabel *_middleLabel; 232 UITableView *_table; 233 NSDictionary *_selectedDict; 234 NSIndexPath *_selectedCell; 235 } 236 237 @end 238 239 @implementation SWCViewController 240 241 - (NSDictionary *)selectedItem 242 { 243 return _selectedDict; 244 } 245 246 - (void)setCredentials:(NSArray *)inArray 247 { 248 NSMutableArray *credentials = [[NSMutableArray alloc] initWithArray:inArray]; 249 [credentials sortUsingSelector:@selector(compareCredentialDictionaryAscending:)]; 250 _credentials = credentials; 251 if (_table) 252 [_table reloadData]; 253 } 254 255 - (void)_enableTable 256 { 257 [_table setUserInteractionEnabled:YES]; 258 } 259 260 - (UITableView *)tableView 261 { 262 if (_table == nil) { 263 _table = [[UITableView alloc] init]; 264 [_table setTranslatesAutoresizingMaskIntoConstraints:NO]; 265 [_table setAutoresizingMask:UIViewAutoresizingNone]; 266 [_table setBackgroundColor:[UIColor systemBackgroundColor]]; 267 [_table setSeparatorStyle:UITableViewCellSeparatorStyleNone]; 268 } 269 [_table sizeToFit]; 270 271 return (UITableView *)_table; 272 } 273 274 -(void)loadView 275 { 276 277 UIView* view = [[UIView alloc] init]; 278 279 UITableView* table = [self tableView]; 280 [table setDelegate: self]; 281 [table setDataSource: self]; 282 283 [view addSubview: table]; 284 285 CFErrorRef error = NULL; 286 audit_token_t auditToken = {}; 287 memset(&auditToken, 0, sizeof(auditToken)); 288 CFArrayRef credentialList = swca_copy_pairs(swca_copy_pairs_request_id, &auditToken, &error); 289 if (error) { 290 NSLog(@"Unable to get accounts: %@", [(__bridge NSError*)error localizedDescription]); 291 } 292 293 [self setCredentials:(__bridge NSArray*)credentialList]; 294 if (credentialList) { 295 CFRelease(credentialList); 296 } 297 298 table.frame = CGRectMake(0.0, 0.0, 300.0, 45.0); 299 [table layoutIfNeeded]; // so autolayout can do its thing and then we can measure a cell 300 UITableViewCell* cell = table.visibleCells.firstObject; 301 CGFloat heightForOneRow = cell ? CGRectGetHeight(cell.frame) : 45.0; 302 303 NSDictionary* views = NSDictionaryOfVariableBindings(table); 304 305 if ([_credentials count] > 2) 306 { 307 308 CGFloat manyCredentialsHeight = CGFloatMax((heightForOneRow * 2) + 30.0, 120.0); 309 NSDictionary *metrics = @{@"height":@(manyCredentialsHeight)}; 310 [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[table]-|" options:0 metrics:metrics views:views]]; 311 [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[table(height)]-|" options:0 metrics:metrics views:views]]; 312 313 CGFloat scale = [[UIScreen mainScreen] scale]; 314 table.layer.borderWidth = 1.0 / scale; 315 table.layer.borderColor = [UIColor opaqueSeparatorColor].CGColor; 316 317 [self setPreferredContentSize:CGSizeMake(0, manyCredentialsHeight + 20.0)]; 318 319 } else if ([_credentials count] == 2) { 320 321 NSDictionary *metrics = @{@"height":@(heightForOneRow * 2)}; 322 [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[table]|" options:0 metrics:metrics views:views]]; 323 [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[table(height)]-|" options:0 metrics:metrics views:views]]; 324 325 [self setPreferredContentSize:CGSizeMake(0, heightForOneRow * 2)]; 326 [table setScrollEnabled: NO]; 327 328 } else { // [_credentials count] == 1 329 330 NSDictionary *metrics = @{@"height":@(heightForOneRow)}; 331 [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[table]|" options:0 metrics:metrics views:views]]; 332 [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[table(height)]|" options:0 metrics:metrics views:views]]; 333 334 [self setPreferredContentSize:CGSizeMake(0, heightForOneRow)]; 335 [table setScrollEnabled: NO]; 336 337 } 338 339 [self setView:view]; 340 } 341 342 -(void)viewWillAppear:(BOOL)animated 343 { 344 345 // Select the first cell by default 346 347 NSDictionary *dict = [_credentials objectAtIndex: 0]; 348 _selectedDict = dict; 349 _selectedCell = [NSIndexPath indexPathForItem:0 inSection: 0]; 350 SWCItemCell *cell = (SWCItemCell *)[_table cellForRowAtIndexPath: _selectedCell]; 351 [cell setTicked: YES]; 352 [cell layoutSubviews]; 353 [_table selectRowAtIndexPath: _selectedCell animated: NO scrollPosition: UITableViewScrollPositionTop]; 354 355 CFErrorRef error = NULL; 356 audit_token_t auditToken = {}; 357 memset(&auditToken, 0, sizeof(auditToken)); 358 bool result = swca_set_selection(swca_set_selection_request_id, 359 &auditToken, (__bridge CFDictionaryRef)dict, &error); 360 if (!result) { 361 NSLog(@"Unable to select item: %@", [(__bridge NSError*)error localizedDescription]); 362 } 363 364 [super viewWillAppear:animated]; 365 } 366 367 368 // 369 // UITableView delegate methods 370 // 371 372 - (NSInteger)tableView:(UITableView *)table numberOfRowsInSection:(NSInteger)section 373 { 374 return [_credentials count]; 375 } 376 377 - (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 378 { 379 NSUInteger row = indexPath.row; 380 381 NSDictionary *dict = [_credentials objectAtIndex:row]; 382 _selectedDict = dict; 383 384 CFErrorRef error = NULL; 385 audit_token_t auditToken = {}; 386 memset(&auditToken, 0, sizeof(auditToken)); 387 bool result = swca_set_selection(swca_set_selection_request_id, 388 &auditToken, (__bridge CFDictionaryRef)dict, &error); 389 if (!result) { 390 NSLog(@"Unable to select item: %@", [(__bridge NSError*)error localizedDescription]); 391 } 392 393 _selectedCell = indexPath; 394 SWCItemCell *cell = (SWCItemCell *)[tableView cellForRowAtIndexPath: indexPath]; 395 [cell setTicked: YES]; 396 [cell layoutSubviews]; 397 } 398 399 - (void) tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath 400 { 401 SWCItemCell *cell = (SWCItemCell *)[tableView cellForRowAtIndexPath: indexPath]; 402 [cell setTicked: NO]; 403 [cell layoutSubviews]; 404 405 } 406 407 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 408 { 409 410 NSDictionary *dict = [_credentials objectAtIndex:[indexPath row]]; 411 SWCItemCell *cell = [[SWCItemCell alloc] initWithDictionary:dict]; 412 413 // show separator on top cell if there's only or or two items 414 if ([_credentials count] <= 2) { 415 cell.showTopSeparator = YES; 416 } else { 417 cell.showSeparator = YES; 418 419 if (indexPath.row == 0) 420 { 421 cell.showTopSeparator = YES; 422 } 423 424 } 425 426 if (_selectedCell == indexPath) 427 { 428 [cell setTicked: YES]; 429 } else { 430 [cell setTicked: NO]; 431 } 432 433 return cell; 434 } 435 436 437 @end