diff --git a/CHTCollectionViewWaterfallLayout.h b/CHTCollectionViewWaterfallLayout.h index 5d3d4a8..0cd736c 100644 --- a/CHTCollectionViewWaterfallLayout.h +++ b/CHTCollectionViewWaterfallLayout.h @@ -11,9 +11,9 @@ * Enumerated structure to define direction in which items can be rendered. */ typedef NS_ENUM (NSUInteger, CHTCollectionViewWaterfallLayoutItemRenderDirection) { - CHTCollectionViewWaterfallLayoutItemRenderDirectionShortestFirst, - CHTCollectionViewWaterfallLayoutItemRenderDirectionLeftToRight, - CHTCollectionViewWaterfallLayoutItemRenderDirectionRightToLeft + CHTCollectionViewWaterfallLayoutItemRenderDirectionShortestFirst, + CHTCollectionViewWaterfallLayoutItemRenderDirectionLeftToRight, + CHTCollectionViewWaterfallLayoutItemRenderDirectionRightToLeft }; /** @@ -294,4 +294,12 @@ extern NSString *const CHTCollectionElementKindSectionFooter; */ - (CGFloat)itemWidthInSectionAtIndex:(NSInteger)section; +/** + * @brief Set the headers to float when scrolling + * @discussion + * EXPERIMENTAL!! if YES, will float headers, where available + * Default: NO + */ +@property (nonatomic, assign) BOOL floatHeaders; + @end diff --git a/CHTCollectionViewWaterfallLayout.m b/CHTCollectionViewWaterfallLayout.m index f754c0f..97ad5ff 100644 --- a/CHTCollectionViewWaterfallLayout.m +++ b/CHTCollectionViewWaterfallLayout.m @@ -34,410 +34,489 @@ @implementation CHTCollectionViewWaterfallLayout static const NSInteger unionSize = 20; static CGFloat CHTFloorCGFloat(CGFloat value) { - CGFloat scale = [UIScreen mainScreen].scale; - return floor(value * scale) / scale; + CGFloat scale = [UIScreen mainScreen].scale; + return floor(value * scale) / scale; } #pragma mark - Public Accessors + +-(void)setFloatHeaders:(BOOL)floatHeaders{ + if (_floatHeaders != floatHeaders){ + _floatHeaders = floatHeaders; + [self invalidateLayout]; + } +} + + - (void)setColumnCount:(NSInteger)columnCount { - if (_columnCount != columnCount) { - _columnCount = columnCount; - [self invalidateLayout]; - } + if (_columnCount != columnCount) { + _columnCount = columnCount; + [self invalidateLayout]; + } } - (void)setMinimumColumnSpacing:(CGFloat)minimumColumnSpacing { - if (_minimumColumnSpacing != minimumColumnSpacing) { - _minimumColumnSpacing = minimumColumnSpacing; - [self invalidateLayout]; - } + if (_minimumColumnSpacing != minimumColumnSpacing) { + _minimumColumnSpacing = minimumColumnSpacing; + [self invalidateLayout]; + } } - (void)setMinimumInteritemSpacing:(CGFloat)minimumInteritemSpacing { - if (_minimumInteritemSpacing != minimumInteritemSpacing) { - _minimumInteritemSpacing = minimumInteritemSpacing; - [self invalidateLayout]; - } + if (_minimumInteritemSpacing != minimumInteritemSpacing) { + _minimumInteritemSpacing = minimumInteritemSpacing; + [self invalidateLayout]; + } } - (void)setHeaderHeight:(CGFloat)headerHeight { - if (_headerHeight != headerHeight) { - _headerHeight = headerHeight; - [self invalidateLayout]; - } + if (_headerHeight != headerHeight) { + _headerHeight = headerHeight; + [self invalidateLayout]; + } } - (void)setFooterHeight:(CGFloat)footerHeight { - if (_footerHeight != footerHeight) { - _footerHeight = footerHeight; - [self invalidateLayout]; - } + if (_footerHeight != footerHeight) { + _footerHeight = footerHeight; + [self invalidateLayout]; + } } - (void)setHeaderInset:(UIEdgeInsets)headerInset { - if (!UIEdgeInsetsEqualToEdgeInsets(_headerInset, headerInset)) { - _headerInset = headerInset; - [self invalidateLayout]; - } + if (!UIEdgeInsetsEqualToEdgeInsets(_headerInset, headerInset)) { + _headerInset = headerInset; + [self invalidateLayout]; + } } - (void)setFooterInset:(UIEdgeInsets)footerInset { - if (!UIEdgeInsetsEqualToEdgeInsets(_footerInset, footerInset)) { - _footerInset = footerInset; - [self invalidateLayout]; - } + if (!UIEdgeInsetsEqualToEdgeInsets(_footerInset, footerInset)) { + _footerInset = footerInset; + [self invalidateLayout]; + } } - (void)setSectionInset:(UIEdgeInsets)sectionInset { - if (!UIEdgeInsetsEqualToEdgeInsets(_sectionInset, sectionInset)) { - _sectionInset = sectionInset; - [self invalidateLayout]; - } + if (!UIEdgeInsetsEqualToEdgeInsets(_sectionInset, sectionInset)) { + _sectionInset = sectionInset; + [self invalidateLayout]; + } } - (void)setItemRenderDirection:(CHTCollectionViewWaterfallLayoutItemRenderDirection)itemRenderDirection { - if (_itemRenderDirection != itemRenderDirection) { - _itemRenderDirection = itemRenderDirection; - [self invalidateLayout]; - } + if (_itemRenderDirection != itemRenderDirection) { + _itemRenderDirection = itemRenderDirection; + [self invalidateLayout]; + } } - (NSInteger)columnCountForSection:(NSInteger)section { - if ([self.delegate respondsToSelector:@selector(collectionView:layout:columnCountForSection:)]) { - return [self.delegate collectionView:self.collectionView layout:self columnCountForSection:section]; - } else { - return self.columnCount; - } + if ([self.delegate respondsToSelector:@selector(collectionView:layout:columnCountForSection:)]) { + return [self.delegate collectionView:self.collectionView layout:self columnCountForSection:section]; + } else { + return self.columnCount; + } } - (CGFloat)itemWidthInSectionAtIndex:(NSInteger)section { - UIEdgeInsets sectionInset; - if ([self.delegate respondsToSelector:@selector(collectionView:layout:insetForSectionAtIndex:)]) { - sectionInset = [self.delegate collectionView:self.collectionView layout:self insetForSectionAtIndex:section]; - } else { - sectionInset = self.sectionInset; - } - CGFloat width = self.collectionView.frame.size.width - sectionInset.left - sectionInset.right; - NSInteger columnCount = [self columnCountForSection:section]; - return CHTFloorCGFloat((width - (columnCount - 1) * self.minimumColumnSpacing) / columnCount); + UIEdgeInsets sectionInset; + if ([self.delegate respondsToSelector:@selector(collectionView:layout:insetForSectionAtIndex:)]) { + sectionInset = [self.delegate collectionView:self.collectionView layout:self insetForSectionAtIndex:section]; + } else { + sectionInset = self.sectionInset; + } + CGFloat width = self.collectionView.frame.size.width - sectionInset.left - sectionInset.right; + NSInteger columnCount = [self columnCountForSection:section]; + return CHTFloorCGFloat((width - (columnCount - 1) * self.minimumColumnSpacing) / columnCount); } #pragma mark - Private Accessors - (NSMutableDictionary *)headersAttribute { - if (!_headersAttribute) { - _headersAttribute = [NSMutableDictionary dictionary]; - } - return _headersAttribute; + if (!_headersAttribute) { + _headersAttribute = [NSMutableDictionary dictionary]; + } + return _headersAttribute; } - (NSMutableDictionary *)footersAttribute { - if (!_footersAttribute) { - _footersAttribute = [NSMutableDictionary dictionary]; - } - return _footersAttribute; + if (!_footersAttribute) { + _footersAttribute = [NSMutableDictionary dictionary]; + } + return _footersAttribute; } - (NSMutableArray *)unionRects { - if (!_unionRects) { - _unionRects = [NSMutableArray array]; - } - return _unionRects; + if (!_unionRects) { + _unionRects = [NSMutableArray array]; + } + return _unionRects; } - (NSMutableArray *)columnHeights { - if (!_columnHeights) { - _columnHeights = [NSMutableArray array]; - } - return _columnHeights; + if (!_columnHeights) { + _columnHeights = [NSMutableArray array]; + } + return _columnHeights; } - (NSMutableArray *)allItemAttributes { - if (!_allItemAttributes) { - _allItemAttributes = [NSMutableArray array]; - } - return _allItemAttributes; + if (!_allItemAttributes) { + _allItemAttributes = [NSMutableArray array]; + } + return _allItemAttributes; } - (NSMutableArray *)sectionItemAttributes { - if (!_sectionItemAttributes) { - _sectionItemAttributes = [NSMutableArray array]; - } - return _sectionItemAttributes; + if (!_sectionItemAttributes) { + _sectionItemAttributes = [NSMutableArray array]; + } + return _sectionItemAttributes; } - (id )delegate { - return (id )self.collectionView.delegate; + return (id )self.collectionView.delegate; } #pragma mark - Init - (void)commonInit { - _columnCount = 2; - _minimumColumnSpacing = 10; - _minimumInteritemSpacing = 10; - _headerHeight = 0; - _footerHeight = 0; - _sectionInset = UIEdgeInsetsZero; - _headerInset = UIEdgeInsetsZero; - _footerInset = UIEdgeInsetsZero; - _itemRenderDirection = CHTCollectionViewWaterfallLayoutItemRenderDirectionShortestFirst; + _floatHeaders = NO; + _columnCount = 2; + _minimumColumnSpacing = 10; + _minimumInteritemSpacing = 10; + _headerHeight = 0; + _footerHeight = 0; + _sectionInset = UIEdgeInsetsZero; + _headerInset = UIEdgeInsetsZero; + _footerInset = UIEdgeInsetsZero; + _itemRenderDirection = CHTCollectionViewWaterfallLayoutItemRenderDirectionShortestFirst; } - (id)init { - if (self = [super init]) { - [self commonInit]; - } - return self; + if (self = [super init]) { + [self commonInit]; + } + return self; } - (id)initWithCoder:(NSCoder *)aDecoder { - if (self = [super initWithCoder:aDecoder]) { - [self commonInit]; - } - return self; + if (self = [super initWithCoder:aDecoder]) { + [self commonInit]; + } + return self; } #pragma mark - Methods to Override - (void)prepareLayout { - [super prepareLayout]; - - NSInteger numberOfSections = [self.collectionView numberOfSections]; - if (numberOfSections == 0) { - return; - } - - NSAssert([self.delegate conformsToProtocol:@protocol(CHTCollectionViewDelegateWaterfallLayout)], @"UICollectionView's delegate should conform to CHTCollectionViewDelegateWaterfallLayout protocol"); - NSAssert(self.columnCount > 0 || [self.delegate respondsToSelector:@selector(collectionView:layout:columnCountForSection:)], @"UICollectionViewWaterfallLayout's columnCount should be greater than 0, or delegate must implement columnCountForSection:"); - - // Initialize variables - NSInteger idx = 0; - - [self.headersAttribute removeAllObjects]; - [self.footersAttribute removeAllObjects]; - [self.unionRects removeAllObjects]; - [self.columnHeights removeAllObjects]; - [self.allItemAttributes removeAllObjects]; - [self.sectionItemAttributes removeAllObjects]; - - for (NSInteger section = 0; section < numberOfSections; section++) { - NSInteger columnCount = [self columnCountForSection:section]; - NSMutableArray *sectionColumnHeights = [NSMutableArray arrayWithCapacity:columnCount]; - for (idx = 0; idx < columnCount; idx++) { - [sectionColumnHeights addObject:@(0)]; - } - [self.columnHeights addObject:sectionColumnHeights]; - } - // Create attributes - CGFloat top = 0; - UICollectionViewLayoutAttributes *attributes; - - for (NSInteger section = 0; section < numberOfSections; ++section) { - /* - * 1. Get section-specific metrics (minimumInteritemSpacing, sectionInset) - */ - CGFloat minimumInteritemSpacing; - if ([self.delegate respondsToSelector:@selector(collectionView:layout:minimumInteritemSpacingForSectionAtIndex:)]) { - minimumInteritemSpacing = [self.delegate collectionView:self.collectionView layout:self minimumInteritemSpacingForSectionAtIndex:section]; - } else { - minimumInteritemSpacing = self.minimumInteritemSpacing; - } - - UIEdgeInsets sectionInset; - if ([self.delegate respondsToSelector:@selector(collectionView:layout:insetForSectionAtIndex:)]) { - sectionInset = [self.delegate collectionView:self.collectionView layout:self insetForSectionAtIndex:section]; - } else { - sectionInset = self.sectionInset; - } - - CGFloat width = self.collectionView.frame.size.width - sectionInset.left - sectionInset.right; - NSInteger columnCount = [self columnCountForSection:section]; - CGFloat itemWidth = CHTFloorCGFloat((width - (columnCount - 1) * self.minimumColumnSpacing) / columnCount); - - /* - * 2. Section header - */ - CGFloat headerHeight; - if ([self.delegate respondsToSelector:@selector(collectionView:layout:heightForHeaderInSection:)]) { - headerHeight = [self.delegate collectionView:self.collectionView layout:self heightForHeaderInSection:section]; - } else { - headerHeight = self.headerHeight; + [super prepareLayout]; + + NSInteger numberOfSections = [self.collectionView numberOfSections]; + if (numberOfSections == 0) { + return; } - - UIEdgeInsets headerInset; - if ([self.delegate respondsToSelector:@selector(collectionView:layout:insetForHeaderInSection:)]) { - headerInset = [self.delegate collectionView:self.collectionView layout:self insetForHeaderInSection:section]; - } else { - headerInset = self.headerInset; - } - - top += headerInset.top; - - if (headerHeight > 0) { - attributes = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:CHTCollectionElementKindSectionHeader withIndexPath:[NSIndexPath indexPathForItem:0 inSection:section]]; - attributes.frame = CGRectMake(headerInset.left, - top, - self.collectionView.frame.size.width - (headerInset.left + headerInset.right), - headerHeight); - - self.headersAttribute[@(section)] = attributes; - [self.allItemAttributes addObject:attributes]; - - top = CGRectGetMaxY(attributes.frame) + headerInset.bottom; - } - - top += sectionInset.top; - for (idx = 0; idx < columnCount; idx++) { - self.columnHeights[section][idx] = @(top); - } - - /* - * 3. Section items - */ - NSInteger itemCount = [self.collectionView numberOfItemsInSection:section]; - NSMutableArray *itemAttributes = [NSMutableArray arrayWithCapacity:itemCount]; - - // Item will be put into shortest column. - for (idx = 0; idx < itemCount; idx++) { - NSIndexPath *indexPath = [NSIndexPath indexPathForItem:idx inSection:section]; - NSUInteger columnIndex = [self nextColumnIndexForItem:idx inSection:section]; - CGFloat xOffset = sectionInset.left + (itemWidth + self.minimumColumnSpacing) * columnIndex; - CGFloat yOffset = [self.columnHeights[section][columnIndex] floatValue]; - CGSize itemSize = [self.delegate collectionView:self.collectionView layout:self sizeForItemAtIndexPath:indexPath]; - CGFloat itemHeight = 0; - if (itemSize.height > 0 && itemSize.width > 0) { - itemHeight = CHTFloorCGFloat(itemSize.height * itemWidth / itemSize.width); - } - - attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; - attributes.frame = CGRectMake(xOffset, yOffset, itemWidth, itemHeight); - [itemAttributes addObject:attributes]; - [self.allItemAttributes addObject:attributes]; - self.columnHeights[section][columnIndex] = @(CGRectGetMaxY(attributes.frame) + minimumInteritemSpacing); - } - - [self.sectionItemAttributes addObject:itemAttributes]; - - /* - * 4. Section footer - */ - CGFloat footerHeight; - NSUInteger columnIndex = [self longestColumnIndexInSection:section]; - top = [self.columnHeights[section][columnIndex] floatValue] - minimumInteritemSpacing + sectionInset.bottom; - - if ([self.delegate respondsToSelector:@selector(collectionView:layout:heightForFooterInSection:)]) { - footerHeight = [self.delegate collectionView:self.collectionView layout:self heightForFooterInSection:section]; - } else { - footerHeight = self.footerHeight; + + NSAssert([self.delegate conformsToProtocol:@protocol(CHTCollectionViewDelegateWaterfallLayout)], @"UICollectionView's delegate should conform to CHTCollectionViewDelegateWaterfallLayout protocol"); + NSAssert(self.columnCount > 0 || [self.delegate respondsToSelector:@selector(collectionView:layout:columnCountForSection:)], @"UICollectionViewWaterfallLayout's columnCount should be greater than 0, or delegate must implement columnCountForSection:"); + + // Initialize variables + NSInteger idx = 0; + + [self.headersAttribute removeAllObjects]; + [self.footersAttribute removeAllObjects]; + [self.unionRects removeAllObjects]; + [self.columnHeights removeAllObjects]; + [self.allItemAttributes removeAllObjects]; + [self.sectionItemAttributes removeAllObjects]; + + for (NSInteger section = 0; section < numberOfSections; section++) { + NSInteger columnCount = [self columnCountForSection:section]; + NSMutableArray *sectionColumnHeights = [NSMutableArray arrayWithCapacity:columnCount]; + for (idx = 0; idx < columnCount; idx++) { + [sectionColumnHeights addObject:@(0)]; + } + [self.columnHeights addObject:sectionColumnHeights]; } - - UIEdgeInsets footerInset; - if ([self.delegate respondsToSelector:@selector(collectionView:layout:insetForFooterInSection:)]) { - footerInset = [self.delegate collectionView:self.collectionView layout:self insetForFooterInSection:section]; - } else { - footerInset = self.footerInset; + // Create attributes + CGFloat top = 0; + UICollectionViewLayoutAttributes *attributes; + + for (NSInteger section = 0; section < numberOfSections; ++section) { + /* + * 1. Get section-specific metrics (minimumInteritemSpacing, sectionInset) + */ + CGFloat minimumInteritemSpacing; + if ([self.delegate respondsToSelector:@selector(collectionView:layout:minimumInteritemSpacingForSectionAtIndex:)]) { + minimumInteritemSpacing = [self.delegate collectionView:self.collectionView layout:self minimumInteritemSpacingForSectionAtIndex:section]; + } else { + minimumInteritemSpacing = self.minimumInteritemSpacing; + } + + UIEdgeInsets sectionInset; + if ([self.delegate respondsToSelector:@selector(collectionView:layout:insetForSectionAtIndex:)]) { + sectionInset = [self.delegate collectionView:self.collectionView layout:self insetForSectionAtIndex:section]; + } else { + sectionInset = self.sectionInset; + } + + CGFloat width = self.collectionView.frame.size.width - sectionInset.left - sectionInset.right; + NSInteger columnCount = [self columnCountForSection:section]; + CGFloat itemWidth = CHTFloorCGFloat((width - (columnCount - 1) * self.minimumColumnSpacing) / columnCount); + + /* + * 2. Section header + */ + CGFloat headerHeight; + if ([self.delegate respondsToSelector:@selector(collectionView:layout:heightForHeaderInSection:)]) { + headerHeight = [self.delegate collectionView:self.collectionView layout:self heightForHeaderInSection:section]; + } else { + headerHeight = self.headerHeight; + } + + UIEdgeInsets headerInset; + if ([self.delegate respondsToSelector:@selector(collectionView:layout:insetForHeaderInSection:)]) { + headerInset = [self.delegate collectionView:self.collectionView layout:self insetForHeaderInSection:section]; + } else { + headerInset = self.headerInset; + } + + top += headerInset.top; + + if (headerHeight > 0) { + attributes = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:CHTCollectionElementKindSectionHeader withIndexPath:[NSIndexPath indexPathForItem:0 inSection:section]]; + attributes.frame = CGRectMake(headerInset.left, + top, + self.collectionView.frame.size.width - (headerInset.left + headerInset.right), + headerHeight); + + self.headersAttribute[@(section)] = attributes; + [self.allItemAttributes addObject:attributes]; + + top = CGRectGetMaxY(attributes.frame) + headerInset.bottom; + } + + top += sectionInset.top; + for (idx = 0; idx < columnCount; idx++) { + self.columnHeights[section][idx] = @(top); + } + + /* + * 3. Section items + */ + NSInteger itemCount = [self.collectionView numberOfItemsInSection:section]; + NSMutableArray *itemAttributes = [NSMutableArray arrayWithCapacity:itemCount]; + + // Item will be put into shortest column. + for (idx = 0; idx < itemCount; idx++) { + NSIndexPath *indexPath = [NSIndexPath indexPathForItem:idx inSection:section]; + NSUInteger columnIndex = [self nextColumnIndexForItem:idx inSection:section]; + CGFloat xOffset = sectionInset.left + (itemWidth + self.minimumColumnSpacing) * columnIndex; + CGFloat yOffset = [self.columnHeights[section][columnIndex] floatValue]; + CGSize itemSize = [self.delegate collectionView:self.collectionView layout:self sizeForItemAtIndexPath:indexPath]; + CGFloat itemHeight = 0; + if (itemSize.height > 0 && itemSize.width > 0) { + itemHeight = CHTFloorCGFloat(itemSize.height * itemWidth / itemSize.width); + } + + attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; + attributes.frame = CGRectMake(xOffset, yOffset, itemWidth, itemHeight); + [itemAttributes addObject:attributes]; + [self.allItemAttributes addObject:attributes]; + self.columnHeights[section][columnIndex] = @(CGRectGetMaxY(attributes.frame) + minimumInteritemSpacing); + } + + [self.sectionItemAttributes addObject:itemAttributes]; + + /* + * 4. Section footer + */ + CGFloat footerHeight; + NSUInteger columnIndex = [self longestColumnIndexInSection:section]; + top = [self.columnHeights[section][columnIndex] floatValue] - minimumInteritemSpacing + sectionInset.bottom; + + if ([self.delegate respondsToSelector:@selector(collectionView:layout:heightForFooterInSection:)]) { + footerHeight = [self.delegate collectionView:self.collectionView layout:self heightForFooterInSection:section]; + } else { + footerHeight = self.footerHeight; + } + + UIEdgeInsets footerInset; + if ([self.delegate respondsToSelector:@selector(collectionView:layout:insetForFooterInSection:)]) { + footerInset = [self.delegate collectionView:self.collectionView layout:self insetForFooterInSection:section]; + } else { + footerInset = self.footerInset; + } + + top += footerInset.top; + + if (footerHeight > 0) { + attributes = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:CHTCollectionElementKindSectionFooter withIndexPath:[NSIndexPath indexPathForItem:0 inSection:section]]; + attributes.frame = CGRectMake(footerInset.left, + top, + self.collectionView.frame.size.width - (footerInset.left + footerInset.right), + footerHeight); + + self.footersAttribute[@(section)] = attributes; + [self.allItemAttributes addObject:attributes]; + + top = CGRectGetMaxY(attributes.frame) + footerInset.bottom; + } + + for (idx = 0; idx < columnCount; idx++) { + self.columnHeights[section][idx] = @(top); + } + } // end of for (NSInteger section = 0; section < numberOfSections; ++section) + + // Build union rects + idx = 0; + NSInteger itemCounts = [self.allItemAttributes count]; + while (idx < itemCounts) { + CGRect unionRect = ((UICollectionViewLayoutAttributes *)self.allItemAttributes[idx]).frame; + NSInteger rectEndIndex = MIN(idx + unionSize, itemCounts); + + for (NSInteger i = idx + 1; i < rectEndIndex; i++) { + unionRect = CGRectUnion(unionRect, ((UICollectionViewLayoutAttributes *)self.allItemAttributes[i]).frame); + } + + idx = rectEndIndex; + + [self.unionRects addObject:[NSValue valueWithCGRect:unionRect]]; } - - top += footerInset.top; - - if (footerHeight > 0) { - attributes = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:CHTCollectionElementKindSectionFooter withIndexPath:[NSIndexPath indexPathForItem:0 inSection:section]]; - attributes.frame = CGRectMake(footerInset.left, - top, - self.collectionView.frame.size.width - (footerInset.left + footerInset.right), - footerHeight); - - self.footersAttribute[@(section)] = attributes; - [self.allItemAttributes addObject:attributes]; - - top = CGRectGetMaxY(attributes.frame) + footerInset.bottom; - } - - for (idx = 0; idx < columnCount; idx++) { - self.columnHeights[section][idx] = @(top); - } - } // end of for (NSInteger section = 0; section < numberOfSections; ++section) - - // Build union rects - idx = 0; - NSInteger itemCounts = [self.allItemAttributes count]; - while (idx < itemCounts) { - CGRect unionRect = ((UICollectionViewLayoutAttributes *)self.allItemAttributes[idx]).frame; - NSInteger rectEndIndex = MIN(idx + unionSize, itemCounts); - - for (NSInteger i = idx + 1; i < rectEndIndex; i++) { - unionRect = CGRectUnion(unionRect, ((UICollectionViewLayoutAttributes *)self.allItemAttributes[i]).frame); - } - - idx = rectEndIndex; - - [self.unionRects addObject:[NSValue valueWithCGRect:unionRect]]; - } } - (CGSize)collectionViewContentSize { - NSInteger numberOfSections = [self.collectionView numberOfSections]; - if (numberOfSections == 0) { - return CGSizeZero; - } - - CGSize contentSize = self.collectionView.bounds.size; - contentSize.height = [[[self.columnHeights lastObject] firstObject] floatValue]; - - return contentSize; + NSInteger numberOfSections = [self.collectionView numberOfSections]; + if (numberOfSections == 0) { + return CGSizeZero; + } + + CGSize contentSize = self.collectionView.bounds.size; + contentSize.height = [[[self.columnHeights lastObject] firstObject] floatValue]; + + return contentSize; } - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)path { - if (path.section >= [self.sectionItemAttributes count]) { - return nil; - } - if (path.item >= [self.sectionItemAttributes[path.section] count]) { - return nil; - } - return (self.sectionItemAttributes[path.section])[path.item]; + if (path.section >= [self.sectionItemAttributes count]) { + return nil; + } + if (path.item >= [self.sectionItemAttributes[path.section] count]) { + return nil; + } + return (self.sectionItemAttributes[path.section])[path.item]; } - (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { - UICollectionViewLayoutAttributes *attribute = nil; - if ([kind isEqualToString:CHTCollectionElementKindSectionHeader]) { - attribute = self.headersAttribute[@(indexPath.section)]; - } else if ([kind isEqualToString:CHTCollectionElementKindSectionFooter]) { - attribute = self.footersAttribute[@(indexPath.section)]; - } - return attribute; + UICollectionViewLayoutAttributes *attribute = nil; + if ([kind isEqualToString:CHTCollectionElementKindSectionHeader]) { + attribute = self.headersAttribute[@(indexPath.section)]; + } else if ([kind isEqualToString:CHTCollectionElementKindSectionFooter]) { + attribute = self.footersAttribute[@(indexPath.section)]; + } + return attribute; } - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { - NSInteger i; - NSInteger begin = 0, end = self.unionRects.count; - NSMutableArray *attrs = [NSMutableArray array]; - - for (i = 0; i < self.unionRects.count; i++) { - if (CGRectIntersectsRect(rect, [self.unionRects[i] CGRectValue])) { - begin = i * unionSize; - break; + + NSInteger i; + NSInteger begin = 0, end = self.unionRects.count; + NSMutableArray *attrs = [NSMutableArray array]; + + for (i = 0; i < self.unionRects.count; i++) { + if (CGRectIntersectsRect(rect, [self.unionRects[i] CGRectValue])) { + begin = i * unionSize; + break; + } } - } - for (i = self.unionRects.count - 1; i >= 0; i--) { - if (CGRectIntersectsRect(rect, [self.unionRects[i] CGRectValue])) { - end = MIN((i + 1) * unionSize, self.allItemAttributes.count); - break; + for (i = self.unionRects.count - 1; i >= 0; i--) { + if (CGRectIntersectsRect(rect, [self.unionRects[i] CGRectValue])) { + end = MIN((i + 1) * unionSize, self.allItemAttributes.count); + break; + } } - } - for (i = begin; i < end; i++) { - UICollectionViewLayoutAttributes *attr = self.allItemAttributes[i]; - if (CGRectIntersectsRect(rect, attr.frame)) { - [attrs addObject:attr]; + for (i = begin; i < end; i++) { + UICollectionViewLayoutAttributes *attr = self.allItemAttributes[i]; + if (CGRectIntersectsRect(rect, attr.frame)) { + [attrs addObject:attr]; + } } - } - - return [NSArray arrayWithArray:attrs]; + if (_floatHeaders){ + + NSInteger endToTest = MAX(end-1, 0); + + if ([self.allItemAttributes count] > 0 && self.allItemAttributes[begin] && self.allItemAttributes[endToTest] ){ + NSInteger sectionOfBegin = [[(UICollectionViewLayoutAttributes *)self.allItemAttributes[begin] indexPath] section]; + NSInteger sectionOfEnd = [[(UICollectionViewLayoutAttributes *)self.allItemAttributes[endToTest] indexPath] section]; + + for (NSInteger visibleSection = sectionOfBegin; visibleSection <= sectionOfEnd; visibleSection++){ + + UICollectionViewLayoutAttributes *beyondVisibleAttribute = self.headersAttribute[@(visibleSection)]; + // add headers that aren't visible and may be beyond the rect + if (beyondVisibleAttribute && !CGRectIntersectsRect(rect, beyondVisibleAttribute.frame) ){ + [attrs addObject:beyondVisibleAttribute]; + } + } + } + + for (UICollectionViewLayoutAttributes *layoutAttributes in attrs) { + + if (layoutAttributes.representedElementKind == CHTCollectionElementKindSectionHeader){ + + NSInteger section = layoutAttributes.indexPath.section; + NSInteger numberOfItemsInSection = [self.collectionView numberOfItemsInSection:section]; + + NSIndexPath *firstObjectIndexPath = [NSIndexPath indexPathForItem:0 inSection:section]; + NSIndexPath *lastObjectIndexPath = [NSIndexPath indexPathForItem:MAX(0, (numberOfItemsInSection - 1)) inSection:section]; + + UICollectionViewLayoutAttributes *firstObjectAttrs; + UICollectionViewLayoutAttributes *lastObjectAttrs; + + if (numberOfItemsInSection > 0) { // no need to calculate if there aren't items in the section + firstObjectAttrs = [self layoutAttributesForItemAtIndexPath:firstObjectIndexPath]; + lastObjectAttrs = [self layoutAttributesForItemAtIndexPath:lastObjectIndexPath]; + + + CGFloat topHeaderHeight = CGRectGetHeight(layoutAttributes.frame); + CGFloat bottomHeaderHeight = CGRectGetHeight(layoutAttributes.frame); + CGRect frameWithEdgeInsets = UIEdgeInsetsInsetRect(layoutAttributes.frame, + self.collectionView.contentInset); + + CGPoint origin = frameWithEdgeInsets.origin ; + + CGFloat bottomInsetCompensation = self.headerInset.bottom; + if ([self.delegate respondsToSelector:@selector(collectionView:layout:insetForHeaderInSection:)]){ + bottomInsetCompensation = [self.delegate collectionView:self.collectionView layout:self insetForHeaderInSection:section].bottom; + } + + CGFloat topOfCV = self.collectionView.contentOffset.y + self.collectionView.contentInset.top; + CGFloat topOfHeader = CGRectGetMinY(firstObjectAttrs.frame) - topHeaderHeight - bottomInsetCompensation ; + + CGFloat lastYToFloatOn = (CGRectGetMaxY(lastObjectAttrs.frame) - bottomHeaderHeight); + + + origin.y = MIN( + MAX( + topOfCV, + topOfHeader + ), + lastYToFloatOn + ) ; + + layoutAttributes.zIndex = 1024; + layoutAttributes.frame = CGRectMake(0, origin.y, layoutAttributes.frame.size.width, layoutAttributes.frame.size.height); + } + } + } + } + + return [NSArray arrayWithArray:attrs]; } - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds { - CGRect oldBounds = self.collectionView.bounds; - if (CGRectGetWidth(newBounds) != CGRectGetWidth(oldBounds)) { - return YES; - } - return NO; + CGRect oldBounds = self.collectionView.bounds; + if (CGRectGetWidth(newBounds) != CGRectGetWidth(oldBounds)) { + return YES; + } + return _floatHeaders; } #pragma mark - Private Methods @@ -448,18 +527,18 @@ - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds { * @return index for the shortest column */ - (NSUInteger)shortestColumnIndexInSection:(NSInteger)section { - __block NSUInteger index = 0; - __block CGFloat shortestHeight = MAXFLOAT; - - [self.columnHeights[section] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { - CGFloat height = [obj floatValue]; - if (height < shortestHeight) { - shortestHeight = height; - index = idx; - } - }]; - - return index; + __block NSUInteger index = 0; + __block CGFloat shortestHeight = MAXFLOAT; + + [self.columnHeights[section] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + CGFloat height = [obj floatValue]; + if (height < shortestHeight) { + shortestHeight = height; + index = idx; + } + }]; + + return index; } /** @@ -468,18 +547,18 @@ - (NSUInteger)shortestColumnIndexInSection:(NSInteger)section { * @return index for the longest column */ - (NSUInteger)longestColumnIndexInSection:(NSInteger)section { - __block NSUInteger index = 0; - __block CGFloat longestHeight = 0; - - [self.columnHeights[section] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { - CGFloat height = [obj floatValue]; - if (height > longestHeight) { - longestHeight = height; - index = idx; - } - }]; - - return index; + __block NSUInteger index = 0; + __block CGFloat longestHeight = 0; + + [self.columnHeights[section] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + CGFloat height = [obj floatValue]; + if (height > longestHeight) { + longestHeight = height; + index = idx; + } + }]; + + return index; } /** @@ -488,26 +567,26 @@ - (NSUInteger)longestColumnIndexInSection:(NSInteger)section { * @return index for the next column */ - (NSUInteger)nextColumnIndexForItem:(NSInteger)item inSection:(NSInteger)section { - NSUInteger index = 0; - NSInteger columnCount = [self columnCountForSection:section]; - switch (self.itemRenderDirection) { - case CHTCollectionViewWaterfallLayoutItemRenderDirectionShortestFirst: - index = [self shortestColumnIndexInSection:section]; - break; - - case CHTCollectionViewWaterfallLayoutItemRenderDirectionLeftToRight: - index = (item % columnCount); - break; - - case CHTCollectionViewWaterfallLayoutItemRenderDirectionRightToLeft: - index = (columnCount - 1) - (item % columnCount); - break; - - default: - index = [self shortestColumnIndexInSection:section]; - break; - } - return index; + NSUInteger index = 0; + NSInteger columnCount = [self columnCountForSection:section]; + switch (self.itemRenderDirection) { + case CHTCollectionViewWaterfallLayoutItemRenderDirectionShortestFirst: + index = [self shortestColumnIndexInSection:section]; + break; + + case CHTCollectionViewWaterfallLayoutItemRenderDirectionLeftToRight: + index = (item % columnCount); + break; + + case CHTCollectionViewWaterfallLayoutItemRenderDirectionRightToLeft: + index = (columnCount - 1) - (item % columnCount); + break; + + default: + index = [self shortestColumnIndexInSection:section]; + break; + } + return index; } @end diff --git a/Demo/Demo/ViewController.m b/Demo/Demo/ViewController.m index d694db1..17b999f 100644 --- a/Demo/Demo/ViewController.m +++ b/Demo/Demo/ViewController.m @@ -28,11 +28,13 @@ - (UICollectionView *)collectionView { if (!_collectionView) { CHTCollectionViewWaterfallLayout *layout = [[CHTCollectionViewWaterfallLayout alloc] init]; - layout.sectionInset = UIEdgeInsetsMake(10, 10, 10, 10); - layout.headerHeight = 15; + layout.sectionInset = UIEdgeInsetsMake(0, 5, 10, 5); + layout.headerHeight = 30; layout.footerHeight = 10; - layout.minimumColumnSpacing = 20; - layout.minimumInteritemSpacing = 30; + layout.headerInset = UIEdgeInsetsMake(0, 0, 5, 0); + layout.minimumColumnSpacing = 5; + layout.minimumInteritemSpacing = 5; + layout.floatHeaders = YES; _collectionView = [[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:layout]; _collectionView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; @@ -97,7 +99,7 @@ - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSe } - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { - return 2; + return 20; } - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {