UICollectionView的横向布局

Simulator Screen Shot 2017年1月15日 下午10.07.51.png

demo地址:https://github.com/zeR0Lu/XFHorizontalAutoLayoutCollectionView.git
这个demo是陆晓峰写的,特此声明,下文是对横向布局的解读。

#import <UIKit/UIKit.h>

@protocol HorizontalCollectionLayoutDelegate <NSObject>

@optional
// 用协议传回 item 的内容,用于计算 item 宽度
- (NSString *)collectionViewItemSizeWithIndexPath:(NSIndexPath *)indexPath;
// 用协议传回 headerSize 的 size
- (CGSize)collectionViewDynamicHeaderSizeWithIndexPath:(NSIndexPath *)indexPath;
// 用协议传回 footerSize 的 size
- (CGSize)collectionViewDynamicFooterSizeWithIndexPath:(NSIndexPath *)indexPath;
@end

@interface HorizontalCollectionLayout : UICollectionViewLayout
// item 的行距
@property (nonatomic, assign) CGFloat lineSpacing;
// item 的间距 
@property (nonatomic, assign) CGFloat interitemSpacing;
// header 高度
@property (nonatomic, assign) CGFloat headerViewHeight;
// footer 高度
@property (nonatomic, assign) CGFloat footerViewHeight;
// item 高度
@property (nonatomic, assign) CGFloat itemHeight;
// footer 边距缩进
@property (nonatomic, assign) UIEdgeInsets footerInset;
// header 边距缩进
@property (nonatomic, assign) UIEdgeInsets headerInset;
// item 边距缩进
@property (nonatomic, assign) UIEdgeInsets itemInset;
// item Label Font
@property (nonatomic, copy) UIFont *labelFont;

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

@end




#import "HorizontalCollectionLayout.h"

#define SCREENWIDTH [[UIScreen mainScreen] bounds].size.width

@interface HorizontalCollectionLayout ()

@property (nonatomic, strong) NSMutableArray *attributesArray;

@property (nonatomic, strong) NSMutableArray *itemsattributes;

@property (nonatomic, strong) NSMutableArray *headerAttributes;

@property (nonatomic, strong) NSMutableArray *footerAttributes;

@property (nonatomic, assign) CGFloat contentHeight;

@property (nonatomic, assign) CGFloat viewWidth;

@end

@implementation HorizontalCollectionLayout

- (instancetype)init {
    if (self = [super init]) {
        
        //设置间距的默认值
        self.headerViewHeight = 0.0;
        self.footerViewHeight = 0.0;
        self.interitemSpacing = 4.0;
        self.lineSpacing = 4.0;
        self.itemHeight = 30.0;
        self.itemInset = UIEdgeInsetsMake(0.0, 0.0, 0.0, 0.0);
        self.headerInset = UIEdgeInsetsMake(0.0, 0.0, 0.0, 0.0);
        self.footerInset = UIEdgeInsetsMake(0.0, 0.0, 0.0, 0.0);
        self.labelFont = [UIFont systemFontOfSize:15.0];
    }
    return self;
}

/** 1、当collectionView布局item时 第一个执行的方法 */
- (void)prepareLayout {
    /** 重写layout中的方法 首先必须调用父类 */
    [super prepareLayout];
    
    self.viewWidth = SCREENWIDTH - self.itemInset.left - self.itemInset.right;
    //所有内容的布局属性数组
    self.attributesArray = [NSMutableArray array];
    
    //item的数据模型是2原数组,就是第一层数组包含的是section,第二层是每个section包含的item
    self.itemsattributes = [NSMutableArray array];
    //记录 collectionview 的内容高度
    self.contentHeight = 0.0;
    
    /** 获取collectionView 中的item的个数 */
    NSInteger sectionCount = [self.collectionView numberOfSections];
    /** 遍历得到每个item 设置位置信息 */
    for (NSInteger i = 0; i < sectionCount; i++) {
        
        NSInteger itemCount = [self.collectionView numberOfItemsInSection:i];
        for (NSInteger j = 0; j < itemCount; j++) {
            [self setItemFrameWithIndexPath:[NSIndexPath indexPathForItem:j inSection:i]];
            
            if ( (i == sectionCount - 1) && ( j == itemCount - 1) ) {
                //这里是当最后一个 item 的 layoutAttributes 设置完成后如果有设置 footer 就要把 footer 添加到所有 layoutAttributes 数组
                if ( [self.delegate respondsToSelector:@selector(collectionViewDynamicFooterSizeWithIndexPath:)] ) {
                    
                    //获取最后一个 item 的 layoutAttributes
                    UICollectionViewLayoutAttributes *lastAttributes = self.attributesArray.lastObject;
                    //添加 footer 的 layoutAttributes
                    [self makeFooterAttributesWithLastItemAttributes:lastAttributes];
                    
                    // 获取新添加的 footer 的 layoutAttributes
                    UICollectionViewLayoutAttributes *footerAttributes = self.footerAttributes.lastObject;
                    //计算总高度
                    self.contentHeight = CGRectGetMaxY(footerAttributes.frame) + self.itemInset.bottom;
                }
            }
        }
    }
}

