UICollectionView和UITableView很類似毁习,不過對于我個人來講嵌牺,UITableView是經(jīng)常用到的東西俏险,UICollectionView使用較少仇轻,所以這篇文章講UICollectionView鸟妙。
1.類和協(xié)議
1).UICollectionViewController:與UITableViewController功能類似
2).UICollectionViewCell:與UITableViewCell功能類似,同樣有ReuseIdentifier,所以它也有復用機制焦人。
從storyBoard中出列:
MyCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"myCell" forIndexPath:indexPath];
cell.cellLabel.text = [NSString stringWithFormat:@"%ld",(long)indexPath.item];
從nib中注冊:
[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:reuseIdentifier];
3).UICollectionViewDataSource:數(shù)據(jù)源協(xié)議
4).UICollectionViewDelegate:處理包含選中事件的各種方法的協(xié)議
5).UICollectionViewDelegateFlowLayout:這是UICollectionView和UITableView不同的地方,它可以用來定制一些布局重父。
2.例子
1).初始化
新建一個工程花椭,刪除ViewController類,將storyBoard中的ViewController替換為UICollectionViewController房午。
像往常一樣矿辽,你的主要內(nèi)容顯示在 cell 中,cell 可以被任意分組到 section 中。Collection view 的 cell 必須是 UICollectionViewCell 的子類袋倔。所以我們新建UICollectionViewController與UICollectionViewCell的子類雕蔽,將storyBoard中UICollectionViewController的custom class設(shè)置為MyCollectionViewController,將UICollectionViewCell的custom class設(shè)置為MyCollectionViewCell奕污。
在UICollectionViewCell中新增如下圖兩個控件萎羔,UIImageView和UILabel
不要忘記設(shè)置cell的Identifier:
建立兩個IBOutlet:
2).實現(xiàn)數(shù)據(jù)源方法
MyCollectionViewController.m:
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{
return 1;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
return 20;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
MyCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"myCell" forIndexPath:indexPath];
cell.cellLabel.text = [NSString stringWithFormat:@"%ld",(long)indexPath.item];
return cell;
}
配置cell,MyCollectionViewCell.m
-(void)awakeFromNib{
[super awakeFromNib];
self.backgroundColor = [UIColor randomColor];
}
現(xiàn)在運行碳默,如下圖:
旋轉(zhuǎn)屏幕后柵格會自動旋轉(zhuǎn)并對齊:
2).實現(xiàn)委托方法
a.高亮
在cell中添加一個selectedBackgroundView視圖:
-(void)awakeFromNib{
[super awakeFromNib];
self.selectedBackgroundView = [[UIView alloc]initWithFrame:self.frame];
self.selectedBackgroundView.backgroundColor = [UIColor blackColor];
self.backgroundColor = [UIColor randomColor];
}
實現(xiàn)以下代理方法:
-(BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath{
return YES;
}
//放大縮小效果
-(void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath{
UICollectionViewCell *selectedCell = [collectionView cellForItemAtIndexPath:indexPath];
[UIView animateWithDuration:kAnimationDuration animations:^{
selectedCell.transform = CGAffineTransformMakeScale(2.0f, 2.0f);
}];
}
-(void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath{
UICollectionViewCell *selectedCell = [collectionView cellForItemAtIndexPath:indexPath];
[UIView animateWithDuration:kAnimationDuration animations:^{
selectedCell.transform = CGAffineTransformMakeScale(1.0f, 1.0f);
}];
}
現(xiàn)在按下collectionCell會顯示高亮狀態(tài):背景顏色變黑色贾陷,且有一個彈跳的放大縮小效果。
b.選中
如上右邊新建一個MyDetailsViewController嘱根,并且從左邊控制器中segue到MyDetailsViewController髓废。
MyDetailsViewController.m
-(IBAction) doneTapped:(id) sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)viewDidLoad {
[super viewDidLoad];
self.imageView.image = [UIImage imageNamed:@"image"];
}
實現(xiàn)以下代理方法:
MyCollectionViewController.m
-(BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath{
return YES;
}
-(void) collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
dispatch_time_t delayInNanoSeconds = dispatch_time(DISPATCH_TIME_NOW, (int64_t)1*NSEC_PER_SEC);
dispatch_after(delayInNanoSeconds, dispatch_get_main_queue(), ^{
[self performSegueWithIdentifier:@"MainSegue" sender:indexPath];
});
}
這樣在高亮效果1秒后會進行視圖切換。
4).添加頭部和尾部視圖
collection view 額外管理著兩種視圖:supplementary views 该抒, Supplementary views 相當于 table view 的 section header 和 footer views慌洪。像 cells 一樣,他們的內(nèi)容都由數(shù)據(jù)源對象驅(qū)動凑保。然而和 table view 中用法不一樣冈爹,supplementary view 并不一定會作為 header 或 footer view;他們的數(shù)量和放置的位置完全由布局控制欧引。
Supplementary views必須是 UICollectionReusableView的子類频伤。布局使用的每個視圖類都需要在 collection view 中注冊,這樣當 data source 讓它們從 reuse pool 中出列時芝此,它們才能夠創(chuàng)建新的實例憋肖。首先我們需要在storyBoard中啟用"Section Header"和"Section Footer"
之后XCode會自動生成兩個UICollectionResuableView到視圖中:
然后同樣的你可以設(shè)置Identifier,然后在以下代理方法中dequeue即可婚苹,確實很分別岸更,相比UITableView又進一步封裝。
-(UICollectionReusableView *)collectionView:(UICollectionView *)collectionView
viewForSupplementaryElementOfKind:(NSString *)kind
atIndexPath:(NSIndexPath *)indexPath{
NSString *resueIndentifier = kCollectionViewHeaderIndentifier;
UICollectionReusableView *view = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:resueIndentifier forIndexPath:indexPath];
return [collectionView dequeueReusableSupplementaryViewOfKind: UICollectionElementKindSectionFooter
withReuseIdentifier:SupplementaryViewIdentifier
forIndexPath:indexPath];
}
在這個demo膊升,我演示下如何通過加載自定義的nib控件來添加頭部和尾部視圖怎炊,如下我們新建兩個自定義nib控件:
MyCollectionViewController中加載并注冊nib:
-(void)awakeFromNib{
UINib *headerNib = [UINib nibWithNibName:NSStringFromClass([Header class]) bundle:[NSBundle mainBundle]];
[self.collectionView registerNib:headerNib forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:kCollectionViewHeaderIndentifier];
UINib *footerNib = [UINib nibWithNibName:NSStringFromClass([Footer class]) bundle:[NSBundle mainBundle]];
[self.collectionView registerNib:footerNib forSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:kCollectionViewFooterIndentifier];
}
代理方法類似:
-(UICollectionReusableView *)collectionView:(UICollectionView *)collectionView
viewForSupplementaryElementOfKind:(NSString *)kind
atIndexPath:(NSIndexPath *)indexPath{
NSString *resueIndentifier = kCollectionViewHeaderIndentifier;
if ([kind isEqualToString:UICollectionElementKindSectionFooter]) {
resueIndentifier = kCollectionViewFooterIndentifier;
}
UICollectionReusableView *view = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:resueIndentifier forIndexPath:indexPath];
if ([kind isEqualToString:UICollectionElementKindSectionHeader]) {
Header *header = (Header *)view;
header.label.text = [NSString stringWithFormat:@"Section Header %lu",(unsigned long)indexPath.section+1];
}else if ([kind isEqualToString:UICollectionElementKindSectionFooter]){
Footer *footer = (Footer *)view;
NSString *title = [NSString stringWithFormat:@"Section Footer %lu",(unsigned long)indexPath.section+1];
[footer.button setTitle:title forState:UIControlStateNormal];
}
return view;
}
UICollectionView和UITableView最重要的區(qū)別就是UICollectionView并不知道如何布局,它把布局機制委托給了UICollectionViewLayout子類廓译,默認的布局方式是UICollectionFlowViewLayout類提供的流式布局(flow layout)结胀,也就是上面例子顯示的那樣子。這個類允許你通過UICollectionDelegateViewFlowLayout協(xié)議調(diào)整各自屬性责循。
不過你也可以創(chuàng)建自己的布局方式,通過繼承UICollectionViewLayout攀操,現(xiàn)在是一個例子院仿。
3.UICollectionViewLayout子類
上面的例子中,我們所有cell的大小都是一樣的,那如果我們的cell大小不一樣呢歹垫?我們需要實現(xiàn)UICollectionViewDelegateFlowLayout的協(xié)議方法collectionView:layout:sizeForItemAtIndexPath:剥汤,但這會使得效果就像下面左邊那張圖。它會計算每一排中的最大高度排惨,這樣會讓效果看起來不怎么樣吭敢。我們可以繼承UICollectionViewLayout來實現(xiàn)右圖中的效果。
我們新建一個UICollectionViewController暮芭,并把程序運行開始移到改控制器鹿驼。
像上面的例子那樣,顯示50個同樣大小的單元辕宏,具體上面已經(jīng)介紹了畜晰,之后它看起來像這樣:
現(xiàn)在實現(xiàn)UICollectionViewDelegateFlowLayout的協(xié)議方法隨機改變cell大小的高度:
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
CGFloat randomHeight = 80 + (arc4random() % 150);
return CGSizeMake(80, randomHeight);
}
現(xiàn)在效果是這樣:
現(xiàn)在創(chuàng)建一個UICollectionViewLayout的子類:CustomCollectionViewLayout.首先我們需要像UICollectionViewDelegateFlowLayout一樣通過代理的方式來獲取特定indexPath上cell的高度。
@class CustomCollectionViewLayout;
@protocol CustomCollectionViewLayoutDelegate <NSObject>
@required
- (CGFloat) collectionView:(UICollectionView*) collectionView
layout:(CustomCollectionViewLayout*) layout
heightForItemAtIndexPath:(NSIndexPath*) indexPath;
@end
子類需要覆蓋父類以下3個方法:
-(void) prepareLayout;
-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect;
-(CGSize) collectionViewContentSize;
prepareLayout在布局開始之前會被調(diào)用瑞筐,我們需要在這個方法中計算邊框凄鼻,所以我們引入numberOfColumns 和 interItemSpacing兩個變量。分別是item每行的數(shù)目和item間的間距聚假,所以頭文件如下:
@interface CustomCollectionViewLayout : UICollectionViewLayout
@property (nonatomic, assign) NSUInteger numberOfColumns;
@property (nonatomic, assign) CGFloat interItemSpacing;
@property (weak, nonatomic) id<CustomCollectionViewLayoutDelegate> delegate;
@end
在開始布局前會執(zhí)行的方法prepareLayout中块蚌,我們需要計算每個item的frame值,并把它存入字典layoutInfo中膘格,然后峭范,在我們覆蓋父類的方法layoutAttributesForElementsInRect中,可以返回這個字典中的全部frame總值的數(shù)組闯袒。
在prepareLayout中虎敦,frame的height可以通過代理傳入:
CGFloat height = [((id<CustomCollectionViewLayoutDelegate>)self.collectionView.delegate)
collectionView:self.collectionView
layout:self
heightForItemAtIndexPath:indexPath];
frame的width則和numberOfColumns 和 interItemSpacing有關(guān),如下:
//計算Item的寬度
CGFloat fullWidth = self.collectionView.frame.size.width;
CGFloat availableSpaceExcludingPadding = fullWidth - (self.interItemSpacing * (self.numberOfColumns + 1));
CGFloat itemWidth = availableSpaceExcludingPadding / self.numberOfColumns;
x軸和y軸則和當前的indexPath有關(guān)政敢,所以我們遍歷section和item其徙,得到x軸和y軸,并將之前的高度和寬度加起來得到frame值喷户。
NSIndexPath *indexPath;
NSInteger numSections = [self.collectionView numberOfSections];
//遍歷section
for(NSInteger section = 0; section < numSections; section++) {
NSInteger numItems = [self.collectionView numberOfItemsInSection:section];
//遍歷item
for(NSInteger item = 0; item < numItems; item++){
indexPath = [NSIndexPath indexPathForItem:item inSection:section];
UICollectionViewLayoutAttributes *itemAttributes =
[UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
//計算x軸
CGFloat x = self.interItemSpacing + (self.interItemSpacing + itemWidth) * currentColumn;
//計算y軸
CGFloat y = [self.lastYValueForColumn[@(currentColumn)] doubleValue];
//通過協(xié)議回傳高度值
CGFloat height = [((id<CustomCollectionViewLayoutDelegate>)self.collectionView.delegate)
collectionView:self.collectionView
layout:self
heightForItemAtIndexPath:indexPath];
itemAttributes.frame = CGRectMake(x, y, itemWidth, height);
//下一個item的y軸是當前y軸加上item高度唾那,并且加上間距
y += height;
y += self.interItemSpacing;
//把下一個item的y軸記入到字典中
self.lastYValueForColumn[@(currentColumn)] = @(y);
currentColumn ++;
if(currentColumn == self.numberOfColumns) currentColumn = 0;
//將item的屬性記錄到字典中
self.layoutInfo[indexPath] = itemAttributes;
}
}
然后在我們需要覆蓋的第二個方法中,使用enumerateKeysAndObjectsUsingBlock遍歷prepareLayout中的layoutInfo加入一個數(shù)組中:
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
NSMutableArray *allAttributes = [NSMutableArray arrayWithCapacity:self.layoutInfo.count];
[self.layoutInfo enumerateKeysAndObjectsUsingBlock:^(NSIndexPath *indexPath,
UICollectionViewLayoutAttributes *attributes,
BOOL *stop) {
if (CGRectIntersectsRect(rect, attributes.frame)) {
[allAttributes addObject:attributes];
}
}];
return allAttributes;
}
最后一個方法是計算collectionView的內(nèi)容大小褪尝,在第一個方法中闹获,我們已經(jīng)把下每個item的y軸記入到字典lastYValueForColumn中,所以我們通過do-while循環(huán)把這個最大的y值給取出來河哑,加上寬度值即可返回collectionView的內(nèi)容大小避诽。
-(CGSize) collectionViewContentSize {
NSUInteger currentColumn = 0;
CGFloat maxHeight = 0;
do {
//最大高度就是之前字典中的y軸
CGFloat height = [self.lastYValueForColumn[@(currentColumn)] doubleValue];
if(height > maxHeight)
maxHeight = height;
currentColumn ++;
} while (currentColumn < self.numberOfColumns);
return CGSizeMake(self.collectionView.frame.size.width, maxHeight);
}
Done!運行下效果如何:
你可以在這里下載完整的代碼璃谨。如果你覺得對你有幫助沙庐,希望你不吝嗇你的star:)