ios 定制UICollectionViewFlowLayout类实现网格布局

默认的流式布局会自动换行,以便使区段中的条目能够使用集合视图的长度或宽度,但这样做出来的视图只能在一个方向上滚动。如果愿意多做一些数学运算,就可以编写自定义的布局子类,从而实现不会换行的双向滚动视图。实现该功能所需要的运算律比较大,而且不是特别容易。

ios 定制UICollectionViewFlowLayout类实现网格布局_第1张图片

完全定制了UICollectionViewFlowLayout的子类,覆写了collectionViewContentSize及layoutAttributesForItemAtIndexPath:方法,以便手工摆放每个条目。这种实现方式完全考虑到所有与间隔有关的请求以及回调方法。相比之下,普通的流式布局只会在满足多个最小值的前提下,试着把条目合适摆放到屏幕上。

typedef NS_ENUM(NSUInteger, GridRowAlignment) {
    GridRowAlignmentNone,
    GridRowAlignmentTop,
    GridRowAlignmentCenter,
    GridRowAlignmentBottom,
};

@interface GridLayout : UICollectionViewFlowLayout

@property (nonatomic,assign) GridRowAlignment aligment;

@end
@implementation GridLayout
- (BOOL)usesIndividualItemSizing
{
    return [self.collectionView.delegate respondsToSelector:@selector(collectionView:layout:sizeForItemAtIndexPath:)];
}
- (CGSize)sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
    CGSize itemSize = self.itemSize;
    if ([self usesIndividualItemSizing]) {
        itemSize = [(id )self.collectionView.delegate collectionView:self.collectionView layout:self sizeForItemAtIndexPath:indexPath];
    }
    return itemSize;
}

- (BOOL)usesIndividualInsets
{
    return [self.collectionView.delegate respondsToSelector:@selector(collectionView:layout:insetForSectionAtIndex:)];
}

- (UIEdgeInsets)insetsForSection:(NSInteger)section
{
    UIEdgeInsets insets = self.sectionInset;
    if ([self usesIndividualInsets]) {
        insets = [(id)self.collectionView.delegate collectionView:self.collectionView layout:self insetForSectionAtIndex:section];
    }
    return insets;
}

- (BOOL)usesIndividualItemSpacing
{
    return [self.collectionView.delegate respondsToSelector:@selector(collectionView:layout:minimumInteritemSpacingForSectionAtIndex:)];
}

- (CGFloat)itemSpacingForSection:(NSInteger)section
{
    CGFloat spacing = self.minimumInteritemSpacing;
    if ([self usesIndividualItemSpacing]) {
        spacing = [(id )self.collectionView.delegate collectionView:self.collectionView layout:self minimumInteritemSpacingForSectionAtIndex:section];
    }
    return spacing;
}

- (CGFloat)maxItemHeightForSection:(NSInteger)section
{
    CGFloat maxHeight = 0;
    NSInteger numberOfItems = [self.collectionView numberOfItemsInSection:section];
    for (int i = 0; i< numberOfItems; i++) {
        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:section];
        CGSize itemSize = [self sizeForItemAtIndexPath:indexPath];
        maxHeight = MAX(maxHeight, itemSize.height);
        
    }
    return  maxHeight;
}

- (CGFloat)fullWidthForSection:(NSInteger)section
{
    UIEdgeInsets insets = [self insetsForSection:section];
    CGFloat horizontalInsetExtent = insets.left + insets.right;
    CGFloat collectiveWith = horizontalInsetExtent;
    
    NSInteger numberOfItems = [self.collectionView numberOfItemsInSection:section];
    for (int i = 0; i < numberOfItems; i ++) {
        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:section];
        CGSize itemSize = [self sizeForItemAtIndexPath:indexPath];
        collectiveWith += itemSize.width;
        collectiveWith += [self itemSpacingForSection:section];
    }
    
    collectiveWith -= [self itemSpacingForSection:section];
    return collectiveWith;
}

- (CGSize)fullSizeForSection:(NSInteger)section
{
    CGFloat headerExtent = (self.scrollDirection == UICollectionViewScrollDirectionHorizontal) ? self.headerReferenceSize.width : self.headerReferenceSize.height;
    CGFloat footerExtent = (self.scrollDirection == UICollectionViewScrollDirectionHorizontal) ? self.footerReferenceSize.width : self.footerReferenceSize.height;
    UIEdgeInsets insets = [self insetsForSection:section];
    CGFloat verticalInsetExtent = insets.top + insets.bottom;
    CGFloat maxHeight = [self maxItemHeightForSection:section];
    
    CGFloat fullHeight = headerExtent + footerExtent + verticalInsetExtent + maxHeight;
    CGFloat fullWith = [self fullWidthForSection:section];
    return CGSizeMake(fullWith, fullHeight);
}

