iOS开发 自定义UICollectionViewLayout实现Masonry Layout

集合视图(UICollectionView)的功能非常强大,它与表示图(UITableView)非常相似,不同之处在于集合视图本身并不知道自己应该怎样布局,它将布局方式委托给了UICollectionLayout的子类。系统本身提供了一个强大的子类——流式布局(UICollectionViewFlowLayout),可以通过设置scrollDirection属性来选择集合视图是水平滚动还是竖直滚动,也可以设置每个UICollectionViewCell之间的间隔;这个类通过UICollectionViewDelegateFlowLayout协议调整每个UICollectionViewCell的大小。


添加集合视图

使用代码添加集合视图,需要在init方法中选择布局方式,具体方法是:

- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout;

除此之外,还需要指定集合视图的cell类和cell的重用标识,具体方法是:

- (void)registerClass:(Class)cellClass forCellWithReuseIdentifier:(NSString *)identifier;

最后,也要像表视图一样指定delegate和dataSource。做完之后,就可以将集合视图显示到屏幕上了。


流式布局

接下来就要实现集合视图的各种协议方法了,其中,dataSource和delegate的协议方法和表视图的协议方法类似,在这就不多提了,但是,集合视图还需要实现布局方式的协议方法来获取每个item(UICollectionViewCell)的大小,假若使用流式布局,则需要实现:

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath;

这个方法返回对应位置item的size(CGSize),做完这些后,可以看到大致如下的效果:


这是每个item的size都一样的情况下的效果,但是如果每个item的宽度一样,高度却不一样会如何?答案是:


因为如果使用流式布局,该布局会先计算一行中所有item的最大高度,然后开始布局下一行的item,这样做就会使每个item都会占据这一行的最大高度,所以导致了这些空白。解决方案就是自己自定义 UICollectionViewLayout。


Masonry Layout

要实现Masonry Layout(也称石工布局)需要自定义UICollectionViewLayout。首先建立UICollectionViewLayout的子类并定义一个得到目标位置item大小的协议方法,最后覆盖UICollectionViewLayout的三个方法:

- (void)prepareLayout;
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect;
- (CGSize)collectionViewContentSize;

prepareLayout方法会在集合视图开始布局前被调用,在这个方法中,需要计算item的布局方式;layoutAttributesForElementsInRect:方法则需要返回在rect以内的item的布局方式;collectionViewContentSize方法则需要返回当前集合视图的contentSize。具体例子如下:

//MasonryCollectionViewLayout.h

#import <UIKit/UIKit.h>

#define SpaceWidth      10

@protocol MasonryCollectionViewLayoutDelegate;

@interface MasonryCollectionViewLayout : UICollectionViewLayout

@property (nonatomic, weak) id<MasonryCollectionViewLayoutDelegate> delegate;

@end

@protocol MasonryCollectionViewLayoutDelegate <NSObject>

- (CGFloat) collectionView:(UICollectionView *) collectionView layout:(MasonryCollectionViewLayout *) layout heightForItemAtIndexPath:(NSIndexPath *) indexPath;

@end

//MasonryCollectionViewLayout.m
#import "MasonryCollectionViewLayout.h"

@interface MasonryCollectionViewLayout (){
    NSUInteger                   _numberOfColumns;
    
    NSMutableDictionary*        _layoutInfo;
    NSMutableDictionary*        _lastYValueForColumn;
}

@end

@implementation MasonryCollectionViewLayout

- (void)prepareLayout
{
    _numberOfColumns = 2;
    
    _lastYValueForColumn = [NSMutableDictionary dictionary];
    _layoutInfo = [NSMutableDictionary dictionary];
    
    //[self getLayoutInfoInOrder];//顺序排列
    [self getLayoutInfoRegular];//整齐排列
}

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    NSMutableArray *allAttributes = [NSMutableArray array];
    [_layoutInfo enumerateKeysAndObjectsUsingBlock:^(NSIndexPath *indexPath, UICollectionViewLayoutAttributes *attributes, BOOL *stop) {
        if (CGRectIntersectsRect(rect, attributes.frame)) {
            [allAttributes addObject:attributes];
        }
    }];
    return allAttributes;
}