- (void)setItemFrameWithIndexPath:(NSIndexPath *)indexPath {
    
    //这里主要是设置一下 item 的初始 frame
    CGFloat x = 0.0;
    CGFloat y = 0.0;
    CGFloat width = [self countItemSizeWithIndexPath:indexPath];
    CGFloat height = 30.0;
    
    // 获取数组最后一个 layoutAttributes, 这样方便计算 frame 和 判断是否需要计算新的 section
    UICollectionViewLayoutAttributes *lastAttributes = self.attributesArray.lastObject;
    
    if ( lastAttributes ) {
        //如果数组有值代表不是设置第一个item
        if ( lastAttributes.indexPath.section == indexPath.section ) {
            //同一组
            if ( CGRectGetMaxX(lastAttributes.frame) + self.interitemSpacing + width > self.viewWidth ) {
                //需要换行
                x = self.itemInset.left + self.lineSpacing;
                y = CGRectGetMaxY(lastAttributes.frame) + self.lineSpacing;
            }else {
                //不需要换行
                x = CGRectGetMaxX(lastAttributes.frame) + self.interitemSpacing;
                y = CGRectGetMinY(lastAttributes.frame);
            }
        }else {
            //不同一组
            //添加 footer 的布局,内部会判断是否需要添加
            [self makeFooterAttributesWithLastItemAttributes:lastAttributes];
            
            //添加一个新的 section 数组
            [self.itemsattributes addObject:[NSMutableArray array]];
            
            //这里重新获取最后一个 layoutAttributes 是因为如果加入了 footer 总的 layoutAttributes就会改变
            lastAttributes = self.attributesArray.lastObject;
            
            //添加 header 的布局,内部会判断是否需要添加
            [self makeHeaderAttributesWithIndexPath:indexPath lastItemAttributes:lastAttributes];
            
            //设置新的 section 的第一个 item 的 frame
            x = self.itemInset.left + self.lineSpacing;
            y = CGRectGetMaxY(lastAttributes.frame) + self.lineSpacing * 2 + self.headerViewHeight;
        }
    }else {
        //这里是设置第一个section的item
        [self.itemsattributes addObject:[NSMutableArray array]];
        
        //添加 header 的布局,内部会判断是否需要添加
        [self makeHeaderAttributesWithIndexPath:indexPath lastItemAttributes:lastAttributes];
        
        //这里判断是否有 header, 如果有就获取最后一个 layoutAttributes
        if ( self.headerAttributes.count ) {
            lastAttributes = self.attributesArray.lastObject;
        }
        
        //设置新的 section 的第一个 item 的 frame
        x = self.itemInset.left + self.lineSpacing;
        y = self.lineSpacing + lastAttributes.size.height;
    }
    
    //设置每一个 item 的 frame
    UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
    /** 添加frame */
    attributes.frame = CGRectMake(x, y, width, height);
    self.contentHeight = CGRectGetMaxY(attributes.frame) + self.lineSpacing;
    /** 保存在数组中 */
    [self.itemsattributes[indexPath.section] addObject:attributes];
    [self.attributesArray addObject:attributes];
}

