Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2017 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #import "ios/chrome/browser/payments/payment_request_edit_view_controller.h" | |
| 6 | |
| 7 #include "base/logging.h" | |
| 8 #import "base/mac/foundation_util.h" | |
| 9 #include "base/strings/sys_string_conversions.h" | |
| 10 #include "components/autofill/core/browser/field_types.h" | |
| 11 #include "components/strings/grit/components_strings.h" | |
| 12 #import "ios/chrome/browser/payments/cells/payments_text_item.h" | |
| 13 #import "ios/chrome/browser/payments/payment_request_edit_view_controller+intern al.h" | |
| 14 #import "ios/chrome/browser/payments/payment_request_edit_view_controller_action s.h" | |
| 15 #import "ios/chrome/browser/payments/payment_request_editor_field.h" | |
| 16 #import "ios/chrome/browser/ui/collection_view/cells/MDCCollectionViewCell+Chrom e.h" | |
| 17 #import "ios/chrome/browser/ui/collection_view/cells/collection_view_footer_item .h" | |
| 18 #import "ios/chrome/browser/ui/colors/MDCPalette+CrAdditions.h" | |
| 19 #import "ios/chrome/browser/ui/settings/autofill_edit_accessory_view.h" | |
| 20 #import "ios/chrome/browser/ui/settings/cells/autofill_edit_item.h" | |
| 21 #import "ios/chrome/browser/ui/uikit_ui_util.h" | |
| 22 #include "ios/chrome/grit/ios_theme_resources.h" | |
| 23 #import "ios/third_party/material_components_ios/src/components/Typography/src/M aterialTypography.h" | |
| 24 #include "ui/base/l10n/l10n_util.h" | |
| 25 | |
| 26 #if !defined(__has_feature) || !__has_feature(objc_arc) | |
| 27 #error "This file requires ARC support." | |
| 28 #endif | |
| 29 | |
| 30 namespace { | |
| 31 | |
| 32 NSString* const kPaymentRequestEditCollectionViewID = | |
| 33 @"kPaymentRequestEditCollectionViewID"; | |
| 34 | |
| 35 const CGFloat kSeparatorEdgeInset = 14; | |
| 36 | |
| 37 const CGFloat kFooterCellHorizontalPadding = 16; | |
| 38 | |
| 39 AutofillEditCell* AutofillEditCellForTextField(UITextField* textField) { | |
|
lpromero
2017/03/23 12:58:36
Nit: add a comment.
Moe
2017/03/23 15:20:43
Done.
| |
| 40 for (UIView* view = textField; view; view = [view superview]) { | |
| 41 AutofillEditCell* cell = base::mac::ObjCCast<AutofillEditCell>(view); | |
| 42 if (cell) | |
| 43 return cell; | |
| 44 } | |
| 45 | |
| 46 // There has to be a cell associated with this text field. | |
| 47 NOTREACHED(); | |
| 48 return nil; | |
| 49 } | |
| 50 | |
| 51 typedef NS_ENUM(NSInteger, SectionIdentifier) { | |
| 52 SectionIdentifierFooter = kSectionIdentifierEnumZero, | |
| 53 SectionIdentifierFirstTextField, | |
| 54 }; | |
| 55 | |
| 56 typedef NS_ENUM(NSInteger, ItemType) { | |
| 57 ItemTypeFooter = kItemTypeEnumZero, | |
| 58 ItemTypeTextField, // This is a repeated item type. | |
| 59 ItemTypeErrorMessage, // This is a repeated item type. | |
| 60 }; | |
| 61 | |
| 62 } // namespace | |
| 63 | |
| 64 @interface PaymentRequestEditViewController ()< | |
| 65 AutofillEditAccessoryDelegate, | |
| 66 PaymentRequestEditViewControllerActions, | |
| 67 PaymentRequestEditViewControllerValidator, | |
| 68 UITextFieldDelegate> { | |
| 69 NSArray<EditorField*>* _fields; | |
| 70 | |
| 71 // The currently focused cell. May be nil. | |
| 72 __weak AutofillEditCell* _currentEditingCell; | |
| 73 | |
| 74 AutofillEditAccessoryView* _accessoryView; | |
| 75 } | |
| 76 | |
| 77 // Returns the indexPath for the same row as that of |indexPath| in a section | |
| 78 // with the given offset relative to that of |indexPath|. May return nil. | |
| 79 - (NSIndexPath*)indexPathWithSectionOffset:(NSInteger)offset | |
| 80 fromPath:(NSIndexPath*)indexPath; | |
| 81 | |
| 82 // Returns the text field with the given offset relative to the currently | |
| 83 // focused text field. May return nil. | |
| 84 - (AutofillEditCell*)nextTextFieldWithOffset:(NSInteger)offset; | |
| 85 | |
| 86 // Enables or disables the accessory view's previous and next buttons depending | |
| 87 // on whether there is a text field before and after the currently focused text | |
| 88 // field. | |
| 89 - (void)updateAccessoryViewButtonsStates; | |
| 90 | |
| 91 @end | |
| 92 | |
| 93 @implementation PaymentRequestEditViewController | |
| 94 | |
| 95 @synthesize editorDelegate = _editorDelegate; | |
| 96 @synthesize validatorDelegate = _validatorDelegate; | |
| 97 | |
| 98 - (instancetype)initWithEditorFields:(NSArray<EditorField*>*)fields { | |
| 99 self = [super initWithStyle:CollectionViewControllerStyleAppBar]; | |
| 100 if (self) { | |
| 101 _fields = fields; | |
| 102 | |
| 103 // Set self as the validator delegate. | |
| 104 _validatorDelegate = self; | |
| 105 | |
| 106 // Set up leading (cancel) button. | |
| 107 UIBarButtonItem* cancelButton = [[UIBarButtonItem alloc] | |
| 108 initWithTitle:l10n_util::GetNSString(IDS_CANCEL) | |
| 109 style:UIBarButtonItemStylePlain | |
| 110 target:nil | |
| 111 action:@selector(onReturn)]; | |
| 112 [cancelButton setTitleTextAttributes:@{ | |
| 113 NSForegroundColorAttributeName : [UIColor lightGrayColor] | |
| 114 } | |
| 115 forState:UIControlStateDisabled]; | |
| 116 [cancelButton | |
| 117 setAccessibilityLabel:l10n_util::GetNSString(IDS_ACCNAME_CANCEL)]; | |
| 118 [self navigationItem].leftBarButtonItem = cancelButton; | |
| 119 | |
| 120 // Set up trailing (done) button. | |
| 121 UIBarButtonItem* doneButton = | |
| 122 [[UIBarButtonItem alloc] initWithTitle:l10n_util::GetNSString(IDS_DONE) | |
| 123 style:UIBarButtonItemStylePlain | |
| 124 target:nil | |
| 125 action:@selector(onDone)]; | |
| 126 [doneButton setTitleTextAttributes:@{ | |
| 127 NSForegroundColorAttributeName : [UIColor lightGrayColor] | |
| 128 } | |
| 129 forState:UIControlStateDisabled]; | |
| 130 [doneButton setAccessibilityLabel:l10n_util::GetNSString(IDS_ACCNAME_DONE)]; | |
| 131 [self navigationItem].rightBarButtonItem = doneButton; | |
| 132 | |
| 133 _accessoryView = [[AutofillEditAccessoryView alloc] initWithDelegate:self]; | |
| 134 } | |
| 135 return self; | |
| 136 } | |
| 137 | |
| 138 #pragma mark - PaymentRequestEditViewControllerActions methods | |
| 139 | |
| 140 - (void)onReturn { | |
| 141 [_editorDelegate paymentRequestEditViewControllerDidReturn:self]; | |
| 142 } | |
| 143 | |
| 144 - (void)onDone { | |
| 145 if ([self validateForm]) { | |
| 146 [_editorDelegate paymentRequestEditViewController:self | |
| 147 didFinishEditingFields:_fields]; | |
| 148 } | |
| 149 } | |
| 150 | |
| 151 - (void)viewDidAppear:(BOOL)animated { | |
| 152 [super viewDidAppear:animated]; | |
| 153 [[NSNotificationCenter defaultCenter] | |
| 154 addObserver:self | |
| 155 selector:@selector(keyboardDidShow) | |
| 156 name:UIKeyboardDidShowNotification | |
| 157 object:nil]; | |
| 158 } | |
| 159 | |
| 160 - (void)viewWillDisappear:(BOOL)animated { | |
| 161 [super viewWillDisappear:animated]; | |
| 162 [[NSNotificationCenter defaultCenter] | |
| 163 removeObserver:self | |
| 164 name:UIKeyboardDidShowNotification | |
| 165 object:nil]; | |
| 166 } | |
| 167 | |
| 168 #pragma mark - CollectionViewController methods | |
| 169 | |
| 170 - (void)loadModel { | |
| 171 [super loadModel]; | |
| 172 CollectionViewModel* model = self.collectionViewModel; | |
| 173 | |
| 174 // Iterate over the fields and add the respective sections and items. | |
| 175 int sectionIdentifier = static_cast<int>(SectionIdentifierFirstTextField); | |
| 176 for (EditorField* field in _fields) { | |
| 177 [model addSectionWithIdentifier:sectionIdentifier]; | |
| 178 AutofillEditItem* item = | |
| 179 [[AutofillEditItem alloc] initWithType:ItemTypeTextField]; | |
| 180 item.textFieldName = field.label; | |
| 181 item.textFieldEnabled = YES; | |
| 182 item.textFieldValue = field.value; | |
| 183 item.required = field.isRequired; | |
| 184 item.autofillType = | |
| 185 static_cast<autofill::ServerFieldType>(field.autofillType); | |
| 186 [model addItem:item | |
| 187 toSectionWithIdentifier:static_cast<NSInteger>(sectionIdentifier)]; | |
| 188 field.item = item; | |
| 189 field.sectionIdentifier = static_cast<NSInteger>(sectionIdentifier); | |
| 190 ++sectionIdentifier; | |
| 191 } | |
| 192 | |
| 193 [self loadFooterItems]; | |
| 194 } | |
| 195 | |
| 196 - (void)viewDidLoad { | |
| 197 [super viewDidLoad]; | |
| 198 | |
| 199 self.collectionView.accessibilityIdentifier = | |
| 200 kPaymentRequestEditCollectionViewID; | |
| 201 | |
| 202 // Customize collection view settings. | |
| 203 self.styler.cellStyle = MDCCollectionViewCellStyleCard; | |
| 204 self.styler.separatorInset = | |
| 205 UIEdgeInsetsMake(0, kSeparatorEdgeInset, 0, kSeparatorEdgeInset); | |
| 206 } | |
| 207 | |
| 208 #pragma mark - UITextFieldDelegate | |
| 209 | |
| 210 - (void)textFieldDidBeginEditing:(UITextField*)textField { | |
| 211 _currentEditingCell = AutofillEditCellForTextField(textField); | |
| 212 [textField setInputAccessoryView:_accessoryView]; | |
| 213 [self updateAccessoryViewButtonsStates]; | |
| 214 } | |
| 215 | |
| 216 - (void)textFieldDidEndEditing:(UITextField*)textField { | |
| 217 DCHECK(_currentEditingCell == AutofillEditCellForTextField(textField)); | |
| 218 | |
| 219 // Validate the text field. | |
| 220 CollectionViewModel* model = self.collectionViewModel; | |
| 221 | |
| 222 NSIndexPath* indexPath = [self indexPathForCurrentTextField]; | |
| 223 AutofillEditItem* item = base::mac::ObjCCastStrict<AutofillEditItem>( | |
| 224 [model itemAtIndexPath:indexPath]); | |
| 225 | |
| 226 NSString* errorMessage = | |
| 227 [_validatorDelegate paymentRequestEditViewController:self | |
| 228 validateValue:textField.text | |
| 229 autofillType:item.autofillType | |
| 230 required:item.required]; | |
| 231 NSInteger sectionIdentifier = | |
| 232 [model sectionIdentifierForSection:[indexPath section]]; | |
| 233 [self addOrRemoveErrorMessage:errorMessage | |
| 234 inSectionWithIdentifier:sectionIdentifier]; | |
| 235 | |
| 236 [textField setInputAccessoryView:nil]; | |
| 237 _currentEditingCell = nil; | |
| 238 } | |
| 239 | |
| 240 - (BOOL)textFieldShouldReturn:(UITextField*)textField { | |
| 241 DCHECK([_currentEditingCell textField] == textField); | |
| 242 AutofillEditCell* nextCell = [self nextTextFieldWithOffset:1]; | |
| 243 if (nextCell) | |
| 244 [self nextPressed]; | |
| 245 else | |
| 246 [self closePressed]; | |
| 247 | |
| 248 return NO; | |
| 249 } | |
| 250 | |
| 251 #pragma mark - AutofillEditAccessoryDelegate | |
| 252 | |
| 253 - (void)nextPressed { | |
| 254 AutofillEditCell* nextCell = [self nextTextFieldWithOffset:1]; | |
| 255 if (nextCell) | |
| 256 [nextCell.textField becomeFirstResponder]; | |
| 257 } | |
| 258 | |
| 259 - (void)previousPressed { | |
| 260 AutofillEditCell* previousCell = [self nextTextFieldWithOffset:-1]; | |
| 261 if (previousCell) | |
| 262 [previousCell.textField becomeFirstResponder]; | |
| 263 } | |
| 264 | |
| 265 - (void)closePressed { | |
| 266 [[_currentEditingCell textField] resignFirstResponder]; | |
| 267 } | |
| 268 | |
| 269 #pragma mark - UICollectionViewDataSource | |
| 270 | |
| 271 - (UICollectionViewCell*)collectionView:(UICollectionView*)collectionView | |
| 272 cellForItemAtIndexPath:(NSIndexPath*)indexPath { | |
| 273 UICollectionViewCell* cell = | |
| 274 [super collectionView:collectionView cellForItemAtIndexPath:indexPath]; | |
| 275 | |
| 276 CollectionViewItem* item = | |
| 277 [self.collectionViewModel itemAtIndexPath:indexPath]; | |
| 278 switch (item.type) { | |
| 279 case ItemTypeTextField: { | |
| 280 AutofillEditCell* autofillEditCell = | |
| 281 base::mac::ObjCCast<AutofillEditCell>(cell); | |
| 282 autofillEditCell.textField.delegate = self; | |
| 283 autofillEditCell.textField.clearButtonMode = UITextFieldViewModeNever; | |
| 284 autofillEditCell.textLabel.font = [MDCTypography body2Font]; | |
| 285 autofillEditCell.textLabel.textColor = [[MDCPalette greyPalette] tint900]; | |
| 286 autofillEditCell.textField.font = [MDCTypography body1Font]; | |
| 287 autofillEditCell.textField.textColor = | |
| 288 [[MDCPalette cr_bluePalette] tint600]; | |
| 289 break; | |
| 290 } | |
| 291 case ItemTypeErrorMessage: { | |
| 292 PaymentsTextCell* errorMessageCell = | |
| 293 base::mac::ObjCCastStrict<PaymentsTextCell>(cell); | |
| 294 errorMessageCell.textLabel.font = [MDCTypography body1Font]; | |
| 295 errorMessageCell.textLabel.textColor = | |
| 296 [[MDCPalette cr_redPalette] tint600]; | |
| 297 break; | |
| 298 } | |
| 299 case ItemTypeFooter: { | |
| 300 CollectionViewFooterCell* footerCell = | |
| 301 base::mac::ObjCCastStrict<CollectionViewFooterCell>(cell); | |
| 302 footerCell.textLabel.font = [MDCTypography body2Font]; | |
| 303 footerCell.textLabel.textColor = [[MDCPalette greyPalette] tint600]; | |
| 304 footerCell.textLabel.shadowColor = nil; // No shadow. | |
| 305 footerCell.horizontalPadding = kFooterCellHorizontalPadding; | |
| 306 break; | |
| 307 } | |
| 308 default: | |
| 309 break; | |
| 310 } | |
| 311 | |
| 312 return cell; | |
| 313 } | |
| 314 | |
| 315 #pragma mark MDCCollectionViewStylingDelegate | |
| 316 | |
| 317 - (CGFloat)collectionView:(UICollectionView*)collectionView | |
| 318 cellHeightAtIndexPath:(NSIndexPath*)indexPath { | |
| 319 CollectionViewItem* item = | |
| 320 [self.collectionViewModel itemAtIndexPath:indexPath]; | |
| 321 switch (item.type) { | |
| 322 case ItemTypeTextField: | |
| 323 case ItemTypeErrorMessage: | |
| 324 case ItemTypeFooter: | |
| 325 return [MDCCollectionViewCell | |
| 326 cr_preferredHeightForWidth:CGRectGetWidth(collectionView.bounds) | |
| 327 forItem:item]; | |
| 328 default: | |
| 329 NOTREACHED(); | |
| 330 return MDCCellDefaultOneLineHeight; | |
| 331 } | |
| 332 } | |
| 333 | |
| 334 - (BOOL)collectionView:(UICollectionView*)collectionView | |
| 335 hidesInkViewAtIndexPath:(NSIndexPath*)indexPath { | |
| 336 NSInteger type = [self.collectionViewModel itemTypeForIndexPath:indexPath]; | |
| 337 switch (type) { | |
| 338 case ItemTypeErrorMessage: | |
| 339 case ItemTypeFooter: | |
| 340 return YES; | |
| 341 default: | |
| 342 return NO; | |
| 343 } | |
| 344 } | |
| 345 | |
| 346 - (BOOL)collectionView:(UICollectionView*)collectionView | |
| 347 shouldHideItemBackgroundAtIndexPath:(NSIndexPath*)indexPath { | |
| 348 NSInteger type = [self.collectionViewModel itemTypeForIndexPath:indexPath]; | |
| 349 switch (type) { | |
| 350 case ItemTypeFooter: | |
| 351 return YES; | |
| 352 default: | |
| 353 return NO; | |
| 354 } | |
| 355 } | |
| 356 | |
| 357 #pragma mark - PaymentRequestEditViewControllerValidator | |
| 358 | |
| 359 - (NSString*)paymentRequestEditViewController: | |
| 360 (PaymentRequestEditViewController*)controller | |
| 361 validateValue:(NSString*)value | |
| 362 autofillType:(NSInteger)autofillType | |
| 363 required:(BOOL)required { | |
| 364 if (required && value.length == 0) { | |
| 365 return l10n_util::GetNSString( | |
| 366 IDS_PAYMENTS_FIELD_REQUIRED_VALIDATION_MESSAGE); | |
| 367 } | |
| 368 return @""; | |
| 369 } | |
| 370 | |
| 371 #pragma mark - Helper methods | |
| 372 | |
| 373 - (NSIndexPath*)indexPathWithSectionOffset:(NSInteger)offset | |
| 374 fromPath:(NSIndexPath*)indexPath { | |
| 375 DCHECK(indexPath); | |
| 376 DCHECK(offset); | |
| 377 NSInteger nextSection = [indexPath section] + offset; | |
| 378 if (nextSection >= 0 && | |
| 379 nextSection < [[self collectionView] numberOfSections]) { | |
| 380 return [NSIndexPath indexPathForRow:[indexPath row] inSection:nextSection]; | |
| 381 } | |
| 382 return nil; | |
| 383 } | |
| 384 | |
| 385 - (AutofillEditCell*)nextTextFieldWithOffset:(NSInteger)offset { | |
| 386 UICollectionView* collectionView = [self collectionView]; | |
| 387 NSIndexPath* currentCellPath = [self indexPathForCurrentTextField]; | |
| 388 DCHECK(currentCellPath); | |
| 389 NSIndexPath* nextCellPath = | |
| 390 [self indexPathWithSectionOffset:offset fromPath:currentCellPath]; | |
| 391 if (nextCellPath) { | |
| 392 id nextCell = [collectionView cellForItemAtIndexPath:nextCellPath]; | |
| 393 if ([nextCell isKindOfClass:[AutofillEditCell class]]) { | |
| 394 return base::mac::ObjCCastStrict<AutofillEditCell>( | |
| 395 [collectionView cellForItemAtIndexPath:nextCellPath]); | |
| 396 } | |
| 397 } | |
| 398 return nil; | |
| 399 } | |
| 400 | |
| 401 - (void)updateAccessoryViewButtonsStates { | |
| 402 AutofillEditCell* previousCell = [self nextTextFieldWithOffset:-1]; | |
| 403 [[_accessoryView previousButton] setEnabled:previousCell != nil]; | |
| 404 | |
| 405 AutofillEditCell* nextCell = [self nextTextFieldWithOffset:1]; | |
| 406 [[_accessoryView nextButton] setEnabled:nextCell != nil]; | |
| 407 } | |
| 408 | |
| 409 #pragma mark - Keyboard handling | |
| 410 | |
| 411 - (void)keyboardDidShow { | |
| 412 [self.collectionView | |
| 413 scrollToItemAtIndexPath:[self.collectionView | |
| 414 indexPathForCell:_currentEditingCell] | |
| 415 atScrollPosition:UICollectionViewScrollPositionCenteredVertically | |
| 416 animated:YES]; | |
| 417 } | |
| 418 | |
| 419 @end | |
| 420 | |
| 421 @implementation PaymentRequestEditViewController (Internal) | |
| 422 | |
| 423 - (BOOL)validateForm { | |
| 424 for (EditorField* field in _fields) { | |
| 425 AutofillEditItem* item = field.item; | |
| 426 | |
| 427 NSString* errorMessage = | |
| 428 [_validatorDelegate paymentRequestEditViewController:self | |
| 429 validateValue:item.textFieldValue | |
| 430 autofillType:field.autofillType | |
| 431 required:field.isRequired]; | |
| 432 [self addOrRemoveErrorMessage:errorMessage | |
| 433 inSectionWithIdentifier:field.sectionIdentifier]; | |
| 434 if (errorMessage.length != 0) { | |
| 435 return NO; | |
| 436 } | |
| 437 | |
| 438 field.value = item.textFieldValue; | |
| 439 } | |
| 440 return YES; | |
| 441 } | |
| 442 | |
| 443 - (void)loadFooterItems { | |
| 444 CollectionViewModel* model = self.collectionViewModel; | |
| 445 | |
| 446 [model addSectionWithIdentifier:SectionIdentifierFooter]; | |
| 447 CollectionViewFooterItem* footerItem = | |
| 448 [[CollectionViewFooterItem alloc] initWithType:ItemTypeFooter]; | |
| 449 footerItem.text = l10n_util::GetNSString(IDS_PAYMENTS_REQUIRED_FIELD_MESSAGE); | |
| 450 [model addItem:footerItem toSectionWithIdentifier:SectionIdentifierFooter]; | |
| 451 } | |
| 452 | |
| 453 - (NSIndexPath*)indexPathForCurrentTextField { | |
| 454 DCHECK(_currentEditingCell); | |
| 455 NSIndexPath* indexPath = | |
| 456 [[self collectionView] indexPathForCell:_currentEditingCell]; | |
| 457 DCHECK(indexPath); | |
| 458 return indexPath; | |
| 459 } | |
| 460 | |
| 461 - (void)addOrRemoveErrorMessage:(NSString*)errorMessage | |
| 462 inSectionWithIdentifier:(NSInteger)sectionIdentifier { | |
| 463 CollectionViewModel* model = self.collectionViewModel; | |
| 464 if ([model hasItemForItemType:ItemTypeErrorMessage | |
| 465 sectionIdentifier:sectionIdentifier]) { | |
| 466 NSIndexPath* indexPath = [model indexPathForItemType:ItemTypeErrorMessage | |
| 467 sectionIdentifier:sectionIdentifier]; | |
| 468 if (errorMessage.length == 0) { | |
| 469 // Remove the item at the index path. | |
| 470 [model removeItemWithType:ItemTypeErrorMessage | |
| 471 fromSectionWithIdentifier:sectionIdentifier]; | |
| 472 [self.collectionView deleteItemsAtIndexPaths:@[ indexPath ]]; | |
| 473 } else { | |
| 474 // Reload the item at the index path. | |
| 475 PaymentsTextItem* item = base::mac::ObjCCastStrict<PaymentsTextItem>( | |
| 476 [model itemAtIndexPath:indexPath]); | |
| 477 item.text = errorMessage; | |
| 478 [self.collectionView reloadItemsAtIndexPaths:@[ indexPath ]]; | |
| 479 } | |
| 480 } else if (errorMessage.length != 0) { | |
| 481 // Insert an item at the index path. | |
| 482 PaymentsTextItem* errorMessageItem = | |
| 483 [[PaymentsTextItem alloc] initWithType:ItemTypeErrorMessage]; | |
| 484 errorMessageItem.text = errorMessage; | |
| 485 errorMessageItem.image = NativeImage(IDR_IOS_PAYMENTS_WARNING); | |
| 486 [model addItem:errorMessageItem toSectionWithIdentifier:sectionIdentifier]; | |
| 487 NSIndexPath* indexPath = [model indexPathForItemType:ItemTypeErrorMessage | |
| 488 sectionIdentifier:sectionIdentifier]; | |
| 489 [self.collectionView insertItemsAtIndexPaths:@[ indexPath ]]; | |
| 490 } | |
| 491 } | |
| 492 | |
| 493 @end | |
| OLD | NEW |