- (CGSize)collectionViewContentSize
{
    NSUInteger currentColumns = 0;
    CGFloat maxHeight = 0;
    do {
        CGFloat height = [_lastYValueForColumn[@(currentColumns)] doubleValue];
        if (height > maxHeight) {
            maxHeight = height;
        }
        currentColumns ++;
    }while (currentColumns < _numberOfColumns);
    return CGSizeMake(self.collectionView.frame.size.width, maxHeight);
}

#pragma mark -- private method
- (void)getLayoutInfoInOrder  //顺序排列cell
{
    NSUInteger currentColumn = 0;
    CGFloat itemWidth = ([UIScreen mainScreen].bounds.size.width - SpaceWidth * (_numberOfColumns + 1)) / _numberOfColumns;
    
    NSIndexPath *indexPath;
    NSInteger numberOfSection = [self.collectionView numberOfSections];
    
    for (NSInteger section = 0; section < numberOfSection; section++) {
        NSInteger numberOfItem = [self.collectionView numberOfItemsInSection:section];

        for (NSInteger item = 0; item < numberOfItem; item++) {
            indexPath = [NSIndexPath indexPathForItem:item inSection:section];

            UICollectionViewLayoutAttributes *itemAttributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];

            CGFloat originX = SpaceWidth + (itemWidth + SpaceWidth) * currentColumn;
            CGFloat originY = [_lastYValueForColumn[@(currentColumn)] doubleValue];
            if (originY == 0.0) {
                originY = 12.5;
            }

            CGFloat itemHeight = [self.delegate collectionView:self.collectionView layout:self heightForItemAtIndexPath:indexPath];

            itemAttributes.frame = CGRectMake(originX, originY, itemWidth, itemHeight);
            _layoutInfo[indexPath] = itemAttributes;
            _lastYValueForColumn[@(currentColumn)] = @(originY + itemHeight + SpaceWidth);

            currentColumn++;
            if (currentColumn == _numberOfColumns) {
                currentColumn = 0;
            }
            
        }
        
    }
}

- (void)getLayoutInfoRegular //整齐排列cell
{
    NSUInteger currentColumn = 0;
    CGFloat itemWidth = ([UIScreen mainScreen].bounds.size.width - SpaceWidth * (_numberOfColumns + 1)) / _numberOfColumns;
    
    NSIndexPath *indexPath;
    NSInteger numberOfSection = [self.collectionView numberOfSections];
    
    for (NSInteger section = 0; section < numberOfSection; section++) {
        NSInteger numberOfItem = [self.collectionView numberOfItemsInSection:section];
        
        for (NSInteger item = 0; item < numberOfItem; item++) {
            indexPath = [NSIndexPath indexPathForItem:item inSection:section];
            
            UICollectionViewLayoutAttributes *itemAttributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
            
            currentColumn = [self getMiniHeightColumn];
            CGFloat originX = SpaceWidth + (itemWidth + SpaceWidth) * currentColumn;
            CGFloat originY = [_lastYValueForColumn[@(currentColumn)] doubleValue];
            if (originY == 0.0) {
                originY = 12.5;
            }
            
            CGFloat itemHeight = [self.delegate collectionView:self.collectionView layout:self heightForItemAtIndexPath:indexPath];
            
            itemAttributes.frame = CGRectMake(originX, originY, itemWidth, itemHeight);
            _layoutInfo[indexPath] = itemAttributes;
            _lastYValueForColumn[@(currentColumn)] = @(originY + itemHeight + SpaceWidth);
            
        }
        
    }
}

- (NSUInteger)getMiniHeightColumn
{
    NSInteger miniHeightColumn = 0;
    CGFloat miniHeight = [_lastYValueForColumn[@(miniHeightColumn)] doubleValue];
    for (NSUInteger column = 0; column < _numberOfColumns; column++) {
        CGFloat height = [_lastYValueForColumn[@(column)] doubleValue];
        if (height < miniHeight) {
            miniHeight = height;
            miniHeightColumn = column;
        }
    }
    return miniHeightColumn;
}

效果如下:


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值