#pragma mark - New Header Or Footer
- (void)makeHeaderAttributesWithIndexPath:(NSIndexPath *)indexPath lastItemAttributes:(UICollectionViewLayoutAttributes *)attributes {
    //设置第一个section的header
    UICollectionViewLayoutAttributes *headerAttributes = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader withIndexPath:indexPath];
    
    CGFloat y = (attributes)?CGRectGetMaxY(attributes.frame) + self.lineSpacing:self.itemInset.top;
    CGFloat headerWidth =  0.0;
    CGFloat headerHeight =  0.0;
    
    if ( [self.delegate respondsToSelector:@selector(collectionViewDynamicHeaderSizeWithIndexPath:)] ) {
        CGSize size = [self.delegate collectionViewDynamicHeaderSizeWithIndexPath:indexPath];
        
        headerWidth = size.width;
        headerHeight = size.height;
    }else {
        headerWidth = SCREENWIDTH - self.headerInset.left - self.headerInset.right;
        headerHeight = self.headerViewHeight;
    }
    
    if ( headerHeight > 0.0 ) {
        headerAttributes.frame = CGRectMake(self.headerInset.left, y, headerWidth, headerHeight);
        
        [self.headerAttributes addObject:headerAttributes];
        [self.attributesArray addObject:headerAttributes];
    }
}

- (void)makeFooterAttributesWithLastItemAttributes:(UICollectionViewLayoutAttributes *)attributes {
    
    UICollectionViewLayoutAttributes *footerAttributes = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionFooter withIndexPath:attributes.indexPath];
    
    CGFloat footerWidth =  0.0;
    CGFloat footerHeight =  0.0;
    if ( [self.delegate respondsToSelector:@selector(collectionViewDynamicFooterSizeWithIndexPath:)] ) {
        
        CGSize size = [self.delegate collectionViewDynamicFooterSizeWithIndexPath:attributes.indexPath];
        footerWidth = size.width;
        footerHeight = size.height;
    } else {
        footerWidth = SCREENWIDTH - self.footerInset.left - self.footerInset.right;
        footerHeight = self.footerViewHeight;
    }
    
    if ( footerHeight > 0 ) {
        footerAttributes.frame = CGRectMake(self.footerInset.left, CGRectGetMaxY(attributes.frame) + self.lineSpacing, footerWidth, footerHeight);
        [self.footerAttributes addObject:footerAttributes];
        [self.attributesArray addObject:footerAttributes];
    }
    
}

#pragma mark -
//这个是返回所有 header, footer, item 属性的回调方法, 一定要实现
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
    return self.attributesArray;
}

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
    return self.itemsattributes[indexPath.section][indexPath.item];
}

- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath {
    if ( [elementKind isEqual: UICollectionElementKindSectionHeader] ) {
        return self.headerAttributes[indexPath.section];
    }else {
        return self.footerAttributes[indexPath.section];
    }
}

/** 4、设置滚动范围 */
// 这里可以处理 uicollectionview 内容不够屏幕高度不能滑动的问题,只要把 contentsize.height 设置成比屏幕高度大就可以了
- (CGSize)collectionViewContentSize {
    
    return CGSizeMake(0.0, self.contentHeight + self.itemInset.bottom);
}

- (CGFloat)countItemSizeWithIndexPath:(NSIndexPath *)indexPath {
    NSString *content = [self.delegate collectionViewItemSizeWithIndexPath:indexPath];
    
    CGSize size = [content sizeWithAttributes:@{NSFontAttributeName:self.labelFont}];
    
    return MAX(size.width + 24.0, 40.0);
}


#pragma mark - lazy
// header 的布局属性数组
- (NSMutableArray *)headerAttributes {
    if ( !_headerAttributes ) {
        _headerAttributes = [NSMutableArray array];
    }
    return _headerAttributes;
}

// footer 的布局属性数组
- (NSMutableArray *)footerAttributes {
    if ( !_footerAttributes ) {
        _footerAttributes = [NSMutableArray array];
    }
    return _footerAttributes;
}
@end

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,511评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,495评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,595评论 0 225
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,558评论 0 190
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,715评论 3 270
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,672评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,112评论 2 291
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,837评论 0 181
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,417评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,928评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,316评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,773评论 2 234
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,253评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,827评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,440评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,523评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,583评论 2 249

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,471评论 25 707
  • 这是一个奇迹!这个奇迹发生在2001年的大年初一,在东海之滨的厦门,一个叫做如家的地方,我和老梁,遇见了! 人生的...
    三戒201610阅读 279评论 0 3
  • 有没有那么一种朋友, 你自认为关系很好,什么都想一起分享, 但是慢慢的会发现, 只是你一厢情愿罢了, 你把你所有开...
    夏小暖summer阅读 284评论 0 0