- (CGFloat)horizontalInsetForItemAtIndexPath:(NSIndexPath *)indexPath
{
    UIEdgeInsets insets = [self insetsForSection:indexPath.section];
    CGFloat horizontalOffset = insets.left;
    for (int i = 0;  i < indexPath.item; i ++) {
        NSIndexPath *indexPath1 = [NSIndexPath indexPathForItem:i inSection:indexPath.section];
        CGSize itemSize = [self sizeForItemAtIndexPath:indexPath1];
        horizontalOffset += (itemSize.width + [self itemSpacingForSection:indexPath.section]);
        
    }
    return horizontalOffset;
}

- (CGFloat)verticalInsetForItemAtIndexPath:(NSIndexPath *)indexPath
{
    CGSize thisItemSize = [self sizeForItemAtIndexPath:indexPath];
    CGFloat verticlOffset = 0;
    for (int i = 0; i < indexPath.section; i ++) {
        verticlOffset += [self fullSizeForSection:i].height;
    }
    CGFloat headerExtent = (self.scrollDirection == UICollectionViewScrollDirectionHorizontal) ? self.headerReferenceSize.width : self.headerReferenceSize.height;
    verticlOffset +=headerExtent;
    
    UIEdgeInsets insets = [self insetsForSection:indexPath.section];
    verticlOffset += insets.top;
    
    CGFloat maxHeigth = [self maxItemHeightForSection:indexPath.section];
    CGFloat fullHeight = (maxHeigth - thisItemSize.height);
    CGFloat midHeight = fullHeight / 2;
    
    switch (self.aligment) {
        case GridRowAlignmentNone:
        case GridRowAlignmentTop:
            break;
        case GridRowAlignmentCenter:
            verticlOffset += midHeight;
            break;
        case GridRowAlignmentBottom:
            verticlOffset += fullHeight;
            break;
            
        default:
            break;
    }
    return verticlOffset;
}

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
    CGSize thisItemSize = [self sizeForItemAtIndexPath:indexPath];
    CGFloat verticalOffset = [self verticalInsetForItemAtIndexPath:indexPath];
    CGFloat horizontalOffset = [self horizontalInsetForItemAtIndexPath:indexPath];
    if (self.scrollDirection == UICollectionViewScrollDirectionVertical) {
        attributes.frame = CGRectMake(horizontalOffset, verticalOffset, thisItemSize.width, thisItemSize.height);
        
    }else{
        attributes.frame = CGRectMake(verticalOffset, horizontalOffset, thisItemSize.width, thisItemSize.height);
    }
    return attributes;
}

- (CGSize)collectionViewContentSize
{
    NSInteger sections = self.collectionView.numberOfSections;
    
    CGFloat maxWith = 0;
    CGFloat collectiveHeight = 0;
    for (int i=0; i < sections; i++) {
        CGSize sectionSize = [self fullSizeForSection:i];
        collectiveHeight += sectionSize.height;
        maxWith = MAX(maxWith, sectionSize.width);
    }
    
    if (self.scrollDirection == UICollectionViewScrollDirectionVertical) {
        return CGSizeMake(maxWith, collectiveHeight);
    }else
    {
        return CGSizeMake(collectiveHeight, maxWith);
    }
}

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    NSMutableArray *attributes = [NSMutableArray array];
    for (NSInteger section = 0; section < self.collectionView.numberOfSections; section++) {
        for (NSInteger item = 0; item < [self.collectionView numberOfItemsInSection:section]; item ++ ) {
            NSIndexPath *indexPath = [NSIndexPath indexPathForItem:item inSection:section];
            UICollectionViewLayoutAttributes * layout = [self layoutAttributesForItemAtIndexPath:indexPath];
            [attributes addObject:layout];
        }
    }
    return attributes;
}

- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
    return YES;
}
@end
布局对象要把每个元素的排布位置都计算出来。但是它不会使用行间距属性,因为该属性只在自动换行的前提下才有意义。我们制作的网格状布局,绝不会自动换行,所以会彻底胡洛这个属性。

还有一个名为alignment属性。这个新的属性决定了表格中的每一行是顶端对齐、底端对齐,还是居中对齐。它会查询一整行的总体高度,然后可以会把比这个高度矮的条目移动一段距离。

你可能感兴趣的:(iOS)