UICollectionViewFlowLayout
1:直接设置FlowLayout对象
创建UICollectionViewLayout对象,通过设置UICollectionViewLayout对象属性的值可以设置item的基本布局,包括大小,间距,内边距等。
UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init];
flowLayout.scrollDirection = UICollectionViewScrollDirectionVertical; // 滑动方向
flowLayout.minimumLineSpacing = 8; // 行间距
flowLayout.minimumInteritemSpacing = 6; // 列间距
flowLayout.itemSize = CGSizeMake(kScreenWidth/2, 90); // item 大小
flowLayout.sectionInset = UIEdgeInsetsMake(20, 15, 20, 15); // 内边距
2:通过UICollectionViewDelegateFlowLayout代理方法返回
// item 大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath;
// 每个分区的内边距
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section;
// 行间距
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section;
// 列间距
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section;
// SectionHeader 大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section;
// SectionFooter 大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section;
注意:第一种和第二种方法可以混合使用,但不要同时使用。例如内边距行列间距通过FlowLayout对象设置,itemSize 通过代理动态返回。但是不要通过FlowLayout对象设置了行列间距之后, 还实现代理方法返回行列间距。同时使用不会报错,但没意义消耗系统资源。
3:自定义布局
当系统的布局无论你怎么设置都满足不了需求的时候,就需要用到自定义布局了。我们需要继承UICollectionViewFlowLayout,重写其计算布局的方法,来打到预期的效果。
上图中,上半部分是系统默认的布局,下半部分是我自定义布局实现的。我们注意到系统布局是将 item 两端对齐,间距根据剩余的宽度自己缩放。UICollectionViewFlowLayout对象的minimumInteritemSpacing说的很清楚,我们设置的是最小间距。但是 UI 小姐姐希望间距相等,这个时候系统的布局就实现不了了。
自定义布局的具体实现
首先继承UICollectionViewFlowLayout不必多说,然后就是重写其布局方法。
1:重写prepareLayout,每次更新布局的时候collectionview都会首先调用这个方法,为将要开始的更新做准备,我们需要在此处计算好必要的布局信息并存储起来
- (void)prepareLayout{
[super prepareLayout];
NSMutableArray *layoutInfoArr = [NSMutableArray array];
// 获取布局信息
NSInteger numberOfSections = [self.collectionView numberOfSections];
for (NSInteger section = 0; section < numberOfSections; section++){
NSInteger numberOfItems = [self.collectionView numberOfItemsInSection:section];
NSMutableArray *subArr = [NSMutableArray arrayWithCapacity:numberOfItems];
for (NSInteger item = 0; item < numberOfItems; item++){
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:item inSection:section];
UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:indexPath];
[subArr addObject:attributes];
}
// 添加到二维数组
[layoutInfoArr addObject:[subArr copy]];
}
// 存储布局信息
self.attributesArray = [layoutInfoArr copy];
}
2:重写并调用layoutAttributesForItemAtIndexPath方法计算布局
// 根据自己的需求,计算每一个item的布局,如果 itemSize是动态的,可以配合代理方法拿到当前 indexPath 的大小
// 这里只自定义了item的布局
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{
// 拿到系统为我们计算的布局
UICollectionViewLayoutAttributes *oldAttributes = [super layoutAttributesForItemAtIndexPath:indexPath];
// 创建一个我们期望的布局
UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
CGFloat itemX = self.sectionInset.left; // 默认X值
CGFloat itemY = oldAttributes.frame.origin.y; // Y值直接用系统算的
CGSize itemSize = oldAttributes.size; // 大小直接代理返回的
// 无需换行 && indexPath.row !=0 调整X值 (indexPath.row=0时 self.lastFrame 还未赋值)
if (oldAttributes.frame.origin.x != itemX && indexPath.row != 0) {
itemX = self.lastFrame.origin.x + self.lastFrame.size.width + self.minimumInteritemSpacing;
}
// 赋值
attributes.frame = CGRectMake(itemX, itemY, itemSize.width, itemSize.height);
// 更新上一个item的位置
self.lastFrame = CGRectMake(itemX, itemY, itemSize.width, itemSize.height);
return attributes;
}
3:重写layoutAttributesForElementsInRect:方法返回指定区域cell、Supplementary View和Decoration View的布局属性。
// 找出了与指定区域有交接的UICollectionViewLayoutAttributes对象放到一个数组中,然后返回
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{
NSMutableArray *layoutAttributesArr = [NSMutableArray array];
[self.attributesArray enumerateObjectsUsingBlock:^(NSArray *array, NSUInteger i, BOOL * _Nonnull stop) {
[array enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes *obj, NSUInteger idx, BOOL * _Nonnull stop) {
if(CGRectIntersectsRect(obj.frame, rect)) { // 如果 item 在rect内
[layoutAttributesArr addObject:obj];
}
}];
}];
return layoutAttributesArr;
}
4:如果需要自定义分区头部和尾部可以重写下面两个方法,并在prepareLayout里面做相应的处理
- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath;
- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString*)elementKind atIndexPath:(NSIndexPath *)indexPath;
5:重写- (CGSize)collectionViewContentSize方法,返回 contentSize。注意这个方法返回的尺寸是给UICollectionView的父类UIScrollView作为contentSize ,不是UICollectionView的视图尺寸。正是因为这一点,我们自定义layout如果想让它只能横向滑动,只需要将这个 size.height 设置成 collectionView.height 就行了。 这个方法会多次调用,所以最好是在prepareLayout里就计算好,设置为全局变量,直接返回
- (CGSize)collectionViewContentSize{
return self.contentSize;
}
做完这些就可以实现上图的效果了
全部代码
#import "LGCollectionViewFlowLayout.h"
@interface LGCollectionViewFlowLayout ()
@property (nonatomic, assign) CGRect lastFrame; // 上一个item的 布局
@property (nonatomic, strong) NSMutableArray *attributesArray;
@end
@implementation LGCollectionViewFlowLayout
- (void)prepareLayout{
[super prepareLayout];
NSMutableArray *layoutInfoArr = [NSMutableArray array];
// 获取布局信息
NSInteger numberOfSections = [self.collectionView numberOfSections];
for (NSInteger section = 0; section < numberOfSections; section++){
NSInteger numberOfItems = [self.collectionView numberOfItemsInSection:section];
NSMutableArray *subArr = [NSMutableArray arrayWithCapacity:numberOfItems];
for (NSInteger item = 0; item < numberOfItems; item++){
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:item inSection:section];
UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:indexPath];
[subArr addObject:attributes];
}
// 添加到二维数组
[layoutInfoArr addObject:[subArr copy]];
}
// 存储布局信息
self.attributesArray = [layoutInfoArr copy];
}
// 直接使用系统计算的大小 不做更新了
//- (CGSize)collectionViewContentSize{
// return self.contentSize;
//}
// 找出了与指定区域有交接的UICollectionViewLayoutAttributes对象放到一个数组中,然后返回
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{
NSMutableArray *layoutAttributesArr = [NSMutableArray array];
[self.attributesArray enumerateObjectsUsingBlock:^(NSArray *array, NSUInteger i, BOOL * _Nonnull stop) {
[array enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes *obj, NSUInteger idx, BOOL * _Nonnull stop) {
if(CGRectIntersectsRect(obj.frame, rect)) { // 如果 item 在rect内
[layoutAttributesArr addObject:obj];
}
}];
}];
return layoutAttributesArr;
}
// 根据自己的需求,计算每一个item的布局,如果 itemSize是动态的,可以配合代理方法拿到当前 indexPath 的大小
// 这里只自定义了item的布局
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{
// 拿到系统为我们计算的布局
UICollectionViewLayoutAttributes *oldAttributes = [super layoutAttributesForItemAtIndexPath:indexPath];
// 创建一个我们期望的布局
UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
CGFloat itemX = self.sectionInset.left; // 默认X值
CGFloat itemY = oldAttributes.frame.origin.y; // Y值直接用系统算的
CGSize itemSize = oldAttributes.size; // 大小直接代理返回的
// 同一行
BOOL line = oldAttributes.frame.origin.y == self.lastFrame.origin.y;
// 不换行 && (indexPath.row=0时 self.lastFrame 还未赋值) 调整X值
if (oldAttributes.frame.origin.x != itemX && indexPath.row != 0 && line) {
itemX = self.lastFrame.origin.x + self.lastFrame.size.width + self.minimumLineSpacing;
}
// 赋值
attributes.frame = CGRectMake(itemX, itemY, itemSize.width, itemSize.height);
// 更新上一个item的位置
self.lastFrame = CGRectMake(itemX, itemY, itemSize.width, itemSize.height);
return attributes;
}
- (NSMutableArray *)attributesArray{
if (!_attributesArray) {
_attributesArray = [NSMutableArray array];
}
return _attributesArray;
}
@end
使用的时候直接换一下类名就好了
// itemSize 是通过代理动态计算的
LGCollectionViewFlowLayout *flowLayout = [[LGCollectionViewFlowLayout alloc] init];
flowLayout.scrollDirection = UICollectionViewScrollDirectionVertical;
flowLayout.minimumLineSpacing = 8;
flowLayout.minimumInteritemSpacing = 6;
flowLayout.sectionInset = UIEdgeInsetsMake(20, 15, 20, 15);