| // 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_model.h" |
| |
| #include "base/logging.h" |
| #import "ios/chrome/browser/ui/collection_view/cells/collection_view_item.h" |
| |
| #if !defined(__has_feature) || !__has_feature(objc_arc) |
| #error "This file requires ARC support." |
| #endif |
| |
| namespace { |
| typedef NSMutableArray<CollectionViewItem*> SectionItems; |
| } |
| |
| @implementation CollectionViewModel { |
| // Ordered list of section identifiers, one per section in the model. |
| NSMutableArray<NSNumber*>* _sectionIdentifiers; |
| |
| // The lists of section items, one per section. |
| NSMutableArray<SectionItems*>* _sections; |
| |
| // Maps from section identifier to header and footer. |
| NSMutableDictionary<NSNumber*, CollectionViewItem*>* _headers; |
| NSMutableDictionary<NSNumber*, CollectionViewItem*>* _footers; |
| } |
| |
| - (instancetype)init { |
| if ((self = [super init])) { |
| _sectionIdentifiers = [[NSMutableArray alloc] init]; |
| _sections = [[NSMutableArray alloc] init]; |
| _headers = [[NSMutableDictionary alloc] init]; |
| _footers = [[NSMutableDictionary alloc] init]; |
| } |
| return self; |
| } |
| |
| #pragma mark Modification methods |
| |
| - (void)addSectionWithIdentifier:(NSInteger)sectionIdentifier { |
| DCHECK_GE(sectionIdentifier, kSectionIdentifierEnumZero); |
| DCHECK_EQ(static_cast<NSUInteger>(NSNotFound), |
| [self internalSectionForIdentifier:sectionIdentifier]); |
| [_sectionIdentifiers addObject:@(sectionIdentifier)]; |
| |
| SectionItems* section = [[SectionItems alloc] init]; |
| [_sections addObject:section]; |
| } |
| |
| - (void)insertSectionWithIdentifier:(NSInteger)sectionIdentifier |
| atIndex:(NSUInteger)index { |
| DCHECK_GE(sectionIdentifier, kSectionIdentifierEnumZero); |
| DCHECK_EQ(static_cast<NSUInteger>(NSNotFound), |
| [self internalSectionForIdentifier:sectionIdentifier]); |
| DCHECK_LE(index, [_sections count]); |
| |
| [_sectionIdentifiers insertObject:@(sectionIdentifier) atIndex:index]; |
| |
| SectionItems* section = [[SectionItems alloc] init]; |
| [_sections insertObject:section atIndex:index]; |
| } |
| |
| - (void)addItem:(CollectionViewItem*)item |
| toSectionWithIdentifier:(NSInteger)sectionIdentifier { |
| DCHECK_GE(item.type, kItemTypeEnumZero); |
| NSInteger section = [self sectionForSectionIdentifier:sectionIdentifier]; |
| SectionItems* items = [_sections objectAtIndex:section]; |
| [items addObject:item]; |
| } |
| |
| - (void)insertItem:(CollectionViewItem*)item |
| inSectionWithIdentifier:(NSInteger)sectionIdentifier |
| atIndex:(NSUInteger)index { |
| DCHECK_GE(item.type, kItemTypeEnumZero); |
| NSInteger section = [self sectionForSectionIdentifier:sectionIdentifier]; |
| SectionItems* items = [_sections objectAtIndex:section]; |
| DCHECK(index <= [items count]); |
| [items insertObject:item atIndex:index]; |
| } |
| |
| - (void)removeItemWithType:(NSInteger)itemType |
| fromSectionWithIdentifier:(NSInteger)sectionIdentifier { |
| [self removeItemWithType:itemType |
| fromSectionWithIdentifier:sectionIdentifier |
| atIndex:0]; |
| } |
| |
| - (void)removeItemWithType:(NSInteger)itemType |
| fromSectionWithIdentifier:(NSInteger)sectionIdentifier |
| atIndex:(NSUInteger)index { |
| NSInteger section = [self sectionForSectionIdentifier:sectionIdentifier]; |
| SectionItems* items = [_sections objectAtIndex:section]; |
| NSInteger item = |
| [self itemForItemType:itemType inSectionItems:items atIndex:index]; |
| DCHECK_NE(NSNotFound, item); |
| [items removeObjectAtIndex:item]; |
| } |
| |
| - (void)removeSectionWithIdentifier:(NSInteger)sectionIdentifier { |
| NSInteger section = [self sectionForSectionIdentifier:sectionIdentifier]; |
| [_sectionIdentifiers removeObjectAtIndex:section]; |
| [_sections removeObjectAtIndex:section]; |
| } |
| |
| - (void)setHeader:(CollectionViewItem*)header |
| forSectionWithIdentifier:(NSInteger)sectionIdentifier { |
| NSNumber* key = [NSNumber numberWithInteger:sectionIdentifier]; |
| if (header) { |
| [_headers setObject:header forKey:key]; |
| } else { |
| [_headers removeObjectForKey:key]; |
| } |
| } |
| |
| - (void)setFooter:(CollectionViewItem*)footer |
| forSectionWithIdentifier:(NSInteger)sectionIdentifier { |
| NSNumber* key = [NSNumber numberWithInteger:sectionIdentifier]; |
| if (footer) { |
| [_footers setObject:footer forKey:key]; |
| } else { |
| [_footers removeObjectForKey:key]; |
| } |
| } |
| |
| #pragma mark Query model coordinates from index paths |
| |
| - (NSInteger)sectionIdentifierForSection:(NSInteger)section { |
| DCHECK_LT(static_cast<NSUInteger>(section), [_sectionIdentifiers count]); |
| return [[_sectionIdentifiers objectAtIndex:section] integerValue]; |
| } |
| |
| - (NSInteger)itemTypeForIndexPath:(NSIndexPath*)indexPath { |
| return [self itemAtIndexPath:indexPath].type; |
| } |
| |
| - (NSUInteger)indexInItemTypeForIndexPath:(NSIndexPath*)indexPath { |
| DCHECK_LT(static_cast<NSUInteger>(indexPath.section), [_sections count]); |
| SectionItems* items = [_sections objectAtIndex:indexPath.section]; |
| |
| CollectionViewItem* item = [self itemAtIndexPath:indexPath]; |
| NSUInteger indexInItemType = |
| [self indexInItemTypeForItem:item inSectionItems:items]; |
| return indexInItemType; |
| } |
| |
| #pragma mark Query items from index paths |
| |
| - (BOOL)hasItemAtIndexPath:(NSIndexPath*)indexPath { |
| if (!indexPath) |
| return NO; |
| |
| if (static_cast<NSUInteger>(indexPath.section) < [_sections count]) { |
| SectionItems* items = [_sections objectAtIndex:indexPath.section]; |
| return static_cast<NSUInteger>(indexPath.item) < [items count]; |
| } |
| return NO; |
| } |
| |
| - (CollectionViewItem*)itemAtIndexPath:(NSIndexPath*)indexPath { |
| DCHECK(indexPath); |
| DCHECK_LT(static_cast<NSUInteger>(indexPath.section), [_sections count]); |
| SectionItems* items = [_sections objectAtIndex:indexPath.section]; |
| |
| DCHECK_LT(static_cast<NSUInteger>(indexPath.item), [items count]); |
| return [items objectAtIndex:indexPath.item]; |
| } |
| |
| - (CollectionViewItem*)headerForSection:(NSInteger)section { |
| NSInteger sectionIdentifier = [self sectionIdentifierForSection:section]; |
| NSNumber* key = [NSNumber numberWithInteger:sectionIdentifier]; |
| return [_headers objectForKey:key]; |
| } |
| |
| - (CollectionViewItem*)footerForSection:(NSInteger)section { |
| NSInteger sectionIdentifier = [self sectionIdentifierForSection:section]; |
| NSNumber* key = [NSNumber numberWithInteger:sectionIdentifier]; |
| return [_footers objectForKey:key]; |
| } |
| |
| - (NSArray<CollectionViewItem*>*)itemsInSectionWithIdentifier: |
| (NSInteger)sectionIdentifier { |
| NSInteger section = [self sectionForSectionIdentifier:sectionIdentifier]; |
| DCHECK_LT(static_cast<NSUInteger>(section), [_sections count]); |
| return [_sections objectAtIndex:section]; |
| } |
| |
| - (CollectionViewItem*)headerForSectionWithIdentifier: |
| (NSInteger)sectionIdentifier { |
| NSNumber* key = [NSNumber numberWithInteger:sectionIdentifier]; |
| return [_headers objectForKey:key]; |
| } |
| |
| - (CollectionViewItem*)footerForSectionWithIdentifier: |
| (NSInteger)sectionIdentifier { |
| NSNumber* key = [NSNumber numberWithInteger:sectionIdentifier]; |
| return [_footers objectForKey:key]; |
| } |
| |
| #pragma mark Query index paths from model coordinates |
| |
| - (BOOL)hasSectionForSectionIdentifier:(NSInteger)sectionIdentifier { |
| NSUInteger section = [self internalSectionForIdentifier:sectionIdentifier]; |
| return section != static_cast<NSUInteger>(NSNotFound); |
| } |
| |
| - (NSInteger)sectionForSectionIdentifier:(NSInteger)sectionIdentifier { |
| NSUInteger section = [self internalSectionForIdentifier:sectionIdentifier]; |
| DCHECK_NE(static_cast<NSUInteger>(NSNotFound), section); |
| return section; |
| } |
| |
| - (BOOL)hasItemForItemType:(NSInteger)itemType |
| sectionIdentifier:(NSInteger)sectionIdentifier { |
| return [self hasItemForItemType:itemType |
| sectionIdentifier:sectionIdentifier |
| atIndex:0]; |
| } |
| |
| - (NSIndexPath*)indexPathForItemType:(NSInteger)itemType |
| sectionIdentifier:(NSInteger)sectionIdentifier { |
| return [self indexPathForItemType:itemType |
| sectionIdentifier:sectionIdentifier |
| atIndex:0]; |
| } |
| |
| - (BOOL)hasItemForItemType:(NSInteger)itemType |
| sectionIdentifier:(NSInteger)sectionIdentifier |
| atIndex:(NSUInteger)index { |
| if (![self hasSectionForSectionIdentifier:sectionIdentifier]) { |
| return NO; |
| } |
| NSInteger section = [self sectionForSectionIdentifier:sectionIdentifier]; |
| SectionItems* items = [_sections objectAtIndex:section]; |
| NSInteger item = |
| [self itemForItemType:itemType inSectionItems:items atIndex:index]; |
| return item != NSNotFound; |
| } |
| |
| - (NSIndexPath*)indexPathForItemType:(NSInteger)itemType |
| sectionIdentifier:(NSInteger)sectionIdentifier |
| atIndex:(NSUInteger)index { |
| NSInteger section = [self sectionForSectionIdentifier:sectionIdentifier]; |
| SectionItems* items = [_sections objectAtIndex:section]; |
| NSInteger item = |
| [self itemForItemType:itemType inSectionItems:items atIndex:index]; |
| return [NSIndexPath indexPathForItem:item inSection:section]; |
| } |
| |
| #pragma mark Query index paths from items |
| |
| - (BOOL)hasItem:(CollectionViewItem*)item |
| inSectionWithIdentifier:(NSInteger)sectionIdentifier { |
| return [[self itemsInSectionWithIdentifier:sectionIdentifier] |
| indexOfObject:item] != NSNotFound; |
| } |
| |
| - (NSIndexPath*)indexPathForItem:(CollectionViewItem*)item |
| inSectionWithIdentifier:(NSInteger)sectionIdentifier { |
| NSArray* itemsInSection = |
| [self itemsInSectionWithIdentifier:sectionIdentifier]; |
| |
| NSInteger section = [self sectionForSectionIdentifier:sectionIdentifier]; |
| NSInteger itemIndex = [itemsInSection indexOfObject:item]; |
| DCHECK_NE(NSNotFound, itemIndex); |
| return [NSIndexPath indexPathForItem:itemIndex inSection:section]; |
| } |
| |
| #pragma mark UICollectionView data sourcing |
| |
| - (NSInteger)numberOfSections { |
| return [_sections count]; |
| } |
| |
| - (NSInteger)numberOfItemsInSection:(NSInteger)section { |
| DCHECK_LT(static_cast<NSUInteger>(section), [_sections count]); |
| SectionItems* items = [_sections objectAtIndex:section]; |
| return items.count; |
| } |
| |
| #pragma mark Private methods |
| |
| // Returns the section for the given section identifier. If the section |
| // identifier is not found, NSNotFound is returned. |
| - (NSUInteger)internalSectionForIdentifier:(NSInteger)sectionIdentifier { |
| return [_sectionIdentifiers indexOfObject:@(sectionIdentifier)]; |
| } |
| |
| // Returns the item for the given item type in the list of items, at the |
| // given index. If no item is found with the given type, NSNotFound is returned. |
| - (NSUInteger)itemForItemType:(NSInteger)itemType |
| inSectionItems:(SectionItems*)sectionItems |
| atIndex:(NSUInteger)index { |
| __block NSUInteger item = NSNotFound; |
| __block NSUInteger indexInItemType = 0; |
| [sectionItems enumerateObjectsUsingBlock:^(CollectionViewItem* obj, |
| NSUInteger idx, BOOL* stop) { |
| if (obj.type == itemType) { |
| if (indexInItemType == index) { |
| item = idx; |
| *stop = YES; |
| } else { |
| indexInItemType++; |
| } |
| } |
| }]; |
| return item; |
| } |
| |
| // Returns |item|'s index among all the items of the same type in the given |
| // section items. |item| must belong to |sectionItems|. |
| - (NSUInteger)indexInItemTypeForItem:(CollectionViewItem*)item |
| inSectionItems:(SectionItems*)sectionItems { |
| DCHECK([sectionItems containsObject:item]); |
| BOOL found = NO; |
| NSUInteger indexInItemType = 0; |
| for (CollectionViewItem* sectionItem in sectionItems) { |
| if (sectionItem == item) { |
| found = YES; |
| break; |
| } |
| if (sectionItem.type == item.type) { |
| indexInItemType++; |
| } |
| } |
| DCHECK(found); |
| return indexInItemType; |
| } |
| |
| @end |