| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "ios/chrome/browser/ui/collection_view/collection_view_controller.h" |
| |
| #include "base/logging.h" |
| #include "base/mac/foundation_util.h" |
| #import "ios/chrome/browser/ui/collection_view/cells/collection_view_item.h" |
| #import "ios/chrome/browser/ui/collection_view/collection_view_model.h" |
| #import "ios/chrome/browser/ui/material_components/utils.h" |
| #import "ios/third_party/material_components_ios/src/components/AppBar/src/MaterialAppBar.h" |
| #import "ios/third_party/material_components_ios/src/components/CollectionCells/src/MaterialCollectionCells.h" |
| |
| #if !defined(__has_feature) || !__has_feature(objc_arc) |
| #error "This file requires ARC support." |
| #endif |
| |
| // The implementation of this controller follows the guidelines from |
| // https://github.com/material-components/material-components-ios/tree/develop/components/AppBar |
| @implementation CollectionViewController |
| @synthesize appBar = _appBar; |
| @synthesize collectionViewModel = _collectionViewModel; |
| |
| - (instancetype)initWithStyle:(CollectionViewControllerStyle)style { |
| UICollectionViewLayout* layout = [[MDCCollectionViewFlowLayout alloc] init]; |
| self = [super initWithCollectionViewLayout:layout]; |
| if (self) { |
| if (style == CollectionViewControllerStyleAppBar) { |
| _appBar = [[MDCAppBar alloc] init]; |
| [self addChildViewController:_appBar.headerViewController]; |
| } |
| } |
| return self; |
| } |
| |
| - (void)viewDidLoad { |
| [super viewDidLoad]; |
| |
| // Configure the app bar, if there is one. |
| if (self.appBar) { |
| // Configure the app bar style. |
| ConfigureAppBarWithCardStyle(self.appBar); |
| // Set the header view's tracking scroll view. |
| self.appBar.headerViewController.headerView.trackingScrollView = |
| self.collectionView; |
| // After all other views have been registered. |
| [self.appBar addSubviewsToParent]; |
| } |
| } |
| |
| - (UIViewController*)childViewControllerForStatusBarHidden { |
| return self.appBar.headerViewController; |
| } |
| |
| - (UIViewController*)childViewControllerForStatusBarStyle { |
| return self.appBar.headerViewController; |
| } |
| |
| - (void)loadModel { |
| _collectionViewModel = [[CollectionViewModel alloc] init]; |
| } |
| |
| - (void)reconfigureCellsForItems:(NSArray*)items |
| inSectionWithIdentifier:(NSInteger)sectionIdentifier { |
| for (CollectionViewItem* item in items) { |
| NSIndexPath* indexPath = |
| [self.collectionViewModel indexPathForItem:item |
| inSectionWithIdentifier:sectionIdentifier]; |
| [self reconfigureCellAtIndexPath:indexPath withItem:item]; |
| } |
| } |
| |
| - (void)reconfigureCellsAtIndexPaths:(NSArray*)indexPaths { |
| for (NSIndexPath* indexPath in indexPaths) { |
| CollectionViewItem* item = |
| [self.collectionViewModel itemAtIndexPath:indexPath]; |
| [self reconfigureCellAtIndexPath:indexPath withItem:item]; |
| } |
| } |
| |
| #pragma mark MDCCollectionViewEditingDelegate |
| |
| - (void)collectionView:(UICollectionView*)collectionView |
| willDeleteItemsAtIndexPaths:(NSArray*)indexPaths { |
| // Check that the parent class doesn't implement this method. Otherwise, it |
| // would need to be called here. |
| DCHECK([self isKindOfClass:[MDCCollectionViewController class]]); |
| DCHECK(![MDCCollectionViewController instancesRespondToSelector:_cmd]); |
| |
| // Sort and enumerate in reverse order to delete the items from the collection |
| // view model. |
| NSArray* sortedIndexPaths = |
| [indexPaths sortedArrayUsingSelector:@selector(compare:)]; |
| for (NSIndexPath* indexPath in [sortedIndexPaths reverseObjectEnumerator]) { |
| NSInteger sectionIdentifier = [self.collectionViewModel |
| sectionIdentifierForSection:indexPath.section]; |
| NSInteger itemType = |
| [self.collectionViewModel itemTypeForIndexPath:indexPath]; |
| NSUInteger index = |
| [self.collectionViewModel indexInItemTypeForIndexPath:indexPath]; |
| [self.collectionViewModel removeItemWithType:itemType |
| fromSectionWithIdentifier:sectionIdentifier |
| atIndex:index]; |
| } |
| } |
| |
| - (void)collectionView:(UICollectionView*)collectionView |
| willMoveItemAtIndexPath:(NSIndexPath*)indexPath |
| toIndexPath:(NSIndexPath*)newIndexPath { |
| // Check that the parent class doesn't implement this method. Otherwise, it |
| // would need to be called here. |
| DCHECK([self isKindOfClass:[MDCCollectionViewController class]]); |
| DCHECK(![MDCCollectionViewController instancesRespondToSelector:_cmd]); |
| |
| // Retain the item to be able to move it. |
| CollectionViewItem* item = |
| [self.collectionViewModel itemAtIndexPath:indexPath]; |
| |
| // Item coordinates. |
| NSInteger sectionIdentifier = |
| [self.collectionViewModel sectionIdentifierForSection:indexPath.section]; |
| NSInteger itemType = |
| [self.collectionViewModel itemTypeForIndexPath:indexPath]; |
| NSUInteger indexInItemType = |
| [self.collectionViewModel indexInItemTypeForIndexPath:indexPath]; |
| |
| // Move the item. |
| [self.collectionViewModel removeItemWithType:itemType |
| fromSectionWithIdentifier:sectionIdentifier |
| atIndex:indexInItemType]; |
| NSInteger section = [self.collectionViewModel |
| sectionIdentifierForSection:newIndexPath.section]; |
| [self.collectionViewModel insertItem:item |
| inSectionWithIdentifier:section |
| atIndex:newIndexPath.item]; |
| } |
| |
| #pragma mark UICollectionViewDataSource |
| |
| - (NSInteger)numberOfSectionsInCollectionView: |
| (UICollectionView*)collectionView { |
| return [self.collectionViewModel numberOfSections]; |
| } |
| |
| - (NSInteger)collectionView:(UICollectionView*)collectionView |
| numberOfItemsInSection:(NSInteger)section { |
| return [self.collectionViewModel numberOfItemsInSection:section]; |
| } |
| |
| - (UICollectionViewCell*)collectionView:(UICollectionView*)collectionView |
| cellForItemAtIndexPath:(NSIndexPath*)indexPath { |
| CollectionViewItem* item = |
| [self.collectionViewModel itemAtIndexPath:indexPath]; |
| Class cellClass = [item cellClass]; |
| NSString* reuseIdentifier = NSStringFromClass(cellClass); |
| [self.collectionView registerClass:cellClass |
| forCellWithReuseIdentifier:reuseIdentifier]; |
| MDCCollectionViewCell* cell = [self.collectionView |
| dequeueReusableCellWithReuseIdentifier:reuseIdentifier |
| forIndexPath:indexPath]; |
| [item configureCell:cell]; |
| return cell; |
| } |
| |
| - (UICollectionReusableView*)collectionView:(UICollectionView*)collectionView |
| viewForSupplementaryElementOfKind:(NSString*)kind |
| atIndexPath:(NSIndexPath*)indexPath { |
| CollectionViewItem* item = nil; |
| UIAccessibilityTraits traits = UIAccessibilityTraitNone; |
| if ([kind isEqualToString:UICollectionElementKindSectionHeader]) { |
| item = [self.collectionViewModel headerForSection:indexPath.section]; |
| traits = UIAccessibilityTraitHeader; |
| } else if ([kind isEqualToString:UICollectionElementKindSectionFooter]) { |
| item = [self.collectionViewModel footerForSection:indexPath.section]; |
| } else { |
| return [super collectionView:collectionView |
| viewForSupplementaryElementOfKind:kind |
| atIndexPath:indexPath]; |
| } |
| |
| Class cellClass = [item cellClass]; |
| NSString* reuseIdentifier = NSStringFromClass(cellClass); |
| [self.collectionView registerClass:cellClass |
| forSupplementaryViewOfKind:kind |
| withReuseIdentifier:reuseIdentifier]; |
| MDCCollectionViewCell* cell = [self.collectionView |
| dequeueReusableSupplementaryViewOfKind:kind |
| withReuseIdentifier:reuseIdentifier |
| forIndexPath:indexPath]; |
| [item configureCell:cell]; |
| cell.accessibilityTraits |= traits; |
| return cell; |
| }; |
| |
| #pragma mark - UICollectionViewDelegateFlowLayout |
| |
| - (CGSize)collectionView:(UICollectionView*)collectionView |
| layout: |
| (UICollectionViewLayout*)collectionViewLayout |
| referenceSizeForHeaderInSection:(NSInteger)section { |
| CollectionViewItem* item = |
| [self.collectionViewModel headerForSection:section]; |
| |
| if (item) { |
| // TODO(crbug.com/635604): Support arbitrary sized headers. |
| return CGSizeMake(0, MDCCellDefaultOneLineHeight); |
| } |
| return CGSizeZero; |
| } |
| |
| - (CGSize)collectionView:(UICollectionView*)collectionView |
| layout: |
| (UICollectionViewLayout*)collectionViewLayout |
| referenceSizeForFooterInSection:(NSInteger)section { |
| CollectionViewItem* item = |
| [self.collectionViewModel footerForSection:section]; |
| |
| if (item) { |
| // TODO(crbug.com/635604): Support arbitrary sized footers. |
| return CGSizeMake(0, MDCCellDefaultOneLineHeight); |
| } |
| return CGSizeZero; |
| } |
| |
| #pragma mark UIScrollViewDelegate |
| |
| - (void)scrollViewDidScroll:(UIScrollView*)scrollView { |
| MDCFlexibleHeaderView* headerView = |
| self.appBar.headerViewController.headerView; |
| if (scrollView == headerView.trackingScrollView) { |
| [headerView trackingScrollViewDidScroll]; |
| } |
| } |
| |
| - (void)scrollViewDidEndDecelerating:(UIScrollView*)scrollView { |
| MDCFlexibleHeaderView* headerView = |
| self.appBar.headerViewController.headerView; |
| if (scrollView == headerView.trackingScrollView) { |
| [headerView trackingScrollViewDidEndDecelerating]; |
| } |
| } |
| |
| - (void)scrollViewDidEndDragging:(UIScrollView*)scrollView |
| willDecelerate:(BOOL)decelerate { |
| MDCFlexibleHeaderView* headerView = |
| self.appBar.headerViewController.headerView; |
| if (scrollView == headerView.trackingScrollView) { |
| [headerView trackingScrollViewDidEndDraggingWillDecelerate:decelerate]; |
| } |
| } |
| |
| - (void)scrollViewWillEndDragging:(UIScrollView*)scrollView |
| withVelocity:(CGPoint)velocity |
| targetContentOffset:(inout CGPoint*)targetContentOffset { |
| MDCFlexibleHeaderView* headerView = |
| self.appBar.headerViewController.headerView; |
| if (scrollView == headerView.trackingScrollView) { |
| [headerView |
| trackingScrollViewWillEndDraggingWithVelocity:velocity |
| targetContentOffset:targetContentOffset]; |
| } |
| } |
| |
| #pragma mark - Private |
| |
| // Reconfigures the cell at |indexPath| by calling |configureCell:| with |item|. |
| - (void)reconfigureCellAtIndexPath:(NSIndexPath*)indexPath |
| withItem:(CollectionViewItem*)item { |
| MDCCollectionViewCell* cell = |
| base::mac::ObjCCastStrict<MDCCollectionViewCell>( |
| [self.collectionView cellForItemAtIndexPath:indexPath]); |
| |
| // |cell| may be nil if the row is not currently on screen. |
| if (cell) { |
| [item configureCell:cell]; |
| } |
| } |
| |
| @end |