集合視圖(UICollectionView
)的功能非常強大,它與表視圖(UITableView
)非常相似,不同之處在于集合視圖本身并不知道自己應該怎樣布局劫灶,它將布局方式委托給了UICollectionLayout的子類怔昨。系統(tǒng)本身提供了一個強大的子類——流式布局(UICollectionViewFlowLayout
)罩锐,可以通過設置scrollDirection
屬性來選擇集合視圖是水平滾動還是豎直滾動蟀拷,也可以設置每個UICollectionViewCell
之間的間隔强戴;這個類通過UICollectionViewDelegateFlowLayout
協(xié)議調(diào)整每個UICollectionViewCell
的大小翘县。
添加UICollectionview
添加集合視圖
使用代碼添加集合視圖,需要在init方法中選擇布局方式,具體方法是:- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout;
除此之外绩鸣,還需要指定集合視圖的cell類和cell的重用標識,具體方法是:- (void)registerClass:(Class)cellClass forCellWithReuseIdentifier:(NSString *)identifier;
最后泳梆,也要像表視圖一樣指定delegate
和dataSource
。
UICollectionViewFlowLayout
若使用流式布局允蚣,還需要實現(xiàn)UICollectionViewDelegateFlowLayout
協(xié)議來調(diào)整每個UICollectionViewCell
的大泻恕:
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath;
做完這些工作后狈谊,可以看到的大致效果如下:
這是每個item的size都一樣的情況下的效果二庵,但是如果每個item的寬度一樣催享,高度卻不一樣會如何?答案是:
因為如果使用
UICollectionViewFlowLayout
裆操,該布局會先計算一行中所有item的最大高度踪区,然后開始布局下一行的item茅郎,這樣做就會使每個item都會占據(jù)這一行的最大高度惯豆,所以導致了這些空白裂明。解決方案就是自己自定義UICollectionViewLayout
。
Masonry Layout
要實現(xiàn)MasonryLayout
(也稱石工布局)需要自定義UICollectionViewLayout
尼荆。首先建立UICollectionViewLayout
的子類并定義一個得到目標位置item大小的協(xié)議方法左腔,最后覆蓋UICollectionViewLayout
的三個方法:
- (void)prepareLayout;
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect;
- (CGSize)collectionViewContentSize;
prepareLayout
方法會在集合視圖開始布局前被調(diào)用,在這個方法中捅儒,需要計算item的布局方式液样;
layoutAttributesForElementsInRect:
方法則需要返回在rect以內(nèi)的item的布局方式巧还;
collectionViewContentSize
方法則需要返回當前集合視圖的contentSize
具體例子如下:
//MasonryLayout.h
#import <UIKit/UIKit.h>
#define MasonryCollectionViewSpaceWidth 10
typedef NS_ENUM(NSInteger, LayoutStyle) {
LayoutStyleInOrder = 0, //順序排列cell
LayoutStyleRegular = 1, //整齊排列cell
};
@protocol MasonryLayoutDelegate;
@interface MasonryLayout : UICollectionViewLayout
@property (nonatomic, weak) id<MasonryLayoutDelegate> delegate;
- (instancetype)initWithLayoutStyle:(LayoutStyle)style;
@end
@protocol MasonryLayoutDelegate <NSObject>
//返回indexPath位置cell的高度
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(MasonryLayout *)layout heightForItemAtIndexPath:(NSIndexPath *)indexPath;
@end
//MasonryLayout.m
#import "MasonryLayout.h"
@interface MasonryLayout ()
{
NSUInteger _numberOfColumns; //列數(shù)
NSMutableDictionary* _layoutInfo; //儲存每個cell的UICollectionViewLayoutAttributes
NSMutableDictionary* _lastYValueForColumn; //儲存每一列當前最大y坐標
LayoutStyle _style;
}
@end
@implementation MasonryLayout
- (instancetype)initWithLayoutStyle:(LayoutStyle)style
{
self = [super init];
if (self) {
_style = style;
}
return self;
}
- (void)prepareLayout
{
_numberOfColumns = 2; //有兩列cell
_lastYValueForColumn = [NSMutableDictionary dictionary];
_layoutInfo = [NSMutableDictionary dictionary];
switch (_style) {
case LayoutStyleInOrder:{
[self getLayoutInfoInOrder];
}
break;
case LayoutStyleRegular:{
[self getLayoutInfoRegular];
}
break;
default:
break;
}
}
- (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 function
- (void)getLayoutInfoInOrder
{
NSUInteger currentColumn = 0;
CGFloat itemWidth = ([UIScreen mainScreen].bounds.size.width - MasonryCollectionViewSpaceWidth * (_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 = MasonryCollectionViewSpaceWidth + (itemWidth + MasonryCollectionViewSpaceWidth) * currentColumn;
CGFloat originY = [_lastYValueForColumn[@(currentColumn)] doubleValue];
if (originY == 0.0) {
originY = MasonryCollectionViewSpaceWidth;
}
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 + MasonryCollectionViewSpaceWidth);
currentColumn++;
if (currentColumn == _numberOfColumns) {
currentColumn = 0;
}
}
}
}
- (void)getLayoutInfoRegular
{
NSUInteger currentColumn = 0;
CGFloat itemWidth = ([UIScreen mainScreen].bounds.size.width - MasonryCollectionViewSpaceWidth * (_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 = MasonryCollectionViewSpaceWidth + (itemWidth + MasonryCollectionViewSpaceWidth) * currentColumn;
CGFloat originY = [_lastYValueForColumn[@(currentColumn)] doubleValue];
if (originY == 0.0) {
originY = MasonryCollectionViewSpaceWidth;
}
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 + MasonryCollectionViewSpaceWidth);
}
}
}
- (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;
}
@end
//ViewController.m
#import "ViewController.h"
#import "MasonryLayout.h"
@interface ViewController () <UICollectionViewDelegate, UICollectionViewDataSource, MasonryLayoutDelegate>
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.\
MasonryLayout *layout = [MasonryLayout new];
layout.delegate = self;
UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:layout];
[collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"cell"];
collectionView.backgroundColor = [UIColor whiteColor];
collectionView.delegate = self;
collectionView.dataSource = self;
[self.view addSubview:collectionView];
}
#pragma mark -- MasonryLayoutDelegate
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(MasonryLayout *)layout heightForItemAtIndexPath:(NSIndexPath *)indexPath
{
int x = arc4random() % 150 + 50; //生成50-200的隨機數(shù)
return x;
}
#pragma mark -- UICollectionViewDataSource
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return 50;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell" forIndexPath:indexPath];
cell.backgroundColor = [UIColor redColor];
return cell;
}
效果如下: