//
|
// DDCollectionViewFlowLayout.m
|
// DDCollectionViewFlowLayout
|
//
|
// Created by DeJohn Dong on 15-2-12.
|
// Copyright (c) 2015年 DDKit. All rights reserved.
|
//
|
|
#import "DDCollectionViewFlowLayout.h"
|
|
@interface DDCollectionViewFlowLayout() {
|
NSMutableArray *sectionRects;
|
NSMutableArray *columnRectsInSection;
|
|
NSMutableArray *layoutItemAttributes;
|
NSDictionary *headerFooterItemAttributes;
|
|
NSMutableArray *sectionInsetses;
|
|
UIEdgeInsets currentEdgeInsets;
|
}
|
|
@end
|
|
@implementation DDCollectionViewFlowLayout
|
|
#pragma mark - UISubclassingHooks Category Methods
|
|
- (CGSize)collectionViewContentSize {
|
[super collectionViewContentSize];
|
|
CGRect lastSectionRect = [[sectionRects lastObject] CGRectValue];
|
CGSize lastSize = CGSizeMake(CGRectGetWidth(self.collectionView.bounds), CGRectGetMaxY(lastSectionRect));
|
return lastSize;
|
}
|
|
- (void)prepareLayout {
|
|
NSUInteger numberOfSections = self.collectionView.numberOfSections;
|
sectionRects = [[NSMutableArray alloc] initWithCapacity:numberOfSections];
|
columnRectsInSection = [[NSMutableArray alloc] initWithCapacity:numberOfSections];
|
layoutItemAttributes = [[NSMutableArray alloc] initWithCapacity:numberOfSections];
|
sectionInsetses = [[NSMutableArray alloc] initWithCapacity:numberOfSections];
|
headerFooterItemAttributes = @{UICollectionElementKindSectionHeader:[NSMutableArray array], UICollectionElementKindSectionFooter:[NSMutableArray array]};
|
|
for (NSUInteger section = 0; section < numberOfSections; ++section) {
|
NSUInteger itemsInSection = [self.collectionView numberOfItemsInSection:section];
|
[layoutItemAttributes addObject:[NSMutableArray array]];
|
[self prepareSectionLayout:section withNumberOfItems:itemsInSection];
|
}
|
}
|
|
- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)kind
|
atIndexPath:(NSIndexPath *)indexPath {
|
return headerFooterItemAttributes[kind][indexPath.section];
|
}
|
|
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{
|
return layoutItemAttributes[indexPath.section][indexPath.item];
|
}
|
|
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
|
return [self searchVisibleLayoutAttributesInRect:rect];
|
}
|
|
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
|
return YES;
|
}
|
|
#pragma mark - Private Methods
|
|
- (void)prepareSectionLayout:(NSUInteger)section withNumberOfItems:(NSUInteger)numberOfItems {
|
UICollectionView *cView = self.collectionView;
|
|
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:section];
|
|
//# hanlde the section header
|
CGFloat headerHeight = 0.0f;
|
|
CGRect previousSectionRect = [self rectForSectionAtIndex:indexPath.section - 1];
|
CGRect sectionRect;
|
sectionRect.origin.x = 0;
|
sectionRect.origin.y = CGRectGetHeight(previousSectionRect) + CGRectGetMinY(previousSectionRect);
|
sectionRect.size.width = cView.bounds.size.width;
|
|
if([self.delegate respondsToSelector:@selector(collectionView:layout:referenceSizeForHeaderInSection:)]) {
|
|
//# Define the rect of the header
|
CGRect headerFrame;
|
headerFrame.origin.x = 0.0f;
|
headerFrame.origin.y = sectionRect.origin.y;
|
|
CGSize headerSize = [self.delegate collectionView:self.collectionView layout:self referenceSizeForHeaderInSection:indexPath.section];
|
headerFrame.size.height = headerSize.height;
|
headerFrame.size.width = headerSize.width;
|
|
UICollectionViewLayoutAttributes *headerAttributes =
|
[UICollectionViewLayoutAttributes
|
layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader
|
withIndexPath:indexPath];
|
headerAttributes.frame = headerFrame;
|
|
headerHeight = headerFrame.size.height;
|
[headerFooterItemAttributes[UICollectionElementKindSectionHeader] addObject:headerAttributes];
|
}
|
|
//# get the insets of section
|
UIEdgeInsets sectionInset = UIEdgeInsetsZero;
|
|
if([self.delegate respondsToSelector:@selector(collectionView:layout:insetForSectionAtIndex:)]) {
|
sectionInset = [self.delegate collectionView:cView layout:self insetForSectionAtIndex:section];
|
}
|
|
[sectionInsetses addObject:[NSValue valueWithUIEdgeInsets:sectionInset]];
|
|
CGRect itemsContentRect;
|
|
//# the the lineSpacing & interitemSpacing default values
|
CGFloat lineSpacing = 0.0f;
|
CGFloat interitemSpacing = 0.0f;
|
|
if([self.delegate respondsToSelector:@selector(collectionView:layout:minimumInteritemSpacingForSectionAtIndex:)]){
|
interitemSpacing = [self.delegate collectionView:cView layout:self minimumInteritemSpacingForSectionAtIndex:section];
|
}
|
|
if([self.delegate respondsToSelector:@selector(collectionView:layout:minimumLineSpacingForSectionAtIndex:)]){
|
lineSpacing = [self.delegate collectionView:cView layout:self minimumLineSpacingForSectionAtIndex:section];
|
}
|
|
itemsContentRect.origin.x = sectionInset.left;
|
itemsContentRect.origin.y = headerHeight + sectionInset.top;
|
|
NSUInteger numberOfColumns = [self.delegate collectionView:cView layout:self numberOfColumnsInSection:section];
|
itemsContentRect.size.width = CGRectGetWidth(cView.frame) - (sectionInset.left + sectionInset.right);
|
|
CGFloat columnSpace = itemsContentRect.size.width - (interitemSpacing * (numberOfColumns-1));
|
CGFloat columnWidth = (columnSpace/numberOfColumns);
|
|
// # store space for each column
|
[columnRectsInSection addObject:[NSMutableArray arrayWithCapacity:numberOfColumns]];
|
for (NSUInteger colIdx = 0; colIdx < numberOfColumns; ++colIdx)
|
[columnRectsInSection[section] addObject:[NSMutableArray array]];
|
|
// # Define the rect of the of each item
|
for (NSInteger itemIdx = 0; itemIdx < numberOfItems; ++itemIdx) {
|
NSIndexPath *itemPath = [NSIndexPath indexPathForItem:itemIdx inSection:section];
|
CGSize itemSize = [self.delegate collectionView:cView layout:self sizeForItemAtIndexPath:itemPath];
|
|
NSInteger destColumnIdx = [self preferredColumnIndexInSection:section];
|
NSInteger destRowInColumn = [self numberOfItemsInColumn:destColumnIdx ofSection:section];
|
CGFloat lastItemInColumnOffset = [self lastItemOffsetInColumn:destColumnIdx inSection:section];
|
|
if(destRowInColumn == 0){
|
lastItemInColumnOffset += sectionRect.origin.y;
|
}
|
|
CGRect itemRect;
|
itemRect.origin.x = itemsContentRect.origin.x + destColumnIdx * (interitemSpacing + columnWidth);
|
itemRect.origin.y = lastItemInColumnOffset + (destRowInColumn > 0 ? lineSpacing: sectionInset.top);
|
itemRect.size.width = columnWidth;
|
itemRect.size.height = itemSize.height;
|
|
UICollectionViewLayoutAttributes *itemAttributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:itemPath];
|
itemAttributes.frame = itemRect;
|
[layoutItemAttributes[section] addObject:itemAttributes];
|
[columnRectsInSection[section][destColumnIdx] addObject:[NSValue valueWithCGRect:itemRect]];
|
}
|
|
itemsContentRect.size.height = [self heightOfItemsInSection:indexPath.section] + sectionInset.bottom;
|
|
// # define the section footer
|
CGFloat footerHeight = 0.0f;
|
if([self.delegate respondsToSelector:@selector(collectionView:layout:referenceSizeForFooterInSection:)]){
|
CGRect footerFrame;
|
footerFrame.origin.x = 0;
|
footerFrame.origin.y = itemsContentRect.size.height;
|
|
CGSize footerSize = [self.delegate collectionView:self.collectionView layout:self referenceSizeForFooterInSection:indexPath.section];
|
footerFrame.size.height = footerSize.height;
|
footerFrame.size.width = footerSize.width;
|
|
UICollectionViewLayoutAttributes *footerAttributes = [UICollectionViewLayoutAttributes
|
layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionFooter
|
withIndexPath:indexPath];
|
footerAttributes.frame = footerFrame;
|
|
footerHeight = footerFrame.size.height;
|
|
[headerFooterItemAttributes[UICollectionElementKindSectionFooter] addObject:footerAttributes];
|
}
|
|
if(section > 0){
|
itemsContentRect.size.height -= sectionRect.origin.y;
|
}
|
|
sectionRect.size.height = itemsContentRect.size.height + footerHeight;
|
|
[sectionRects addObject:[NSValue valueWithCGRect:sectionRect]];
|
}
|
|
- (CGFloat)heightOfItemsInSection:(NSUInteger)sectionIdx {
|
CGFloat maxHeightBetweenColumns = 0.0f;
|
NSArray *columnsInSection = columnRectsInSection[sectionIdx];
|
for (NSUInteger columnIdx = 0; columnIdx < columnsInSection.count; ++columnIdx) {
|
CGFloat heightOfColumn = [self lastItemOffsetInColumn:columnIdx inSection:sectionIdx];
|
maxHeightBetweenColumns = MAX(maxHeightBetweenColumns, heightOfColumn);
|
}
|
return maxHeightBetweenColumns;
|
}
|
|
- (NSInteger)numberOfItemsInColumn:(NSInteger)columnIdx ofSection:(NSInteger)sectionIdx {
|
return [columnRectsInSection[sectionIdx][columnIdx] count];
|
}
|
|
- (CGFloat)lastItemOffsetInColumn:(NSInteger)columnIdx inSection:(NSInteger)sectionIdx {
|
NSArray *itemsInColumn = columnRectsInSection[sectionIdx][columnIdx];
|
if (itemsInColumn.count == 0) {
|
if([headerFooterItemAttributes[UICollectionElementKindSectionHeader] count] > sectionIdx){
|
CGRect headerFrame = [headerFooterItemAttributes[UICollectionElementKindSectionHeader][sectionIdx] frame];
|
return headerFrame.size.height;
|
}
|
return 0.0f;
|
} else {
|
CGRect lastItemRect = [[itemsInColumn lastObject] CGRectValue];
|
return CGRectGetMaxY(lastItemRect);
|
}
|
}
|
|
- (NSInteger)preferredColumnIndexInSection:(NSInteger)sectionIdx {
|
NSUInteger shortestColumnIdx = 0;
|
CGFloat heightOfShortestColumn = CGFLOAT_MAX;
|
for (NSUInteger columnIdx = 0; columnIdx < [columnRectsInSection[sectionIdx] count]; ++columnIdx) {
|
CGFloat columnHeight = [self lastItemOffsetInColumn:columnIdx inSection:sectionIdx];
|
if (columnHeight < heightOfShortestColumn) {
|
shortestColumnIdx = columnIdx;
|
heightOfShortestColumn = columnHeight;
|
}
|
}
|
return shortestColumnIdx;
|
}
|
|
- (CGRect)rectForSectionAtIndex:(NSInteger)sectionIdx {
|
if (sectionIdx < 0 || sectionIdx >= sectionRects.count)
|
return CGRectZero;
|
return [sectionRects[sectionIdx] CGRectValue];
|
}
|
|
#pragma mark - Show Attributes Methods
|
- (NSArray *)searchVisibleLayoutAttributesInRect:(CGRect)rect {
|
NSMutableArray *itemAttrs = [[NSMutableArray alloc] init];
|
NSIndexSet *visibleSections = [self sectionIndexesInRect:rect];
|
[visibleSections enumerateIndexesUsingBlock:^(NSUInteger sectionIdx, BOOL *stop) {
|
|
//# items
|
for (UICollectionViewLayoutAttributes *itemAttr in layoutItemAttributes[sectionIdx]) {
|
CGRect itemRect = itemAttr.frame;
|
itemAttr.zIndex = 1;
|
BOOL isVisible = CGRectIntersectsRect(rect, itemRect);
|
if (isVisible)
|
[itemAttrs addObject:itemAttr];
|
}
|
|
//# footer
|
if([headerFooterItemAttributes[UICollectionElementKindSectionFooter] count] > sectionIdx){
|
UICollectionViewLayoutAttributes *footerAttribute = headerFooterItemAttributes[UICollectionElementKindSectionFooter][sectionIdx];
|
BOOL isVisible = CGRectIntersectsRect(rect, footerAttribute.frame);
|
if (isVisible && footerAttribute)
|
[itemAttrs addObject:footerAttribute];
|
currentEdgeInsets = UIEdgeInsetsZero;
|
}else{
|
currentEdgeInsets = [sectionInsetses[sectionIdx] UIEdgeInsetsValue];
|
}
|
|
//# header
|
if([headerFooterItemAttributes[UICollectionElementKindSectionHeader] count] > sectionIdx){
|
UICollectionViewLayoutAttributes *headerAttribute = headerFooterItemAttributes[UICollectionElementKindSectionHeader][sectionIdx];
|
|
if(!self.enableStickyHeaders){
|
BOOL isVisibleHeader = CGRectIntersectsRect(rect, headerAttribute.frame);
|
|
if (isVisibleHeader && headerAttribute)
|
[itemAttrs addObject:headerAttribute];
|
}else{
|
UICollectionViewLayoutAttributes *lastCell = [itemAttrs lastObject];
|
|
if(headerAttribute)
|
[itemAttrs addObject:headerAttribute];
|
|
[self updateHeaderAttributes:headerAttribute lastCellAttributes:lastCell];
|
}
|
}
|
}];
|
return itemAttrs;
|
}
|
|
- (NSIndexSet *)sectionIndexesInRect:(CGRect)rect {
|
CGRect theRect = rect;
|
NSMutableIndexSet *visibleIndexes = [[NSMutableIndexSet alloc] init];
|
NSUInteger numberOfSections = self.collectionView.numberOfSections;
|
for (NSUInteger sectionIdx = 0; sectionIdx < numberOfSections; ++sectionIdx) {
|
CGRect sectionRect = [sectionRects[sectionIdx] CGRectValue];
|
BOOL isVisible = CGRectIntersectsRect(theRect, sectionRect);
|
if (isVisible)
|
[visibleIndexes addIndex:sectionIdx];
|
}
|
return visibleIndexes;
|
}
|
|
#pragma mark - Sticky Header implementation methods
|
|
- (void)updateHeaderAttributes:(UICollectionViewLayoutAttributes *)attributes
|
lastCellAttributes:(UICollectionViewLayoutAttributes *)lastCellAttributes
|
{
|
CGRect currentBounds = self.collectionView.bounds;
|
attributes.zIndex = 1024;
|
attributes.hidden = NO;
|
|
CGPoint origin = attributes.frame.origin;
|
|
CGFloat sectionMaxY = CGRectGetMaxY(lastCellAttributes.frame) - attributes.frame.size.height + currentEdgeInsets.bottom;
|
|
CGFloat y = CGRectGetMaxY(currentBounds) - currentBounds.size.height + self.collectionView.contentInset.top;
|
|
CGFloat maxY = MIN(MAX(y, attributes.frame.origin.y), sectionMaxY);
|
|
origin.y = maxY;
|
|
attributes.frame = (CGRect){
|
origin,
|
attributes.frame.size
|
};
|
}
|
|
@end
|