Index: ios/chrome/browser/ui/collection_view/collection_view_controller.mm |
diff --git a/ios/chrome/browser/ui/collection_view/collection_view_controller.mm b/ios/chrome/browser/ui/collection_view/collection_view_controller.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..75d4fa7a787a0182b96a05ba95eb49495e12708c |
--- /dev/null |
+++ b/ios/chrome/browser/ui/collection_view/collection_view_controller.mm |
@@ -0,0 +1,267 @@ |
+// 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 "base/mac/scoped_nsobject.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" |
+ |
+@implementation CollectionViewController { |
+ // The implementation of this controller follows the guidelines from |
+ // https://github.com/material-components/material-components-ios/tree/develop/components/AppBar |
+ base::scoped_nsobject<MDCAppBar> _appBar; |
+ base::scoped_nsobject<CollectionViewModel> _collectionViewModel; |
+} |
+ |
+- (instancetype)initWithStyle:(CollectionViewControllerStyle)style { |
+ UICollectionViewLayout* layout = |
+ [[[MDCCollectionViewFlowLayout alloc] init] autorelease]; |
+ self = [super initWithCollectionViewLayout:layout]; |
+ if (self) { |
+ if (style == CollectionViewControllerStyleAppBar) { |
+ _appBar.reset([[MDCAppBar alloc] init]); |
+ [self addChildViewController:_appBar.get().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; |
+} |
+ |
+- (MDCAppBar*)appBar { |
+ return _appBar.get(); |
+} |
+ |
+- (CollectionViewModel*)collectionViewModel { |
+ return _collectionViewModel.get(); |
+} |
+ |
+- (void)loadModel { |
+ _collectionViewModel.reset([[CollectionViewModel alloc] init]); |
+} |
+ |
+- (void)reconfigureCellsForItems:(NSArray*)items |
+ inSectionWithIdentifier:(NSInteger)sectionIdentifier { |
+ for (CollectionViewItem* item in items) { |
+ NSIndexPath* indexPath = |
+ [_collectionViewModel indexPathForItem:item |
+ inSectionWithIdentifier:sectionIdentifier]; |
+ 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]; |
+ } |
+ } |
+} |
+ |
+#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 = |
+ [_collectionViewModel sectionIdentifierForSection:indexPath.section]; |
+ NSInteger itemType = [_collectionViewModel itemTypeForIndexPath:indexPath]; |
+ NSUInteger index = |
+ [_collectionViewModel indexInItemTypeForIndexPath:indexPath]; |
+ [_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. |
+ base::scoped_nsobject<CollectionViewItem> item( |
+ [[self.collectionViewModel itemAtIndexPath:indexPath] retain]); |
+ |
+ // 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 [_collectionViewModel numberOfSections]; |
+} |
+ |
+- (NSInteger)collectionView:(UICollectionView*)collectionView |
+ numberOfItemsInSection:(NSInteger)section { |
+ return [_collectionViewModel numberOfItemsInSection:section]; |
+} |
+ |
+- (UICollectionViewCell*)collectionView:(UICollectionView*)collectionView |
+ cellForItemAtIndexPath:(NSIndexPath*)indexPath { |
+ CollectionViewItem* item = [_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 = [_collectionViewModel headerForSection:indexPath.section]; |
+ traits = UIAccessibilityTraitHeader; |
+ } else if ([kind isEqualToString:UICollectionElementKindSectionFooter]) { |
+ item = [_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 = [_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 = [_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]; |
+ } |
+} |
+ |
+@end |